首页
留言
关于
友链
更多
足迹
Search
1
SpringMVC+Spring+MyBatis整合完整版Web实例(附数据)
2,910 阅读
2
关于在Flutter实现Google地图的方法
1,879 阅读
3
druid报异常 “sql injection violation, part alway true condition not allow”的解决方案
1,375 阅读
4
git删除remote
1,338 阅读
5
MyBatis的TooManyResultsException异常的解决办法
1,149 阅读
发现
技术
生活
户外
登录
Search
标签搜索
Git
JavaScript
Flutter
Oracle
Git学习
Java
MySQL
SQL Server
秦岭户外
IntelliJ IDEA
Spring Boot
Flutter 2.0
对称加密算法
Google地图
Maven
ES6
linux
Tomcat
Redis
Spring
Bai Keyang
累计撰写
288
篇文章
累计收到
277
条评论
首页
栏目
发现
技术
生活
户外
页面
留言
关于
友链
足迹
搜索到
4
篇与
ES6
的结果
2022-02-04
ES6之变量的解构赋值(Destructuring)
ES6中解构赋值主要分为6类,分别为 数组解构赋值 、对象解构赋值 、字符串解构赋值 、数值和布尔值解构赋值 、函数参数解构赋值 。什么是解构?在ES6中允许按照一定的模式从数组和对象中提取值,然后对变量进行赋值,这被称为解构(Destructuring)数组解构赋值数组的解构赋值时,等号的右边必须是数组,否则将会报错。只要数据结构具有Iterator借口,则都可以采用数组形式的解构赋值。let [a, b, c] = [1, 2, 3]; a // 1 b // 2 c // 3这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些常见的解构例子:let [f, [[k], y]] = [1, [[2], 3]]; f // 1 k // 2 y // 3 let [ , ,t] = ["php", "java", "go"]; t // go let [x, ,y] = ["php", "java", "go"]; x // php y // go let [k, ...t] = [1, 2, 3, 4]; k // 1 t // [2, 3, 4] let [x, y, ...z] = ["php"]; x // php y // undefined z // []当解构不成功,变量的值就等于undefined。下面代码中,f均解构不成功,为undefined。let [f] = []; let [k, f] = [1];Set结构也可以使用数组的解构赋值,如下:let [x, y, z] = new Set(["php", "java", "go"]); x // php y // java z // go还有一种情况是部分解构成功,如下:let [x, y] = [1, 2, 3]; x // 1 y // 2 let [a, [b], c] = [1, [2, 3], 4] a // 1 b // 2 c // 4以上情况是不完全解构,即等号左边的模式只能匹配一部分等号右边的数组。但是这种情况下,结构依然可以成功。let [f] = 1; let [f] = false; let [f] = NaN; let [f] = undefined; let [f] = null; let [f] = {};以上语句都会报错,因为等号右边的值或是转为对象以后不具备iterator接口(前五个表达式),或是本事就不具备Iterator接口(最后一个表达式)数组解构赋值中允许指定 默认值 ,如:let [f = true] = []; f // true let [a, b = 'x'] = ['y']; a // y b // x let [a, b = 'c'] = ['k', undefined]; a // k b // c let [x = 1] = [undefined]; x // 1 let [x = 1] = [null]; x // null let [x = 1, y = x] = []; // x = 1, y = 1 let [x = 1, y = x] = [2]; // x = 2, y = 2 let [x = 1, y = x] = [1, 2]; // x = 1, y = 2 let [x = y, y = 1] = []; // 报错:ReferenceError最后一个表达式之所以报错,是因为x引用了默认值y时,y还没有被声明。注:ES6内部使用严格相等运算符(===)判断一个位置是否有值。如果一个数组成员不严格等于undefined,默认值是无法生效的。对象解构赋值对象的解构与数组有一个重要的不同:数组的元素是按次序排列的,变量的取值是由它的位置决定的;而对象的属性没有次序,变量必须与属性同名才能取到正确的值。示例如下:let {f, b} = {f: "AA", b: "KK"}; // f --> AA b --> KK let {c} = {f: "AA", b: "KK"}; // c --> undefined如果变量名与属性名不一致,必须用如下的形式:let {f: b} = {f: 'AA', b: 'CC'}; // b --> AA let obj = {hello: 'AA', world: 'CC'}; let {hello: h, world: w} = obj; // h --> hello w --> world hello --> error: hello is not defined对象的解构赋值的内部机制是先找到同名属性,然后再赋值给对应的变量。真正被赋值的是后者,而不是前者。对象解构也可以指定默认值:let {x = 3} = {}; // x --> 3 let {x, y = 5} = {x: 1}; // x --> 1 y --> 5 let {x : y = 3} = {}; // y --> 3 let {x : y = 3} = {y : 5}; // y --> 5 let {x = 3} = {x : undefined}; // x --> 3 let {x = 3} = {x : null}; // x --> null默认值生效的条件是对象的属性值严格等于undefined如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错,如下:let {f: {bo}} = {bc: "bucc"}以上代码报错的原因是因为f此时等于undefined,再取子属性就会报错。如果将一个已经声明的变量用于解构赋值,必须非常小心。 常见的错误如下:// 错误的写法 let x; {x} = {x : 1}; // SyntaxError: syntax error // 正确的写法 let x; ({x} = {x : 1});以上代码中第一部分会报错,是因为JavaScript引擎会将{ x }理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免JavaScript将其解释为代码块,才能解决这个问题。解构赋值允许等号左边的模式之中不放置任何变量名。虽然此类表达式毫无意义,但是语法合法,且可以正常执行:({} = [true, false]); ({} = 'abc'); ({} = []);由于数组本质是特殊的对象,因为可以对数组进行对象属性的解构:let arr = [1, 2, 3]; let {0: first, [arr.length - 1]: last} = arr; // first --> 1 last --> 3字符串解构赋值字符串在进行解构赋值时,字符串被转换成了一个类似数组的对象:let [a, b, c, d, e] = 'hello'; // a -->h a -->e a -->l a -->l a -->o类似数组的对象都有一个length的属性,因为还可以对这个属性进行解构赋值:let {length : len} = 'hello'; // len --> 5数值和布尔值解构赋值在数值和布尔值解构赋值时,如果等号右边是数值或布尔值,则会先转为对象。let {toString:s} = 123; s === Number.prototype.toString; // true let {toString:s} = true; s === Boolean.prototype.toString; // true解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。let {prop: x} = undefined; // TypeError let {prop: y} = null; // TypeError函数参数解构赋值函数的参数也可以使用解构赋值。function add([x, y]) { return x + y; } add([3, 2]); // 5 [[1,2],[3,4]].map(([a, b]) => a + b); // [3, 7]函数参数的解构也可以使用默认值:// 示例一 function move ({x = 0, y = 0} = {}) { return [x, y] } move({x: 3, y: 9}); // [3, 9] move({x: 3}); // [3, 0] move({}); // [0, 0] move(); // [0, 0] // 示例二 function move({x, y} = {x : 0, y : 0}) { return [x, y]; } move({x: 3, y: 9}); // [3, 9] move({x: 3}); // [3, undefined] move({}); // [undefined, undefined] move(); // [0, 0]关于圆括号的问题解构赋值虽然很方便,但是解析起来并不容易。对于编译器来说,一个式子到底是模式还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。ES6规则是只要有可能导致解构的歧义,就不得使用圆括号。但是这个在实际中并不那么容易判断,且处理起来相当的麻烦。所以也建议,只要有可能就不要在模式中放置圆括号。不能使用圆括号的情况情况一:变量声明语句let [(a)] = [1]; // 错误 let {x : (c)} = {};// 错误 let ({x : c}) = [1];// 错误 let {(x : c)} = [1];// 错误 let {(x) : c} = [1];// 错误 let {o : ({p:p})} = {o: {p: 2}};// 错误类似以上代码语句都会报错,因为它们都是变量声明语句,模式不能使用圆括号。情况二:函数参数函数参数也属于变量声明,因此不能使用圆括号function f ([(z)]) { return z; }// 错误 function f ([z, (x)]) {return x; }// 错误情况三:赋值语句的模式// 将整个模式放入圆括号之中导致报错 ({p : a}) = {p: 4};// 错误 ([a]) = [5];// 错误 // 将一部分模式放入圆括号之中导致报错 [({p: a}), {x: c}] = [{}, {}];// 错误可以使用圆括号的情况可以使用圆括号的情况只有一种:赋值语句的非模式部分可以使用圆括号。[(b)] = [3]; // 正确 ({p: (d)} = {});// 正确 [(parseInt.prop)] = [3];// 正确以上三个语句均执行正确,因为他们的都是赋值语句,而不是声明的语句,另外它们的圆括号都不属于模式的一部分。用途变量的解构赋值用途很多:交换变量的值let x = 1; let y = 2; [x, y] = [y, x];从函数返回多个值函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里面返回。// 返回一个数组 function test1() { return [1, 2, 3]; } let [a, b, c] = test1(); // 返回一个对象 function test2() { return { a: 2, b: 6 }; } let {a, b} = test2();函数参数的定义解构赋值可以方便的将一组参数与变量名对应起来// 参数是一组有序的值 function f1([x, y, z]) { ... } f1([1, 2, 3]); // 参数是一组无序的值 function f2({x, y, z}}) { ... } f2({z: 8, x: 6, y: 4});提取JSON数据let jsonData = { id: 2, name: 'LiMing', data: [867, 22] } let {id, name, data: nums} = jsonData; console.log(id, name, nums); // 2, "LiMing", [867, 22]函数参数的默认值jQuery.ajax = function (url, { async = true, cache = true, complate = function(){} }) { // do stuff } 遍历Map结构任何部署了Iterator接口的对象都可以用for...of 循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值获取键名和键值就非常方便。let map = new Map(); map.set('one', 'hello'); map.set('two', 'world'); for(let [key , value] of map) { console.log(key, value); } // 获取键名 for(let [key] of map) { console.log(key); } // 获取键值 for(let [, value] of map) { console.log(value); }输入模块的指定方法:加载模块时往往需要指定输入的方法。解构赋值使得输入语句非常清晰:const {SourceMapConsumer, SourceNode} = require("source-map");
2022年02月04日
211 阅读
0 评论
0 点赞
2021-03-09
ES6标准入门之const命令
const声明一个只读的常量,一旦声明,常量的值就不能改变const P = 90; console.log(P); P = 88; // Uncaught TypeError: Assignment to constant variable.const声明的变量值不可改变,就意味着const一旦声明就必须立即初始化:const P; // Uncaught SyntaxError: Missing initializer in const declarationconst只声明不赋值就会报错const和let的作用域相同,只在声明所在的块级作用域内有效:if (true) { const P = 5; } P // Uncaught ReferenceError: P is not definedconst声明的常量同样存在暂时性死区,只能在声明的位置后使用:if (true) { console.log(P); // ReferenceError const P = 5; }P声明之前就调用,结果报错const声明的常量,与let一样,不可重复声明:var message = "Hello!"; let age = 25; // 以下两行都会报错 const message = "Goodbye!"; const age = 30;理解:const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性:const foo = {}; // 为 foo 添加一个属性,可以成功 foo.prop = 123; foo.prop // 123 // 将 foo 指向另一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错:const a = []; a.push('Hello'); // 可执行 a.length = 0; // 可执行 a = ['Dave']; // 报错使用Object.freeze方法将常量foo指向的一个对象冻结,所以添加新属性不起作用,严格模式时还会报错。const foo = Object.freeze({}); // 常规模式时,下面一行不起作用; // 严格模式时,该行会报错 foo.prop = 123;除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach( (key, i) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } }); };扩展:ES6 声明变量的六种方法:ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。
2021年03月09日
438 阅读
0 评论
0 点赞
2018-05-30
ES6标准入门(二)块级作用域
在ES5中只有全局的作用域和函数作用域,没有块级作用域。这导致在很多场合不合理。如:var temp = new Date(); function go () { console.log(temp); if(false){ var temp = 'hello world'; } } go(); // undefined上面代码的本意是,if代码块的外部使用外层的temp变量,内部使用内层的temp变量。但是go函数执行后,输出的结果是undefined,原因在于变量提升导致内层的temp变量覆盖了外层的temp变量。还有一种情况,用来计数的循环变量泄露为全局变量。如:var s = 'hello'; for(var i = 0; i < s.length; i++){ console.log(s[i]); } console.log(i);//5上面代码中,变量i只用来控制循环,但是循环结束后i并没有消失,而是泄露成了全局变量。ES6块级作用域 let实际上为Javascript新增了块级作用域。function f1 () { let n = 6; if(true){ let n = 10; } console.log(n); //6 }上面代码的函数有两个代码块都声明了变量n,运行后结果输出5。这就表明外层代码块不受内层代码块的影响。如果使用var定义变量的n话,最后输出的值就是10了。 在ES6中允许块级作用域的任意嵌套。如:{{{{ let text = 'Hello World'; }}}};上面代码嵌套了4层的块级作用域。外层作用域无法读取内层作用域的变量。如:{{{{ {let text = 'Hello World';} console.log(text);// 报错 }}}};内层作用域可以定义外层作用域的同名变量。{{{{ let text = 'Hello World'; {let text = 'Hello World';} }}}};块级作用域的出现,实际上使得获得广泛应用的立即执行匿名函数(IIFE)不再必要了。// IIFE写法 (function (){ var temp = 'hello'; ... }()); // 块级作用域写法 { let temp = 'hello'; ... }块级作用域的函数声明 在ES5中规定,函数只能在顶层作用域和函数作用域中声明,不能在块级作用域中声明。如:// 情况一 if(true){ function f() {} } // 情况二 try{ function f() {} }catch(e){ //... }上面这两种函数声明在ES5中都是非法的。但是实际情况是,以上两种情况都能运行,并不会报错。原因是,浏览器没有遵守这个规定,浏览器为了兼容以前的旧代码,还是支持在块级作用域中声明函数。 在ES6中引入了块级作用域,明确允许在块级作用域中声明函数。ES6规定,在块级作用域中函数声明语句的行为类似于let,在块级作用域之外不可应用。如:function f() {console.log('我在外面');} (function(){ if(false){ function f() {console.log('我在里面');} } f(); }());上面代码在ES5中的运行结果是 “我在里面”,因为在if内声明的函数f会被提升到函数头部,实际代码运行如下:// ES5环境 function f() {console.log('我在外面');} (function(){ function f() {console.log('我在里面');} if(false){ } f(); }());而在ES6中运行就完全不一样了,理论上会得到“我在外面”。因为在块级作用域内声明的函数类似于let,对作用域之外的没有影响。 但是真正在ES6浏览器中运行却会报错。这是不是很奇怪? 由于如果改变了块级作用域内声明的函数的处理规则,显然势必会对旧的代码产生非常大的影响。为了减轻因此产生的不兼容问题,ES6在附录B(http://www.ecma-international.org/ecma-262/6.0/index.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics)中规定,浏览器的实现可以不遵守上面的规定,可以有自己的行为方式(https://stackoverflow.com/questions/31419897/what-are-the-precise-semantics-of-block-level-functions-in-es6),具体如下:1、允许在块级作用域内声明函数。2、函数声明类似于var,即会提升到全局作用域或函数作用域的头部。3、同时,函数声明还会提升到所在的块级作用域的头部。需要特别注意,上面3条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。根据这3条规则,在浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。如:// 浏览器的 ES6 环境 function f() { console.log('我在外面'); } (function () { if (false) { function f() { console.log('我在里面'); } } f(); }()); // Uncaught TypeError: f is not a function尽管如此,上面的代码在符合ES6的浏览器中都会报错,因为实际运行的下面的代码:// 浏览器的 ES6 环境 function f() { console.log('我在外面'); } (function () { var f = undefined; if (false) { function f() { console.log('我在里面'); } } f(); }()); // Uncaught TypeError: f is not a function所以,考虑到环境导致的行为差异太大,我们应该避免在块级作用域中声明函数。如果确实需要,也应该写成函数表达式的形式,而不是函数声明语句。如:// 函数声明语句 { let a = 'secret'; function f() { return a; } } // 函数表达式 { let a = 'secret'; let f = function () { return a; }; }另外,还有一个需要注意的地方。在ES6的块级作用域允许声明函数的规则只在使用大括号的情况下才成立,如果没有使用大括号,就会报错。如:// 不报错 'use strict'; if (true) { function f() {} } // 报错 'use strict'; if (true) function f() {} do表达式 本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。如:{ let t = f(); t = t * t + 1; }在ES6中有一个提案(http://wiki.ecmascript.org/doku.php?id=strawman:do_expressions),使得块级作用域可以变为表达式,即可以返回值,办法就是在块级作用域之前加上do,使它变为do表达式。如:let x = do { let t = f(); t * t + 1; }上面的代码中,变量x会得到整个块级作用域的返回值。
2018年05月30日
295 阅读
0 评论
0 点赞
2018-05-22
ES6标准入门(一)let命令
let命令,用于声明变量,用法和var类似,但是所声明的变量只在let命令所在的代码块内有效。{ let a = 1; var b = 2; } console.log(a);// 报错:ReferenceError: a is not defind. console.log(b);// 2在代码块外面调用了let命令声明的a就会报错,而调用var声明的b返回了正常的值。这就说明let声明的变量只在其所在的代码块中有效。下面是一个关于let和var在for循环中的例子:使用var声明变量的for循序特殊例子:var a = []; for(var i = 0; i < 10; i++){ a[i] = function(){ console.log( i ); } } a[6] (); // 10 变量i是var声明的,在全局范围内有效,所以全局只有一个变量,每一次循环i的值都会发生改变,被赋值给数组a的函数内部的console.log(i) 的i指向的是全局的i。也就是说,所有数组a的成员中i都指向的是同一个i,导致输出时输出的是最后一轮的i,也就是10。使用let声明变量的for循序特殊例子:var a = []; for(let i = 0; i < 10; i++){ a[i] = function(){ console.log( i ); } } a[6] (); // 6 变量i是let声明的,i只在本轮循环内有效,所以每一次循环的i其实都是一个新的变量。这是因为在Javascript引擎内部会记住上一轮的循环的值,初始化本轮的变量i时,就会在上一轮循环的基础上进行计算。for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。for(let i = 0; i <i 10; i++){ let i = 'abc'; console.log( i ); } // abc // abc // abc ……上面的结果就是连续输出10次的'abc'。这就表明函数内部的变量i与循环变量i不再同一个作用域。let不存在变量提升的现象。使用var命令声明的变量会发生 “变量提升”现象,就是变量可以在声明之前使用,值为undefined。按照一般的逻辑,变量应该是在声明之后才能被使用的。当然,在ES6中,let命令改变了语法行为,它所声明的变量一定要在声明后使用。如:// var的情况 console.log(foo);// 输出undefined var foo = 2; // let的情况 console.log(fo);// 报错 ReferenceError let fo = 2; let暂时性死区只要在块级作用域存在let命令,则它所声明的变量就 “绑定”(binding) 这个区域,不再受外部的影响。如:var temp = 123; if(true){ temp = 'abc'; // ReferenceError let temp; }上面代码中存在全局变量temp,但是在块级作用域内let又声明了一个局部变量temp,导致后者绑定这个块级作用域,所以在let声明变量之前对temp赋值会报错。在ES6中,如果区域块中存在let和const命令,则这个区域块对这些命令声明的变量从一开始就会形成封闭作用域。只要是在声明之前使用这些变量,则就会报错。所以在代码块内,使用let命令声明变量之前,该变量都是不可用的,这在语法上称为“暂时性死区”(temporal dead zone,简称TDZ)。如:if(true){ // TDZ 开始 temp = 'abc'; // ReferenceError console.log(temp); //ReferenceError let temp; // TDZ 结束 console.log(temp); // undefined temp = 123; console.log(temp); // 123 }上面代码中,在let命令声明变量temp之前都是变量temp的“死区”。这里要提到的一点是,“暂时性死区”也意味着typeof不再是一个百分百安全的操作了。typeof a;// ReferenceError let a;上面代码变量a使用let声明,在声明之前都属于a的“死区”,只要该变量被使用就会报错。但是,如果一个变量根本就没有被声明,使用typeof反而不会报错。typeof okey; // undefined上面代码中okey是一个不存在的变量,所以结果返回undefined。在没有let之前,typeof运算符是百分之百的安全,永远不会报错。这样的设计初衷也是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就会报错。在我们平时的实际使用中,还有一些比较隐蔽的“死区”,这样的“死区”不太容易被发现。如:function go (x = y, y = 6) { return [x, y]; } go(); // 报错上面代码中,调用go函数之所以报错是因为参数x值等于另外一个参数y,而此时y还没有被声明,属于“死区”。反过来如果y的默认值是x的话,则就不会报错,因为此时x已经被声明了。如:function go (x = 6, y = x) { return [x, y]; } go(); // [6, 6]另外,下面的代码也会报错。如:// 不报错 var x = x; // 报错 let y = y; // ReferenceError: y is not defined上面代码报错也是暂时性死区。在使用let声明变量时,只要变量在还没有声明前使用就会报错。ES6规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。暂时性死区的本质就是,只要进入当前作用域,所要使用的变量就已经存在,但是不可获取,只有等到声明变量的那一行代码出现后,才可以获取和使用该变量。let不允许重复声明let不允许在相同的作用域内重复声明同一个变量。如:// 报错 function () { let a = 1; var a = 10; } // 报错 function () { let a = 1; let a = 10; } function func(arg) { let arg;// 报错 } function (arg) { { let arg; // 不报错 } }
2018年05月22日
262 阅读
0 评论
0 点赞