let和const 介绍let和const之前先说说var关键字,他有几个特点:
可以重复声明
无法限制修改
没有块级作用域1 2 3 4 for (var i =0 ; i < 10 ; i++) { console .log (i) } console .log (i)
let的使用 基本使用 ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { let a = 10 ; var b = 1 ; } a b for (let i = 0 ; i < 10 ; i++) { } console .log (i);
不存在变量提升 var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。
为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
1 2 3 4 5 6 7 console .log (foo); var foo = 2 ;console .log (bar); let bar = 2 ;
暂时性死区 在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
1 2 3 4 5 6 7 8 9 10 11 if (true ) { tmp = 'abc' ; console .log (tmp); let tmp; console .log (tmp); tmp = 123 ; console .log (tmp); }
上面代码中,在let命令声明变量tmp之前,都属于变量tmp的“死区”。
1 2 3 4 typeof x; let x;typeof undeclared_variable
“暂时性死区”也意味着typeof不再是一个百分之百安全的操作。
不允许重复声明 let不允许在相同作用域内,重复声明同一个变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function func ( ) { let a = 10 ; var a = 1 ; } function func ( ) { let a = 10 ; let a = 1 ; } function func (arg ) { let arg; } func () function func (arg ) { { let arg; } } func ()
ES6 的块级作用域 let实际上为 JavaScript 新增了块级作用域。
1 2 3 4 5 6 7 function f1 ( ) { let n = 5 ; if (true ) { let n = 10 ; } console .log (n); }
const使用 const声明一个只读的常量。一旦声明,常量的值就不能改变。 const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。 const的作用域与let命令相同:只在声明所在的块级作用域内有效。 const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。 const声明的常量,也与let一样不可重复声明。
箭头函数 基本使用 ES6 允许使用“箭头”(=>)定义函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var f = v => v;var f = function (v ) { return v; }; var f = ( ) => 5 ;var f = function ( ) { return 5 };var sum = (num1, num2 ) => num1 + num2;var sum = function (num1, num2 ) { return num1 + num2; };
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
1 var sum = (num1, num2 ) => { return num1 + num2; }
箭头函数的一个用处是简化回调函数。
1 2 3 4 5 6 7 [1 ,2 ,3 ].map (function (x ) { return x * x; }); [1 ,2 ,3 ].map (x => x * x);
1 2 3 4 5 6 7 var result = values.sort (function (a, b ) { return a - b; }); var result = values.sort ((a, b ) => a - b);
使用注意点 箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var handler = { id : '123456' , init : function ( ) { document .addEventListener ('click' , event => this .doSomething (event.type ), false ); }, doSomething : function (type ) { console .log ('Handling ' + type + ' for ' + this .id ); } }; handler.init ()
这里要理解this的指向问题
this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
所以,箭头函数转成 ES5 的代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function foo ( ) { setTimeout (() => { console .log ('id:' , this .id ); }, 100 ); } function foo ( ) { var _this = this ; setTimeout (function ( ) { console .log ('id:' , _this.id ); }, 100 ); }
上面代码中,转换后的 ES5 版本清楚地说明了,箭头函数里面根本没有自己的this,而是引用外层的this。
请问下面的代码之中有几个this?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function foo ( ) { return () => { return () => { return () => { console .log ('id:' , this .id ); }; }; }; } var f = foo.call ({id : 1 });var t1 = f.call ({id : 2 })()(); var t2 = f ().call ({id : 3 })(); var t3 = f ()().call ({id : 4 });
解构赋值 ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。 什么使模式匹配,从数组或对象等号左边和等号右边模式相同,对应的变量就是对应的值
数组的解构赋值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var a = 1 ;var b = 2 ;var c = 3 ;var a = 1 , b = 2 , c = 3 ;let [a, b, c] = [1 , 2 , 3 ]let let [x, , y] = [1 , 2 , 3 ]; let [head, ...tail] = [1 , 2 , 3 , 4 ]; let [x, y, ...z] = ['a' ];x y z
如果解构不成功变量的值就会等于undefined
不完全结构嵌套
1 2 let [a, [b, c], d] = [1 , [2 , 3 , 4 ], 5 ]let [x, y] = [1 , 2 , 3 ];
上述代码中左右两边对应的结构式相同的,解构过程中会根据从左到右顺序一次给对应的变量赋值
注意:如果等号右边不是数组,则会报错
1 2 3 4 5 6 var [a] = 1 var [c] = true var [a] = {}var [a] = undefined var [a] = null
可以设置默认值
1 2 3 4 5 6 7 8 let [foo = true ] = [];foo let [x, y = 'b' ] = ['a' ]; let [x, y = 'b' ] = ['a' , undefined ]; let [x = 1 ] = [undefined ];x let [x = 1 ] = [null ];x
以上的代码总结:设置默认是es6内部使用严格(===)进行判断变量是否为undefined,只有等于undefined时,默认值设置才会生效
1 2 3 4 5 let [x = 1 , y = x] = []; let [x = 1 , y = x] = [2 ]; let [x = 1 , y = x] = [1 , 2 ]; let [x = y, y = 1 ] = [];
默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
对象的解构赋值 对象的解构赋值和数组时类似 对象和变量解构对应,不同的是,属性必须是对象中存在的属性,否则变量就会等于undefined
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let { foo, bar } = { foo : "aaa" , bar : "bbb" };foo bar let { baz } = { foo : "aaa" , bar : "bbb" };baz let { foo : baz } = { foo : 'aaa' , bar : 'bbb' };baz let obj = { first : 'hello' , last : 'world' };let { first : f, last : l } = obj;f l let { foo : foo, bar : bar } = { foo : "aaa" , bar : "bbb" };
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
1 2 3 let { foo : baz } = { foo : "aaa" , bar : "bbb" };baz foo
上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。
对象的解构也可以指定默认值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var {x = 3 } = {};x var {x, y = 5 } = {x : 1 };x y var {x : y = 3 } = {};y var {x : y = 3 } = {x : 5 };y var { message : msg = 'Something went wrong' } = {};msg var {x = 3 } = {x : undefined };x var {x = 3 } = {x : null };x
字符串的解构赋值 字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
1 2 3 4 5 6 const [a, b, c, d, e] = 'hello' ;a b c d e
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
1 2 let {length : len} = 'hello' ;len
用途 变量的解构赋值用途很多。
(1)交换变量值
1 2 3 4 let x = 1 ;let y = 2 ;[x, y] = [y, x];
(2)从函数返回多个值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function example ( ) { return [1 , 2 , 3 ]; } let [a, b, c] = example ();function example ( ) { return { foo : 1 , bar : 2 }; } let { foo, bar } = example ();
(3)提取 JSON 数据
1 2 3 4 5 6 7 8 9 10 let jsonData = { retCode : '000000' , retInfo : "字段校验成功" , data : [867 , 5309 ] }; let { retCode, retInfo, data : number } = jsonData;console .log (id, status, number);
等等。。。。。。
对象、数组、字符串的扩展 对象的扩展 对象定义过程中更简单写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let io = 'computed' const obj = { io, foo ( ) { console .log ('hello' ) } } var io = 'computed' var obj = { io : io, foo : function ( ) { console .log ('hello' ) } }
数组的扩展 (1)扩展运算符 扩展运算符(spread)是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
1 2 3 console .log (1 , ...[2 ,3 ,4 ]) console .log (1 ,2 ,3 ,4 )
1 2 3 4 5 6 7 8 function add (x, y ) { return x + y } let number = [35 , 27 ]add (...number) add (number[0 ], number[1 ])
1 2 3 4 5 6 7 8 9 10 11 12 13 Math .max .apply (null , [14 , 3 , 77 ])Math .max (...[14 , 3 , 77 ])Math .max (14 , 3 , 77 );new (Date .bind .apply (Date , [null , 2015 , 1 , 1 ]))new Date (...[2015 , 1 , 1 ]);
扩展运算符的应用
1.复制数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const arr = ['hello' , 'world' ]const arrCopy = arrarr[0 ] = 1 console .log (arrCopy) const arr = ['hello' , 'world' ]const arrCopy = arr.concat ()arr[0 ] = 'OK' console .log (arrCopy) const a1 = [1 , 2 ];const a2 = [...a1];const [...a2] = a1;
2.合并数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const a = [2 , 3 , 5 ]const b = [12 , 13 ]a.concat (b) const result = [...a, ...b] const a1 = [{ foo : 1 }];const a2 = [{ bar : 2 }];const a3 = a1.concat (a2);const a4 = [...a1, ...a2];a3[0 ] === a1[0 ] a4[0 ] === a1[0 ]
3.与解构赋值结合 扩展运算符可以与解构赋值结合起来,用于生成数组。
1 2 3 4 a = list[0 ], rest = list.slice (1 ) [a, ...rest] = list
1 2 3 4 5 6 7 8 9 10 11 const [first, ...rest] = [1 , 2 , 3 , 4 , 5 ];first rest const [first, ...rest] = [];first rest const [first, ...rest] = ["foo" ];first rest
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
1 2 3 4 5 const [...butLast, last] = [1 , 2 , 3 , 4 , 5 ];const [first, ...middle, last] = [1 , 2 , 3 , 4 , 5 ];
(2)Array.from() Array.from方法用于将两类对象转为真正的数组:类似数组的对象(伪数组)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
1 2 3 4 5 6 7 8 9 10 11 12 let arrayLike = { '0' : 'a' , '1' : 'b' , '2' : 'c' , length : 3 }; var arr1 = [].slice .call (arrayLike); let arr2 = Array .from (arrayLike);
实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。
1 2 3 4 5 6 7 8 9 10 11 let ps = document .querySelectorAll ('p' );Array .from (ps).filter (p => { return p.textContent .length > 100 ; }); function foo ( ) { var args = Array .from (arguments ); }
(3)Array.of() Array.of方法用于将一组值,转换为数组。
1 2 3 Array .of (3 , 11 , 8 ) Array .of (3 ) Array .of (3 ).length
(4)数组实例的 includes() Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。ES2016 引入了该方法。 参数: 该方法接受两个参数,第一个参数接受一个数值,第二个参数搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。 返回值: 布尔类型,true表示搜索的结果在当前的数组中
1 2 3 4 5 6 [1 , 2 , 3 ].includes (2 ) [1 , 2 , 3 ].includes (4 ) [1 , 2 , NaN ].includes (NaN ) [1 , 2 , 3 ].includes (3 , 3 ); [1 , 2 , 3 ].includes (3 , -1 );
与indexOf的比较:indexOf的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判。
下面代码用来检查当前环境是否支持该方法,如果不支持,部署一个简易的替代版本。
1 2 3 4 5 6 const contains = (() => Array .prototype .includes ? (arr, value ) => arr.includes (value) : (arr, value ) => arr.some (el => el === value) )(); contains (['foo' , 'bar' ], 'baz' );
字符串的扩展 模版字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
传统的 JavaScript 语言,输出模板通常是这样写的(下面使用了 jQuery 的方法)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $('#result' ).append ( 'There are <b>' + basket.count + '</b> ' + 'items in your basket, ' + '<em>' + basket.onSale + '</em> are on sale!' ); $('#result' ).append (` There are <b>${basket.count} </b> items in your basket, <em>${basket.onSale} </em> are on sale! ` );
1 2 3 4 5 6 7 8 9 10 11 12 13 `In JavaScript '\n' is a line-feed.` `In JavaScript this is not legal.` console .log (`string text line 1 string text line 2` );let name = "Bob" , time = "today" ;`Hello ${name} , how are you ${time} ?`
函数的扩展 (1)函数参数的默认值 es6增加了函数参数的默认值
1 2 3 4 5 6 7 8 9 10 11 function Foo (x, y) { this .x = x || 'hello' ; this .y = y || 'world' } function Foo (x = 'hello' , y = 'world' ) { this .x = x ; this .y = y' }
设置函数的默认参数之后,函数内部不能再次声明,否则会报错
1 2 3 4 function boo (x = 1 ) { let x; }
另外,一个容易忽略的地方是,参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。
1 2 3 4 5 6 7 8 9 let x = 99 ;function foo (p = x + 1 ) { console .log (p); } foo () x = 100 ; foo ()
上面代码中,参数p的默认值是x + 1。这时,每次调用函数foo,都会重新计算x + 1,而不是默认p等于 100。
(2)rest 参数 ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
1 2 3 4 5 6 7 8 9 10 11 function add (...values ) { let sum = 0 ; for (var val of values) { sum += val; } return sum; } add (2 , 5 , 3 )
上面代码的add函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。
下面是一个 rest 参数代替arguments变量的例子。
1 2 3 4 5 6 7 function sortNumbers ( ) { return Array .prototype .slice .call (arguments ).sort (); } const sortNumbers = (...numbers ) => numbers.sort ();
上面代码的两种写法,比较后可以发现,rest 参数的写法更自然也更简洁。
arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。下面是一个利用 rest 参数改写数组push方法的例子。
1 2 3 4 5 6 7 8 9 function push (array, ...items ) { items.forEach (function (item ) { array.push (item); console .log (item); }); } var a = [];push (a, 1 , 2 , 3 )
ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
1 2 3 4 5 6 7 8 9 10 11 function add (...values ) { let sum = 0 ; for (var val of values) { sum += val; } return sum; } add (2 , 5 , 3 )
上面代码的add函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。
下面是一个 rest 参数代替arguments变量的例子。
1 2 3 4 5 6 7 function sortNumbers ( ) { return Array .prototype .slice .call (arguments ).sort (); } const sortNumbers = (...numbers ) => numbers.sort ();
上面代码的两种写法,比较后可以发现,rest 参数的写法更自然也更简洁。
arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。下面是一个利用 rest 参数改写数组push方法的例子。
1 2 3 4 5 6 7 8 9 function push (array, ...items ) { items.forEach (function (item ) { array.push (item); console .log (item); }); } var a = [];push (a, 1 , 2 , 3 )
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
1 2 3 4 function f (a, ...b, c ) { }
函数的length属性,不包括 rest 参数。
1 2 3 (function (a ) {}).length (function (...a ) {}).length (function (a, ...b ) {}).length
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
1 2 3 4 function f (a, ...b, c ) { }
函数的length属性,不包括 rest 参数。
1 2 3 (function (a ) {}).length (function (...a ) {}).length (function (a, ...b ) {}).length