ES6核心内容精讲--快速实践ES6(一)


前言

本文大量参考了阮一峰老师的开源教程ECMAScript6 入门和 MDN,适合新手入门或者对 ES6 常用知识点进行全面回顾,目标是以较少的篇幅涵盖 ES6 及部分 ES7 在实践中的绝大多数使用场景。更全面、更深入的请进入上面的教程。如果您觉得有遗漏的常见知识点或者错误的地方,请评论指出!

新的变量声明方式 let 和 const

是什么:

新的变量声明方式,提供变量的块级作用域,同时通过一些限制来更防止我们犯错误。也就是说是更好的声明变量的方式

怎么用

1)let/const 与 var 的区别是提供了块级作用域以及变量创建时不会立即初始化

2)在同一个作用域内 let/const 禁止重复声明相同的变量

var a = 1;
let a = 2; // SyntaxError

3)let 声明的变量可重新赋值,const 声明的变量不能重新赋值,即常量。

4)暂时性死区:在当前作用域,使用的变量已经存在,但是在代码执行到变量声明前禁止访问。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

常见使用场景

1)因为能创建块级作用域,所以常见于 if 和 for 中

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

2)const 在实践中常用来声明一个对象,之后可以再对这个对象的属性进行修改

const foo = {
  name: 'bar',
};

foo.name = 'baz';

console.log(foo);

解构

是什么:

按照阮一峰大神的说法:ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。也就是说通过模式匹配来进行变量赋值。

怎么用:

1)数组基本用法

let [a, b, c] = [1, 2, 3];

a; //1
b; //2
c; //3

2)对象基本用法

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo; // "aaa"
bar; // "bbb"

3)函数参数的解构赋值

如在 vuex 中 action 不使用解构如下:

actions: {
		increment (context) {
			context.commit('increment')
		}
	}

使用解构

actions: {
		increment ({ commit }) {
			commit('increment')
		}
}

4)支持不完全解构

let [foo, bar] = [1, 2, 3];

5)如果解构不成功,变量的值就等于 undefined,同时解构赋值允许指定默认值,默认值生效的条件是对象的属性值严格等于 undefined。

常见使用场景

1)交换变量的值

[x, y] = [y, x];

2)提取 JSON 数据

let jsonData = {
  id: 42,
  status: 'OK',
  data: [867, 5309],
};

let { id, status, data: number } = jsonData;

console.log(id, status, number); // 42, "OK", [867, 5309]

3)函数参数的默认值

jQuery.ajax = function (
  url,
  {
    async = true,
    beforeSend = function () {},
    cache = true,
    complete = function () {},
    crossDomain = false,
    global = true,
    // ... more config
  }
) {
  // ... do stuff
};

4)指定加载模块的什么功能

import { mapActions } from 'vuex';

箭头函数

干嘛的:

箭头函数可以用来替换函数表达式,不用写 function,更加简化。也就是说是函数表达式的简化方式

怎么用:

1)注意箭头函数中的 this 指向外层的 this

2)无法用 call/apply/bind 来改变 this 指向。

3)在 ES6 中,会默认采用严格模式,因此默认情况下 this 不是指向 window 对象,而是 undefined。

<script type="text/javascript">
  setTimeout(() => console.log(this), 1000) // undefined,不是window
</script>

4)不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

使用场景:

关键是你需要 this 指向什么

默认参数和 rest 参数

是什么:

默认参数就是设置参数默认值,rest 参数(翻译为不具名参数,也叫做剩余参数)是将传入的未具名的参数作为一个数组集合

怎么用:

如下,默认参数给参数赋一个默认值,rest 参数使用三个点(…)加数组集合名

function foo(arg1 = 1, ...restArg) {
  console.log(arg1, restArg);
}

foo(undefined, 2, 3, 4); // 1, [2, 3, 4]

foo(2, 3, 4); // 2, [3, 4]

扩展运算符

是什么:

同 rest 参数一样,也是三个点。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。因此常用于函数调用。

常见使用场景:

1)合并数组

let newArr = [...arr1, ...arr2, ...arr3];

2)与解构赋值结合

// ES5
a = list[0], rest = list.slice(1)

// ES6
[a, ...rest] = list

3)将字符串转为数组

[...'test']; // [ "t", "e", "s", "t"]

对象扩展

语法变化

1)属性简写,当对象的一个属性名称与本地变量名相同的时候,可以省略冒号和值

var foo = 'bar';
var baz = { foo };
baz; // {foo: "bar"}

2)属性名表达式,可以在以对象字面量方式定义对象是使用表达式作为属性名

// ES5只能这样
obj['a' + 'bc'] = 123;

// ES6还能这样
let obj = {
  ['a' + 'bc']: 123,
};

3)方法简写,省去:和 function

const foo = {
  bar() {
    console.log('1');
  },
};

Object.is()

更好的判断方法,与===的不同有两点:一是+0 不等于-0,二是 NaN 等于自身。

NaN === NaN; // false
Object.is(NaN, NaN); // true

Object.assign()

1)Object.assign 方法用于对象的合并,用法与 jQuery 和 underscore 的 extend 方法类似,而且同样会改变 target。

Object.assign(target, source1, source2);

2)只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。

3)Object.assign 方法实行的是浅拷贝,而不是深拷贝。

Object.setPrototypeOf

用来设置一个对象的 prototype 对象,返回参数对象本身

Object.setPrototypeOf(object, prototype);

Object.getPrototypeOf()

Object.getPrototypeOf 方法可以用来从子类上获取父类。因此,可以使用这个方法判断,一个类是否继承了另一个类。参见下面类与继承章节

遍历

对象的每个属性都有一个描述对象(Descriptor),Object.getOwnPropertyDescriptor 方法可以获取该属性的描述对象。这个描述对象有 value、writable、enumerable、configurable 四大属性。

ES5 下面三个操作会忽略 enumerable 为 false 的属性。

  • for…in 循环:只遍历对象自身的和继承的可枚举的属性
  • Object.keys():返回对象自身的所有可枚举的属性的键名
  • JSON.stringify():只串行化对象自身的可枚举的属性

ES6 新增的操作 Object.assign(),也会忽略 enumerable 为 false 的属性,只拷贝对象自身的可枚举的属性。

ES6 一共有 5 种方法可以遍历对象的属性。

(1)for…in

for…in 循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

(2)Object.keys(obj)

Object.keys 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)。

(3)Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)。

(4)Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols 返回一个数组,包含对象自身的所有 Symbol 属性。

(5)Reflect.ownKeys(obj)

Reflect.ownKeys 返回一个数组,包含对象自身的所有属性,不管属性名是 Symbol 或字符串,也不管是否可枚举。

以上的 5 种方法遍历对象的属性,都遵守同样的属性遍历的次序规则。

  • 首先遍历所有属性名为数值的属性,按照数字排序。
  • 其次遍历所有属性名为字符串的属性,按照生成时间排序。
  • 最后遍历所有属性名为 Symbol 值的属性,按照生成时间排序。

大多数时候,我们只关心对象自身的可枚举属性。所以,尽量不要用 for…in 循环,而用 Object.keys()代替。

字符串扩展

以前判断一个字符串是否包含某个字符串只能通过 indexOf 的值是否大于-1 来判断,现在新增了三种方法:

includes():表示是否包含该字符串。

startsWith():表示该字符串是否在头部。

endsWith():表示该字符串是否在尾部。

'hello world'.includes('hello'); // true

模板字符串

干嘛的:

和 handlebars 那些模板引擎功能类似,有模板字符串可以不用拼接字符串了

怎么用:

用反引号``将整个字符串包裹起来,${}表示一个变量或者一个表达式,可以嵌套

const tmpl = (addrs) => `
      <table>
      ${addrs
        .map(
          (addr) => `
        <tr><td>${addr.first}</td></tr>
        <tr><td>${addr.last}</td></tr>
      `
        )
        .join('')}
      </table>
    `;

const data = [
  { first: 'Jane', last: 'Bond' },
  { first: 'Lars', last: 'Croft' },
];

console.log(tmpl(data));

标签模板

函数名后面紧接一个模板字符串。该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。当字符串模板有变量时,函数的第一个参数为被变量分开的字符串组成的数组,后面的参数依次为变量,这些变量的参数序列可以使用 rest 参数。

var a = 5;
var b = 10;

tag`Hello ${a + b} world ${a * b}`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);

数组扩展

Array.of()

Array.of 方法用于将一组值,转换为数组。可以替代 Array,且其行为非常统一,不像 Array 只有一个正整数参数 n 时,会生成 n 个空位构成的数组

Array.of(1); // [1]
Array.of(1, 2, 3); // [1, 2, 3]
Array(1); // [undefined * 1],其实不是undefined,是空位,如下可证明两者并不一样
0 in [undefined, undefined, undefined]; // true
0 in [, , ,]; // false
Array(1, 2, 3); // [1, 2, 3]

Array.from()

Array.from 方法用于将两类对象转为真正的数组:类数组对象(array-like object)和可遍历(iterable)对象(包括 ES6 新增的数据结构 Set 和 Map)。Array.from 还可以接受第二个参数,作用类似于数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

Array.from({ 0: 'a', length: 1 }); // ['a']

Array.from('hello'); // ['h', 'e', 'l', 'l', 'o'],因为字符串有Iterator接口,可遍历

Array.from([1, 2, 3], (x) => x * x); // [1, 4, 9]

常见使用场景:

1)转换 NodeList 集合。常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的 arguments 对象。但是后者使用 rest 参数更简便

// NodeList对象
let elementDivList = document.querySelectorAll('div');
Array.from(elementDivList).forEach(function (div) {
  console.log(div);
});

2)数组去重。与下面要讲到的 Set 配合即可很简单地实现数值去重。

var arr = [1, 3, 5, 5, 8, 3, 2];
var uniqueArr = Array.from(new Set(arr));
console.log(uniqueArr); // [1, 3, 5, 8, 2]

数组实例的 copyWithin 方法

Array.prototype.copyWithin(target, (start = 0), (end = this.length));

它接受三个参数。

  • target(必需):从该位置开始替换数据。
  • start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
  • end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
[1, 2, 3, 4, 5].copyWithin(0, 2); // [3, 4, 5, 4, 5]
/* 从索引2开始读取数据,到数组尾部停止,即读到(3, 4, 5),然后从索引0开始替换数据 */

数组实例的 find 和 findIndex 方法

找到第一个符合条件的 item(项)或 index(索引),前者相当于 underscore 中的 first 方法,后者则和 underscore 中的同名方法一致。另外,这两个方法都可以借助 Object.is 发现 NaN,弥补了数组的 IndexOf 方法的不足。

[1, 2, 3, 4]
  .find((x) => x > 2) // 3
  [(1, 2, 3, 4)].findIndex((x) => x > 2) // 2
  [NaN].findIndex((x) => Object.is(NaN, x)); // 0

数组实例的 fill 方法

Array.prototype
  .fill(fillItem, (start = 0), (end = this.length))

  [(1, 3, 6, 11, 4)].fill(10, 2); // [1, 3, 10, 10, 10]

数组实例的 includes 方法

与字符串的 includes 方法类似。该方法属于 ES7,但 Babel 转码器已经支持。

[1, 2, 3]
  .includes(3, 3) // false
  [(1, 2, 3)].includes(3, -1) // true
  [NaN].includes(NaN); // true

Set 和 Map

ES6 中增加了两个新的数据结构:Set 和 Map。Set 是不包含重复值的列表,而 Map 则是键与相对应的值的集合。

Set

是什么:

Set 是不包含重复值的有序列表。

怎么用:

1)Set 构造函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。

const set = new Set([1, 2, 3, 4, 4]);
console.log(set);

2)四个操作方法(add()、delete()、has()、clear())和一个属性(size),使用方法根据名字和下面例子就知道了

const s = new Set();
s.add(1).add(2).add(2); // 注意2被add了两次

s.size; // 2

s.has(1); // true
s.has(2); // true
s.has(3); // false

s.delete(2);
s.has(2); // false

s.add(3);
s.size; // 2

s.clear();
s.size; // 0

3)三个遍历器生成函数(keys()、values()、entries())和一个遍历方法(forEach())

keys 方法、values 方法、entries 方法返回的都是遍历器对象(详见 Iterator)。都这可以使用遍历器对象的方法 for…of 进行遍历。由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以 keys 方法和 values 方法的行为完全一致。forEach 方法与 ES5 数组的 forEach 类似。

let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

set.forEach((value, key) => console.log(value, key));

4)Set 转化为数组,有两种方法:…扩展运算符和 Array.from()

这两者可互换,因此前面提到的使用 Array.from()来数组去重也可以这样做:[…new Set(arr)]

// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map((val) => val * 2));
// set的值是2, 4, 6

// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, (val) => val * 2));
// set的值是2, 4, 6

Map

是什么:

一种由键值对集合构成的数据结构,类似于对象,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

const m = new Map();
const o = { p: 'Hello World' };

m.set(o, 'content');
m.get(o); // "content"

怎么用:

1)Set 构造函数可以接收任何一个具有 Iterator 接口的数据结构作为参数

const set = new Set([
  ['foo', 1],
  ['bar', 2],
]);
const m1 = new Map(set);
m1.get('foo'); // 1

2)5 个操作方法(set(key, value)、get(key)、has(key)、delete(key)、clear())和一个属性(size)

3)遍历生成函数和遍历方法和 Set 类似,Map 结构的默认遍历器接口(Symbol.iterator 属性),就是 entries 方法。

map[Symbol.iterator] === map.entries; // true

4)Map 转为数组,使用…扩展运算符

WeakSet 和 WeakMap

WeakSet、WeakMap 分别和 Set、Map 类似,不过存储的是对象的弱引用方式,这样在内存管理上更加容易优化。


文章作者: Angus
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Angus !
  目录