Let、Const、${表达式}
、箭头函数、解构等你已经常用的特性
这里先大概说一下这些特性,后面详细剖析
- Let
let
声明的变量作用域仅在它所在的代码块内有效。1
2
3
4if(true) {
let x = 1;
}
console.log(x); // undefined
了解更多,可以查看[ES6深入剖析之Let和块作用域]
- Const
const
用来定义常量(静态变量): 一旦被定义就不能再被修改。1
2
3Const REG = /^\d{1,3}$/;
REG = /^\d{1,6}$/;
//报错 Unexpected identifier
- 常量一旦被定义后就不能再被修改。
- 定义在块作用域内部的常量,在外部不能访问到
- 在定义常量的前面,是不能访问到常量的,因此我们通常要将常量定义在文件的最前面
常量是不能被重复的定义的,这是为了保证常量定义使用时候的安全型。
模板字符串
- ‘这里写的字符串可以换行’,写模板再也不用去拼接字符串了
- ‘${这里可以写表达式}’,代替字符串拼接
1
2
3const book = "钢铁是怎样炼成的";
const intro = `<h1>我最喜欢的一本书是${book}</h1>
<p>赶紧介绍一下它</p>`;
箭头函数
- 基本用法
ES6 允许使用“箭头”(=>)定义函数。
1
2var f = v => v;
f(1); //1上面的箭头函数等同于:
1
2
3var f = function(v) {
return v;
};如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
1
2
3
4
5
6
7
8
9
10
11
12
13var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
```
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
```javascript
var sum = (num1, num2) => { return num1 + num2; }由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
1
2
3
4
5// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。
1
let fn = () => void doesNotReturn();
箭头函数可以与变量解构结合使用。
1
2
3
4
5
6const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
return person.first + ' ' + person.last;
}箭头函数使得表达更加简洁。
1
2const isEven = n => n % 2 == 0;
const square = n => n * n;箭头函数的一个用处是简化回调函数。
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);下面是 rest 参数(在函数的扩展中讲)与箭头函数结合的例子。
1
2
3
4
5
6
7const numbers = (...nums) => nums;
numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]
const headAndTail = (head, ...tail) => [head, tail];
headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]- 需要注意几点
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//普通函数
function foo() {
setTimeout(function() {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 }); // id: 21
//箭头函数
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 }); // id: 42
上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42。
箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。下面是另一个例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000);
// 普通函数
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0上面代码中,Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,timer.s1被更新了 3 次,而timer.s2一次都没更新。普通函数内部的this.s2从undefined变为NaN。
箭头函数可以让this指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM 事件的回调函数封装在一个对象里面。
1
2
3
4
5
6
7
8
9
10
11
12var handler = {
id: '123456',
init: function() {
document.addEventListener('click',
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log('Handling ' + type + ' for ' + this.id);
}
};this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
所以,箭头函数转成 ES5 的代码就是我们常用的this备份,如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
// ES5
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}请问下面的代码之中有几个this?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1上面代码之中,只有一个this,就是函数foo的this,所以t1、t2、t3都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的this,它们的this其实都是最外层foo函数的this。
除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments、super、new.target。
1
2
3
4
5
6
7
8function foo() {
setTimeout(() => {
console.log('args:', arguments);
}, 100);
}
foo(2, 4, 6, 8)
// args: [2, 4, 6, 8]上面代码中,箭头函数内部的变量arguments,其实是函数foo的arguments变量。
另外,由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。
1
2
3
4
5
6(function() {
return [
(() => this.x).bind({ x: 'inner' })()
];
}).call({ x: 'outer' });
// ['outer']上面代码中,箭头函数没有自己的this,所以bind方法无效,内部的this指向外部的this。
解构
解构提供了一个方便地从对象或数组中提取数据的方法。1
2
3
4
5
6let [x, y] = [1, 2]; // x = 1, y = 2
// ES5 equivalent:
var arr = [1, 2];
var x = arr[0];
var y = arr[1];利用解构,可以一次性给多个变量赋值。一个很好的附加用处是可以很简单地交换变量值:
1
2let x = 1,y = 2;
[x, y] = [y, x]; // x = 2, y = 1这让我想到一个有趣的问题:
如何在不定义第三个变量的情况下,交换x,y的值
1
2
3
4let x = 1,y = 2;
x = x + y; // x = x + y = 1 + 2 = 3
y = x - y; // y = (x + y) - y = x = 1
x = x - y; // x = (x + y) - x = y不过这里有解构我们可以更容易做到了
解构也可以用于对象。注意对象中必须存在对应的键,不然会获取到
undefined
:1
2let obj = {x: 1, y: 2};
let {x, y} = obj; // x = 1, y = 2也可以使用该机制来修改变量名:
1
2let obj = {x: 1, y: 2};
let {x: a, y: b} = obj; // a = 1, b = 2模拟多个返回值:
1
2
3
4
5function doSomething() {
return [1, 2]
}
let [x, y] = doSomething(); // x = 1, y = 2为参数对象赋默认值。通过对象字面量,可以模拟命名参数:
1
2
3
4function doSomething({y = 1, z = 0}) {
console.log(y, z);
}
doSomething({y: 2});
字符串方法扩展
- startsWith(): 判断字符串是以参数字符开头的
- 第一个参数就是字符串
- 第二个参数表示判断的位置(可不传)
- endsWith(): 判断元字符串是以参数字符串结尾的
- 第一个参数是判断的字符串
- 第二个参数表示判断的位置(可不传)
- includes():判断字符串是否包含参数字符串
- 第一个参数表示被包含的字符串
- 第二个参数判断的位置(可不传)
1
2
3
4
5
6
7
8var str = "我们是搜狐社交产品中心前端团队",
res1 = str.startsWith('我们', 0),
res2 = str.endsWith('团', 14),
res3 = str.includes('个', 2);
console.log(res1,res2,res3) //true true false
str.startsWith('我');str.startsWith('我们'); // true
str.endsWith("队");str.endsWith("团队"); // true
str.includes('是') // true
repeat(): 方法返回一个新字符串,表示将原字符串重复n次。
1
"a123".repeat(2); // "a123a123"
Number对象的扩展
- Number.isFanite(): 用于检查其参数是否是无穷大(不存在或者是NaN返回false,对于数字返回值true)
Number.isNaN(): 当参数是
NaN
时候,返回true
,其他情况都返回false
- 需要注意的是es5中判断一个变量是否为
NaN
采用isNaN()函数与这里ES6的Number
.isNaN()的区别()
1
2
3
4
5
6
7
8
9
10
11
12
13//预期应该只有NaN才会返回true
isNaN(undefined) //true
isNaN(NaN) //true
isNaN('qwer') //true
isNaN(123) //false
//不符合预期
Number.isNaN(undefined) //false
Number.isNaN(NaN) //true
Number.isNaN('qwer') //false
Number.isNaN(123) //false
//符合预期- 需要注意的是es5中判断一个变量是否为
Number.isInteger(): 用来判断一个值是否为整数
1
2
3
4
5Number.isInteger(2) // true
Number.isInteger(2.0) // true
Number.isInteger(2.1) // false
Number.isInteger("15") // false
Number.isInteger(true) // false
Array对象的扩展
Array.from() 用来将其他对象转换成数组
能转换的其他对象的要求:
1.部署了Iterator
接口的对象,比如:Set
,Map
,Array
。
2.类数组对象,什么叫类数组对象,就是一个对象必须有length
属性,没有length
,转出来的就是空数组。1
2
3
4
5
6
7//转换map
const map1 = new Map();
map1.set('k1', 1);
map1.set('k2', 2);
map1.set('k3', 3);
console.log('%s', Array.from(map1))
//k1,1,k2,2,k3,31
2
3
4
5//转换set
const set1 = new Set();
set1.add(1).add(2).add(3)
console.log('%s', Array.from(set1))
//1,2,31
2
3
4
5//转换字符串
console.log('%s', Array.from('hello world'))
console.log('%s', Array.from('\u767d\u8272\u7684\u6d77'))
//h,e,l,l,o, ,w,o,r,l,d
//白,色,的,海1
2
3
4
5
6
7
8//类数组对象
console.log('%s', Array.from({
0: '0',
1: '1',
3: '3',
length:4
}))
//0,1,,3Array.from可以接受三个参数
1
Array.from(arrayLike[, mapFn[, thisArg]])
arrayLike:被转换的的对象;
mapFn:map
函数;
thisArg:map
函数中this指向的对象;Array.of(): 将一组值转化成一个数组,为了解决源生Array构造函数创建数组的一个问题
Array
当传递参数不同,传递的参会表示不同的含义,如果一个参数,这个参数表示的数组的长度,当传递两个或者多个参数时候,这些表示数组的成员,of
为了解决Array
构造的参数不同得到结果行为不一致的问题,参数表示数组的成员,不论参数多少个1
2console.log(Array.of(5)) //[5]
console.log(Array.of(5, 6)) //[5,6]
数组实例的扩展
- copyWithin():在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。
1
Array.prototype.copyWithin(target, start = 0, end = this.length)
它接受三个参数,这三个参数都应该是数值,如果不是,会自动转为数值:
target(必需):从该位置开始替换数据。
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
1 | [1, 2, 3, 4, 5].copyWithin(0, 3) |
- find() 和 findIndex():
- find(): 数组实例的
find
方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true
的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined
。1
2
3[1, 3, 5, 7, 9, 11].find(function(value, index, arr) {
return value > 9;
}) // 11
可以看出,find
方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
- findIndex():数组实例的
findIndex
方法的用法与find
方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。1
2
3[1, 3, 5, 7, 9, 11].findIndex(function(value, index, arr) {
return value > 9;
}) // 5
这两个方法都可以接受第二个参数,用来绑定回调函数的this
对象。
另外,这两个方法都可以发现NaN
,弥补了数组的indexOf
方法的不足。1
2
3
4
5[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0
上面代码中,indexOf
方法无法识别数组的NaN成员,但是findIndex
方法可以借助Object.is方法做到。
- fill():
fill
方法使用给定值,填充一个数组。1
2
3
4
5['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(7)
// [7, 7, 7]
fill
方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。1
['a', 'b', 'c','d'].fill(7, 1, 3)
上面代码表示,fill
方法从 1 号位开始,向原数组填充 7,到 3 号位之前结束。
- entries(),keys() 和 values():
entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器(Iterator)对象,可以用for…of循环进行遍历。
keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
- includes():回一个布尔值,表示某个数组是否包含给定的值,与字符串的
includes
方法类似。1
2
3[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。1
2[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
没有该方法之前,我们通常使用数组的indexOf
方法,检查是否包含某个值。1
2
3if (arr.indexOf(el) !== -1) {
// ...
}
但是indexOf
方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判。1
2[NaN].indexOf(NaN)
// -1
includes使用的是不一样的判断算法,就没有这个问题。1
2[NaN].includes(NaN)
// true
另外,Map 和 Set 数据结构有一个has
方法,需要注意与includes
区分。
- Map 结构的has方法,是用来查找键名的,比如Map.prototype.has(key)、WeakMap.prototype.has(key)、Reflect.has(target, propertyKey)。
- Set 结构的has方法,是用来查找值的,比如Set.prototype.has(value)、WeakSet.prototype.has(value)。