ES6学习记录

记录一下ES6对函数、对象、数组的扩展,作为开发参考。

函数扩展

和解构赋值默认值结合

1
2
3
4
5
6
function foo({x, y = 5}) {
console.log(x, y);
}

foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

只有当函数foo的参数是一个对象时,变量xy才会通过解构赋值生成。

上面foo函数使用的是对象解构赋值默认值,而不是函数参数,所以报错了。

提供函数参数默认值:

1
2
3
4
5
function foo({x, y = 5} = {}) {
console.log(x, y);
}

foo() // undefined 5

注意

有默认值的函数参数如果不是尾参数,则不能忽略,否则报错,除非显示传入undefined,可以触发默认值,但是null不可以。

1
2
3
4
5
6
function f(x, y = 5, z) {
return [x, y, z];
}

f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]

对函数对象的影响

  1. 使length属性失真

    1
    2
    3
    4
    (function (a, b, c = 5) {}).length // 2
    /*如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。*/
    (function (a = 0, b, c) {}).length // 0
    (function (a, b = 1, c) {}).length // 1
  1. 作用域(重点)

    一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)(不设置参数默认值不会出现这个作用域)

    先看个栗子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var x = 1;
    function foo(x, y = function() { x = 2; }) {
    var x = 3;
    y();
    console.log(x);
    }

    foo() // 3
    x - 外部作用域x // 1

解释

foo函数形成一个单独作用域,y的默认值是一个函数,里面的变量x指向同一个作用域(即函数参数形成的单独作用域)的x,而foo函数里面也声明了一个x变量,但是由于不是同一个作用域,因此属于不同的变量。默认值函数也只是改变了同一作用域的x,并没有改变外部作用域和foo函数作用域的值。

改一下

1
2
3
4
5
6
7
8
9
var x = 1;
function foo(x, y = function() { x = 2; }) {
x = 3;
y();
console.log(x);
}

foo() // 2
x // 1

会发现默认函数生效了,全局变量依然不受影响。

解释

foo函数内部没有重新定义变量x,因此内部的x指向了函数参数x。

rest参数

形式

...变量名

先上栗子

1
2
3
4
5
6
7
8
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
});
}

var a = [];
push(a, 1, 2, 3);

可以看到rest参数搭配的后面的变量为一个数组。

rest参数是用来代替arguments变量的。

1
2
3
4
5
6
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
/*arguments是类数组,要使用数组方法要先转为数组*/
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();

箭头函数

使用注意

(1)不能用new

(2)不能作为Generator函数

(3)不能使用arguments,用rest参数代替(arguments指向外层函数)

(4)this 指向是固定的

箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function 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

ES6转成ES5会发现箭头函数没有自己的this,只是_this = this引用了外层的this。因此bind()、apply()、call()方法会失效。

箭头函数不适用场景

定义对象方法,且内部包括this。

需要动态this的时候。

看一个部署管道机制的栗子(前一个函数的输出是后一个函数的输入)

1
2
3
4
5
6
7
8
9
const pipeline = (...funcs) =>
val => funcs.reduce((a, b) => b(a), val);

const plus1 = a => a + 1;
const mult2 = a => a * 2;
const addThenMult = pipeline(plus1, mult2);

addThenMult(5)
// 12

数组扩展

... 扩展运算符

好比rest的逆运算,讲一个数组转为用都好分割的参数序列

强大的运算符

可放置表达式

1
2
3
4
const arr = [
...(x > 0 ? ['a'] : []),
'b',
];

空数组无效

1
2
[...[], 1]
// [1]

一个实际应用

1
2
3
4
5
6
7
8
// ES5 的写法
Math.max.apply(null, [14, 3, 77])

// ES6 的写法
Math.max(...[14, 3, 77])

// 等同于
Math.max(14, 3, 77);

合并/复制数组为浅拷贝

与解构赋值结合

1
2
3
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]

像是逆运算一样

注意

扩展运算符用于数组赋值,只能在参数最后一位,否则报错。

Array.from()

基本用法

将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象。

一个类似数组的对象转化为数组

1
2
3
4
5
6
7
8
9
10
11
12
13
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
'name': 'myname',
length: 3,
};

// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

...扩展运算符的区别

...只能装换Iterable对象,Array.from方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length属性。数组的长度由length决定。

1
2
Array.from({ length: 3 });
// [ undefined, undefined, undefined ]

另外,Array.prototype.slice.call(obj)能将具有length属性的对象转换成数组,也要求

有length属性。

[].prototype.slice.call(obj)第一眼看我有点懵。看看v8_array源码[].slice()的解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let i, cloned = [],
size, len = this.length;//要求对象需要length
size = end - start;

if (size > 0) {
cloned = new Array(size);
if (this.charAt) {//this是字符串对象的情况
for (i = 0; i < size; i++) {
cloned[i] = this.charAt(start + i);
}
} else {
for (i = 0; i < size; i++) {
cloned[i] = this[start + i];//bind(this)让this指向了obj
}
}
}

return cloned;
};

奥,我好了。

第二个参数

Array.from()还可以接受第二个参数,用来处理每个元素,Array.from()最后返回处理后的数组。

一个栗子

1
2
Array.from({ length: 2 }, () => 'jack')
// ['jack', 'jack']

如果第二个参数中用到了this,这个时候就可以利用第三个参数来绑定this了。

一个栗子

1
2
3
4
5
const obj = { name: "myname", age: 19, };

Array.from({ length: 3 }, function(item) {return this.name}, obj);
//注意这里的不能用箭头函数
// ["myname", "myname", "myname"]

arr.copyWithin(target, start = 0, end = this.length)

直接看栗子理解

1
2
[1, 2, 3, 4, 5].copyWithin(0, 3);
// [4, 5, 3, 4, 5]

上面代码表示将从 3 号位直到数组结束的成员(4 和 5),复制到从 0 号位开始的位置,结果覆盖了原来的 1 和 2。

arr.find(callback)

用于找出第一个符合条件的数组成员

arr.fill()

使用指定值,填充数组

1
2
['a', 'b', 'c'].fill(0)
//[0, 0, 0]

使用第二、三个参数,制定填充起始位置。

1
2
['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']

注意 起始位置参数和slice很像,都是[start, end)

遍历数组

1
2
3
4
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
//0 //1

ES6还提供了values()entries()来遍历值、键值对。

arr.includes()

返回一个布尔值

1
[1, 2, 3].includes(2)     // true

如果不支持,自己部署

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'); // => false

arr.flat()

“拉平”多维数组

flat()默认只会“拉平”一层

1
2
[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]

传入一个整数,可以指定”拉平”的层数

1
2
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]

传入Infinity,不管几层都变为一维

1
2
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]

注意

跳过空位

1
2
[1, 2, , 4, 5].flat()
// [1, 2, 4, 5]

arr.flatMap()

map() + flat(),flat()参数默认为1,因此只能展开一层

1
2
3
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]

对象扩展

super关键字

指向当前对象的原型对象

注意

只能用在对象方法中,目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。JavaScript 引擎内部,super.foo等同于Object.getPrototypeOf(this).foo

看个栗子理解一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const proto = {
x: 'hello',
foo() {
console.log(this.x);
},
};

const obj = {
x: 'world',
foo() {
super.foo();
}
}

Object.setPrototypeOf(obj, proto);

obj.foo() // "world"

proto.foo绑定的在执行时绑定的是obj,因此返回”world”。

解构赋值

1
2
3
4
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

注意

(1) 浅拷贝

1
2
3
4
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2

(2)扩展运算符的解构赋值,只能读取对象自身的属性

1
2
3
4
5
6
7
8
9
const o = Object.create({ x: 1, y: 2 });
o.z = 3;

let { x, ...newObj } = o;
let { y, z } = newObj;
x // 1
y // undefined
z // 3
newObj //{3}

ES6 规定,变量声明语句之中,如果使用解构赋值,扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式,下面写法会报错。

1
let { x, ...{ y, z } } = o;

所以上面用newObj做中间变量。

-------------要说再见啦感谢大佬的光临~-------------