JavaScript新特性(ES6,7,8...)

目录

一、JavaScript新增基础

1.let 关键字

2.const 关键字

3.变量的解构赋值

(1)、数组的解构赋值

(2)、对象的解构赋值

(3)、字符串的解构赋值

(4)、数值和布尔值的解构赋值

(5)、函数参数的解构赋值

(6)、圆括号问题 

(7)、用途

4.字符串新增

(1)、字符串的遍历器接口

(2)、模板字符串 

5.字符串新增方法

(1)、String.fromCodePoint() 

(2)、String.raw()

(3)、codePointAt()

(4)、normalize() 

(5)、includes(), startsWith(), endsWith() 

(6)、repeat() 

(7)、padStart(),padEnd() 

(8)、trimStart(),trimEnd()

(9)、matchAll()

(10)、replaceAll() 

6.正则的扩展

7.数值的扩展

8.函数的扩展(rest参数,箭头函数)

9.数组的扩展

10.对象的扩展

11.对象的新增方法

12.运算符的扩展

13.Symbol

(1)、概述

(2)、Symbol的使用

(3)、Symbol.prototype.description

(4)、作为属性名的 Symbol 

(5)、属性名的遍历

(6)、Symbol.for(),Symbol.keyFor()

(7)、Symbol内置值

二、JavaScript新增结构,方法

1.Set

(1)、Set的基础使用

(2)、遍历操作

(3)、WeakSet 

2. Map

(1)、Map的基础使用

(2)、遍历方法

(3)、WeakMap

3.  Class

(1)、Class的概述

(2)、constructor 方法

(3)、静态方法

(4)、Class 的继承

(5)、super 关键字

(6)、类的 prototype 属性和__proto__属性

5. Reflect 与 Proxy

(1)、概述

(2)、Proxy

(3)、Proxy 实例的方法

(4)、Reflect

(5)、Reflect静态方法

6.Promise

(1)、Promise的特点

(2)、基本用法 

(3)、Promise的其他方法

7.Generator 函数(生成器)

(1)、Generator 函数组成

(2)、yield 表达式

(3)、next 方法

(4)、throw方法

(5)、Generator.return()

(6)、yield* 表达式 

8.Iterator(迭代器)

9.async 函数

(1)、async 函数基础

(2)、await

10.Module(模块化)

(1)、概述

(2)、严格模式

(3)、export 命令

(4)、import 命令

(5)、export default命令

(6)、export 与 import 的复合写法

(7)、import() 


一、JavaScript新增基础

1.let 关键字

let 关键字用来声明变量,使用 let 声明的变量有几个特点:

  • 不允许重复声明
  • 块儿级作用域
  • 不存在变量提升
  • 不影响作用域链

注:

  • ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域

2.const 关键字

const 关键字用来声明常量,const 声明有以下特点:

  • 不允许重复声明
  • 块儿级作用域
  • 声明必须赋初始值
  • 值不允许修改
  • 标识符一般为大写

3.变量的解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构

(1)、数组的解构赋值

1.基本语法

“模式匹配”:只要等号两边的模式相同,左边的变量就会被赋予对应的值

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

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

如果解构不成功,变量的值就等于undefined

//解构不成功,foo的值都会等于undefined
let [foo] = [];
let [bar, foo] = [1];

另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

如果等号的右边不是数组,那么将会报错。

2.默认值

解构赋值允许指定默认值

let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

let [x = 1] = [undefined];
x // 1

let [x = 1] = [null];
x // null

注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。

 如果默认值是一个表达式,那么这个表达式是惰性求值的

function f() {
  console.log('aaa');
}

let [x = f()] = [1];
//只有在用到的时候,才会求值
//如果x能取到值,所以函数f根本不会执行

默认值可以引用解构赋值的其他变量,但该变量必须已经声明

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: y is not defined

(2)、对象的解构赋值

1.基本语法

对象的属性没有次序,变量必须与属性同名,才能取到正确的值

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

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

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

赋值到某个变量:

let { log, sin, cos } = Math;

如果变量名与属性名不一致,必须写成下面这样:

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

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

嵌套结构:

const node = {
  loc: {
    start: {
      line: 1,
      column: 5
    }
  }
};

let { loc, loc: { start }, loc: { start: { line }} } = node;
line // 1
loc  // Object {start: Object}
start // Object {line: 1, column: 5}

let obj = {};
let arr = [];

({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });

obj // {prop:123}
arr // [true]

2.默认值

默认值生效的条件是,对象的属性值严格等于undefined

var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

var {x: y = 3} = {};
y // 3

var {x: y = 3} = {x: 5};
y // 5

var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"

var {x = 3} = {x: undefined};
x // 3

var {x = 3} = {x: null};
x // null

3.注意点

  • 不能将大括号放在首行
// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error

// 正确的写法
let x;
({x} = {x: 1});
  • 解构赋值允许等号左边的模式之中,不放置任何变量名
({} = [true, false]);
({} = 'abc');
({} = []);
  • 由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3

(3)、字符串的解构赋值

字符串被转换成了一个类似数组的对象

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值

let {length : len} = 'hello';
len // 5

(4)、数值和布尔值的解构赋值

等号右边是数值和布尔值,则会先转为对象

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

undefinednull无法转为对象,所以对它们进行解构赋值,都会报错

let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

(5)、函数参数的解构赋值

function add([x, y]){
  return x + y;
}

add([1, 2]); // 3



function move({x = 0, y = 0} = {}) { //给参数指定默认值
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
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: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

(6)、圆括号问题 

  • ES6 的规则是,只要有可能导致解构的歧义,就不得使用圆括号
  • 建议只要有可能,就不要在模式中放置圆括号。

不得使用圆括号:

  • 变量声明语句
// 全部报错
let [(a)] = [1];
let {x: (c)} = {};
let ({x: c}) = {};
let {(x: c)} = {};
let {(x): c} = {};
let { o: ({ p: p }) } = { o: { p: 2 } };
  • 函数参数
// 报错
function f([(z)]) { return z; }
// 报错
function f([z,(x)]) { return x; }
  • 赋值语句的模式
// 全部报错
({ p: a }) = { p: 42 };
([a]) = [5];

可以使用圆括号: 

赋值语句的非模式部分,可以使用圆括号

[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确

(7)、用途

  • 交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
//简单明了
  • 从函数返回多个值
// 返回一个数组

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

// 返回一个对象

function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();
  • 函数参数的定义
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
  • 提取 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]
  • 函数参数的默认值
jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
} = {}) {
  // ... do stuff
};
  • 遍历 Map 结构
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world
  • 输入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");

4.字符串新增

字符串新增的方法:

  • 字符的 Unicode 表示法
  • 字符串的遍历器接口
  • 直接输入 U+2028 和 U+2029
  • JSON.stringify() 的改造 
  • 模板字符串 
  • 模板字符串的限制

我们简单讲解一下

(1)、字符串的遍历器接口

for...of循环遍历字符串的遍历器接口模板字符串

for (let codePoint of 'foo') {
  console.log(codePoint)
}
// "f"
// "o"
// "o"

(2)、模板字符串 

模板字符串(template string)是增强版的字符串,用反引号(`)标识,特点:

  • 字符串中可以出现换行符,空格和缩进
  • 可以使用 ${xxx} 形式输出变量
  • 如果你不想要换行,可以使用trim方法消除它

注意:当遇到字符串与变量拼接的情况使用模板字符串

 字符串中可以出现换行符,空格和缩进:

//定义字符串
let str = `<ul>
               <li>沈腾</li>
               <li>玛丽</li>
               <li>魏翔</li>
               <li>艾伦</li>
           </ul>`;
console.log(str);

变量拼接:

//变量拼接
let name = '小可爱';
let result = `欢迎${name}访问我的文章`;
console.log(result);
//欢迎小可爱访问我的文章

注:

  • 大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性 
  • 模板字符串之中还能调用函数
  • 如果大括号中的值不是字符串,将按照一般的规则转为字符串。(toString方法)
  • 如果需要引用模板字符串本身,在需要时执行,可以写成函数 

trim方法:

//定义字符串
let str = `<ul>
               <li>沈腾</li>
               <li>玛丽</li>
               <li>魏翔</li>
               <li>艾伦</li>
           </ul>`.trim();
console.log(str);

5.字符串新增方法

(1)、String.fromCodePoint() 

用于从 Unicode 码点返回对应字符

String.fromCodePoint(0x20BB7)
// "𠮷"
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
// true

(2)、String.raw()

返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法,

它将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用

String.raw`Hi\n${2+3}!`
// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!"

String.raw`Hi\u000A!`;
// 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!"

//如果原字符串的斜杠已经转义,那么String.raw()会进行再次转义
String.raw`Hi\\n`
// 返回 "Hi\\\\n"

String.raw()本质上是一个正常的函数,只是专用于模板字符串的标签函数。如果写成正常函数的形式,它的第一个参数,应该是一个具有raw属性的对象,且raw属性的值应该是一个数组,对应模板字符串解析后的值。

如果String.raw()方法的第一个参数是一个对象,它的raw属性等同于原始的模板字符串解析后得到的数组

// `foo${1 + 2}bar`
// 等同于
String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"

(3)、codePointAt()

JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符(Unicode 码点大于0xFFFF的字符),JavaScript 会认为它们是两个字符

let s = '𠮷a';

s.codePointAt(0) // 134071
s.codePointAt(1) // 57271

s.codePointAt(2) // 97
//字符在字符串中的位置(从 0 开始)。上面代码中,JavaScript 将“𠮷a”视为三个字符
//所以建议使用遍历的方法避免出错

注:

  • codePointAt()方法是测试一个字符由两个字节还是由四个字节组成的最简单方法
  • codePointAt()方法返回的是码点的十进制值,如果想要十六进制的值,可以使用toString()方法转换一下

(4)、normalize() 

许多欧洲语言有语调符号和重音符号。为了表示它们,Unicode 提供了两种方法。一种是直接提供带重音符号的字符,比如Ǒ(\u01D1)。另一种是提供合成符号(combining character),即原字符与重音符号的合成,两个字符合成一个字符,比如O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)

'\u01D1'==='\u004F\u030C' //false

'\u01D1'.length // 1
'\u004F\u030C'.length // 2

用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化

'\u01D1'.normalize() === '\u004F\u030C'.normalize()
// true

normalize方法可以接受一个参数来指定normalize的方式

参数的四个可选值如下:

  • NFC,默认参数,表示“标准等价合成”,返回多个简单字符的合成字符。所谓“标准等价”指的是视觉和语义上的等价。
  • NFD,表示“标准等价分解”,即在标准等价的前提下,返回合成字符分解的多个简单字符。
  • NFKC,表示“兼容等价合成”,返回合成字符。所谓“兼容等价”指的是语义上存在等价,但视觉上不等价,比如“囍”和“喜喜”。(这只是用来举例,normalize方法不能识别中文。)
  • NFKD,表示“兼容等价分解”,即在兼容等价的前提下,返回合成字符分解的多个简单字符。
'\u004F\u030C'.normalize('NFC').length // 1
'\u004F\u030C'.normalize('NFD').length // 2
//NFC参数返回字符的合成形式,NFD参数返回字符的分解形式

(5)、includes(), startsWith(), endsWith() 

传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
let s = 'Hello world!';

s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true

三个方法都支持第二个参数,表示开始搜索的位置

let s = 'Hello world!';

s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

注:

  • 使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束

(6)、repeat() 

返回一个新字符串,表示将原字符串重复n

  • 参数如果是小数,会被取整
  • 参数是负数或者Infinity,会报错
  • 如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0repeat视同为 0
  • 参数NaN等同于 0
  • 如果repeat的参数是字符串,则会先转换成数字
//重复n次
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"

//小数,会被取整
'na'.repeat(2.9) // "nana"

//是负数或者Infinity,会报错
'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError


//如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0。
'na'.repeat(-0.9) // ""

//NaN等同于 0
'na'.repeat(NaN) // ""

//字符串,则会先转换成数字
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"

(7)、padStart(),padEnd() 

如果某个字符串不够指定长度,会在头部或尾部补全

padStart()用于头部补全,padEnd()用于尾部补全

padStart()padEnd()一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串:

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
  • 如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串
  • 如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串,从后方截补的字符串
  • 如果省略第二个参数,默认使用空格补全长度

(8)、trimStart(),trimEnd()

ES2019 对字符串实例新增了trimStart()trimEnd()这两个方法。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。

const s = '  abc  ';

s.trim() // "abc"
s.trimStart() // "abc  "
s.trimEnd() // "  abc"
  • 浏览器还部署了额外的两个方法,trimLeft()trimStart()的别名,trimRight()trimEnd()的别名
  • 这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效  

(9)、matchAll()

matchAll()方法返回一个正则表达式在当前字符串的所有匹配

(10)、replaceAll() 

replaceAll()方法,可以一次性替换所有匹配

用法:

String.prototype.replaceAll(searchValue, replacement)
//searchValue是搜索模式,可以是一个字符串,也可以是一个全局的正则表达式(带有g修饰符)

注:

如果searchValue是一个不带有g修饰符的正则表达式,replaceAll()会报错。这一点跟replace()不同

replaceAll()的第二个参数replacement是一个字符串,表示替换的文本,其中可以使用一些特殊字符串:

  • $&:匹配的字符串。
  • $`:匹配结果前面的文本。
  • $':匹配结果后面的文本。
  • $n:匹配成功的第n组内容,n是从1开始的自然数。这个参数生效的前提是,第一个参数必须是正则表达式。
  • $$:指代美元符号$

6.正则的扩展

正则新增的有:

  • RegExp 构造函数

如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。

  • u 修饰符

ES6 对正则表达式添加了u修饰符,含义为“Unicode 模式”

  • RegExp.prototype.unicode 属性 

正则实例对象新增unicode属性,表示是否设置了u修饰符

  • y 修饰符

除了u修饰符,ES6 还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。

也是全局匹配

  • RegExp.prototype.sticky 属性

是否设置了y修饰符。

  • RegExp.prototype.flags 属性 

返回正则表达式的修饰符

  • s 修饰符:dotAll 模式
  • 后行断言
  • Unicode 属性类
  • 具名组匹配
  • 字符串的正则方法 
  • 正则匹配索引

7.数值的扩展

数值扩增的有:

  • 二进制和八进制表示法

ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示

  • 数值分隔符
  • Number.isFinite(), Number.isNaN() 

Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity

Number.isNaN()用来检查一个值是否为NaN 

  • Number.parseInt(), Number.parseFloat()

ES6 将全局方法parseInt()parseFloat(),移植到Number对象上面,行为完全保持不变

  • Number.isInteger() 

Number.isInteger()用来判断一个数值是否为整数。

  • Number.EPSILON

ES6 在Number对象上面,新增一个极小的常量Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。

  • 安全整数和 Number.isSafeInteger()

ES6 引入了Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。

Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。

  • Math 对象的扩展
  • Math.trunc方法用于去除一个数的小数部分,返回整数部分
  • Math.sign方法用来判断一个数到底是正数(+1)、负数(-1)、零(0)、负零(-0)、其他值,(NaN)。对于非数值,会先将其转换为数值。
  • Math.cbrt()方法用于计算一个数的立方根
  • Math.clz32()方法将参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0
  • Math.imul方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
  • Math.fround方法返回一个数的32位单精度浮点数形式
  • Math.hypot方法返回所有参数的平方和的平方根
  • Math.expm1(x)返回 ex - 1,即Math.exp(x) - 1
  • Math.log1p(x)方法返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN
  • Math.log10(x)返回以 10 为底的x的对数。如果x小于 0,则返回 NaN
  • Math.log2(x)返回以 2 为底的x的对数。如果x小于 0,则返回 NaN。
  • Math.sinh(x) 返回x的双曲正弦(hyperbolic sine)
  • Math.cosh(x) 返回x的双曲余弦(hyperbolic cosine)
  • Math.tanh(x) 返回x的双曲正切(hyperbolic tangent)
  • Math.asinh(x) 返回x的反双曲正弦(inverse hyperbolic sine)
  • Math.acosh(x) 返回x的反双曲余弦(inverse hyperbolic cosine)
  • Math.atanh(x) 返回x的反双曲正切(inverse hyperbolic tangent)
  • BigInt 数据类型 

8.函数的扩展(rest参数,箭头函数)

  • 函数参数的默认值
  • rest 参数

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

// arguments变量的写法
function sortNumbers() {
  return Array.from(arguments).sort();
}

// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();

//rest 参数必须是最后一个形参
// 报错
function f(a, ...b, c) {
  // ...
}
  • 严格模式

规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错

  • name 属性 

函数的name属性,返回该函数的函数名

  • 箭头函数

ES6 允许使用“箭头”(=>)定义函数,通用写法为:

var f = v => v;

// 等同于
var f = function (v) {
  return v;
};


let fn = (arg1, arg2, arg3) => {
    return arg1 + arg2 + arg3;
}

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分

var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回

var sum = (num1, num2) => { return num1 + num2; }

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错

// 报错
let getTempItem = id => { id: id, name: "Temp" };

// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });

箭头函数有几个使用注意点:

  1. 箭头函数没有自己的this对象,this等同于上一层非箭头函数的this值或全局对象(window或undefined)(严格模式this是undefined)
  2. 不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。
  3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  4. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
  • 函数参数的尾逗号
  • Function.prototype.toString() 

修改后的toString()方法,明确要求返回一模一样的原始代码

  • catch 命令的参数省略

9.数组的扩展

数值新增的有:

  • 扩展运算符

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

  • Array.from()

Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)

  • Array.of() 

Array.of()方法用于将一组值,转换为数组。

  • 数组实例的 copyWithin()

数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。

Array.prototype.copyWithin(target, start = 0, end = this.length)
  • target(必需):从该位置开始替换数据。如果为负值,表示倒数。
  • start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
  • end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
  • 数组实例的 find() 和 findIndex()
  • 数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined
  • 数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
  • 数组实例的 fill() 

fill方法使用给定值,填充一个数组。

fill( 给定值, 起始位置(可选),结束位置(可选,结束为位置之前))
  • 数组实例的 entries(),keys() 和 values() 

ES6 提供三个新的方法——entries()keys()values()——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

  • 数组实例的 includes()

Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似

  • 数组实例的 flat(),flatMap()
  • 数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响,默认只会“拉平”一层
[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
  • Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。只能展开一层数组
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]
  • 数组的空位

数组的空位指,数组的某一个位置没有任何值。比如,Array构造函数返回的数组都是空位。

ES6 则是明确将空位转为undefined

  • Array.prototype.sort() 的排序稳定性

排序稳定性(stable sorting)是排序算法的重要属性,指的是排序关键字相同的项目,排序前后的顺序不变

const arr = [
  'peach',
  'straw',
  'apple',
  'spork'
];

const stableSorting = (s1, s2) => {
  if (s1[0] < s2[0]) return -1;
  return 1;
};

arr.sort(stableSorting)
//straw在spork的前面 ["apple", "peach", "straw", "spork"]
//稳定

const unstableSorting = (s1, s2) => {
  if (s1[0] <= s2[0]) return -1;
  return 1;
};

arr.sort(unstableSorting)
//不稳定

注:

  • 插入排序、合并排序、冒泡排序等都是稳定的,堆排序、快速排序等是不稳定的。

10.对象的扩展

对象新增的有:

  • 属性的简洁表示法

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

  • 属性名表达式 

ES6 允许字面量定义对象时,用(表达式)作为对象的属性名,即把表达式放在方括号内。

let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};

  • 方法的 name 属性

函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。

name属性返回函数名

  • 属性的可枚举性和遍历

可枚举性:

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。

let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
//    value: 123,
//    writable: true,
//    enumerable: true,
//    configurable: true
//  }

描述对象的enumerable属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。

有四个操作会忽略enumerablefalse的属性。

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

属性的遍历:

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

  • for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
  • Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
  • Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名
  • Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
  • Reflect.ownKeys返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举

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

  • 首先遍历所有数值键,按照数值升序排列。
  • 其次遍历所有字符串键,按照加入时间升序排列。
  • 最后遍历所有 Symbol 键,按照加入时间升序排列。
  • super 关键字

this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象

  • 对象的扩展运算符
  • AggregateError 错误对象
AggregateError(errors[, message])
  • errors:数组,它的每个成员都是一个错误对象。该参数是必须的。
  • message:字符串,表示 AggregateError 抛出时的提示信息。该参数是可选的。

11.对象的新增方法

对象新增的有:

  • Object.is()

Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致

不同之处只有两个:

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
  • Object.assign()

Object.assign()方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)

Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象。

注意:

如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

  • Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptors()方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。

  • __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
  • __proto__属性(前后各两个下划线),用来读取或设置当前对象的原型对象
  • Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的原型对象(prototype),返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。
  • Object.getPrototypeOf()与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象
  • Object.create()(生成操作)
  • Object.keys(),Object.values(),Object.entries()
  • Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
  • Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

  • Object.fromEntries() 

bject.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。

12.运算符的扩展

  • 指数运算符

指数运算符(**

这个运算符是右结合

// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512
  • 链判断运算符

链判断运算符?.有三种写法。

  • obj?.prop // 对象属性是否存在
  • obj?.[expr] // 对象属性是否存在
  • func?.(...args) // 函数或对象方法是否存在
  • Null 判断运算符

Null 判断运算符??。它的行为类似||,但是只有运算符左侧的值为nullundefined时,才会返回右侧的值。

  • 逻辑赋值运算符

引入了三个新的逻辑赋值运算符(logical assignment operators),将逻辑运算符与赋值运算符进行结合

// 或赋值运算符
x ||= y
// 等同于
x || (x = y)

// 与赋值运算符
x &&= y
// 等同于
x && (x = y)

// Null 赋值运算符
x ??= y
// 等同于
x ?? (x = y)

13.Symbol

(1)、概述

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值,它是 JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型,Symbol 特点如下:

  • Symbol 的值是唯一的,用来解决命名冲突的问题
  • Symbol 值不能与其它数据进行运算
  • Symbol 定义的对象属性不能使用 for…in 循环遍 历 ,但是可以使用 Reflect.ownKeys 来获取对象的所有键名

Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突

let s = Symbol();

typeof s
// "symbol"

注意:

  • Symbol函数前不能使用new命令 
  • Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();

s1 === s2 // false

// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');

s1 === s2 // false
  • Symbol 值不能与其他类型的值进行运算
  • Symbol 值可以显式转为字符串、布尔值,但是不能转为数值
let sym = Symbol('My symbol');

String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'


let sym = Symbol();
Boolean(sym) // true
!sym  // false

if (sym) {
  // ...
}

Number(sym) // TypeError
sym + 2 // TypeError

(2)、Symbol的使用

//创建 Symbol
let s1 = Symbol();
console.log(s1);
console.log(typeof s1);

//添加标识的 Symbol
let s2 = Symbol("张三");
let s2_2 = Symbol("张三");
console.log(s2);
console.log(s2_2);
console.log(s2 === s2_2);

//使用 Symbol for 定义
let s3 = Symbol.for("张三");
let s3_2 = Symbol.for("张三");
console.log(s3);
console.log(s3_2);
console.log(s3 === s3_2);

//在方法中使用 Symbol
let game = {
    name: "狼人杀",
    [Symbol('say')]: function () {
        console.log("我可以发言")
    },
    [Symbol('zibao')]: function () {
        console.log('我可以自爆');
    }
};
console.log(game);

 注意:遇到唯一性的场景时要想到 Symbol

(3)、Symbol.prototype.description

创建 Symbol 的时候,可以添加一个描述。

const sym = Symbol('foo');

sym.description // "foo"

(4)、作为属性名的 Symbol 

由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。

let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

注意:

  • Symbol 值作为对象属性名时,不能用点运算符
  • 在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中

(5)、属性名的遍历

Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。

但是,它也不是私有属性,有一个Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

const obj = {};
let a = Symbol('a');
let b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';

const objectSymbols = Object.getOwnPropertySymbols(obj);

objectSymbols
// [Symbol(a), Symbol(b)]

扩展: 

Reflect.ownKeys()方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。:

let obj = {
  [Symbol('my_key')]: 1,
  enum: 2,
  nonEnum: 3
};

Reflect.ownKeys(obj)
//  ["enum", "nonEnum", Symbol(my_key)]

(6)、Symbol.for(),Symbol.keyFor()

Symbol.for()方法:

有时,我们希望重新使用同一个 Symbol 值,Symbol.for()方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');

s1 === s2 // true

注:

  • Symbol.for()Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。

Symbol.keyFor()方法:

Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

(7)、Symbol内置值

除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。

可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。

  • Symbol.hasInstance 

当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法

class MyClass {
  [Symbol.hasInstance](foo) {
    return foo instanceof Array;
  }
}

[1, 2, 3] instanceof new MyClass() // true
  • Symbol.isConcatSpreadable

对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开

let arr1 = ['c', 'd'];
['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
arr1[Symbol.isConcatSpreadable] // undefined

let arr2 = ['c', 'd'];
arr2[Symbol.isConcatSpreadable] = false;
['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
  • Symbol.species

对象的Symbol.species属性,指向一个构造函数。创建衍生对象时,会使用该属性。

class MyArray extends Array {
}

const a = new MyArray(1, 2, 3);
const b = a.map(x => x);
const c = a.filter(x => x > 1);

b instanceof MyArray // true
c instanceof MyArray // true
/*
子类MyArray继承了父类Array,a是MyArray的实例,b和c是a的衍生对象。
你可能会认为,b和c都是调用数组方法生成的,所以应该是数组(Array的实例),
但实际上它们也是MyArray的实例。
*/

class MyArray extends Array {
  static get [Symbol.species]() { return Array; }
}

const a = new MyArray();
const b = a.map(x => x);

b instanceof MyArray // false
b instanceof Array // true
//a.map(x => x)生成的衍生对象,就不是MyArray的实例,而直接就是Array的实例。

ymbol.species的作用在于,实例对象在运行过程中,需要再次调用自身的构造函数时,会调用该属性指定的构造函数。  

  • Symbol.match

ymbol.species的作用在于,实例对象在运行过程中,需要再次调用自身的构造函数时,会调用该属性指定的构造函数。

  • Symbol.replace

对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。

  • Symbol.search

对象的Symbol.search属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。

  • Symbol.split

对象的Symbol.split属性,指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。

  • Symbol.iterator

对象的Symbol.iterator属性,指向该对象的默认遍历器方法

  • Symbol.toPrimitive

对象的Symbol.toPrimitive属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。

Symbol.toPrimitive被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式。

  • Number:该场合需要转成数值
  • String:该场合需要转成字符串
  • Default:该场合可以转成数值,也可以转成字符串
  • Symbol. toStringTag

对象的Symbol.toStringTag属性,指向一个方法。在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object][object Array]object后面的那个字符串。

  • Symbol. unscopables

对象的Symbol.unscopables属性,指向一个对象。该对象指定了使用with关键字时,哪些属性会被with环境排除。

二、JavaScript新增结构,方法

1.Set

ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历

(1)、Set的基础使用

集合的属性和方法:

  • size:返回集合的元素个数
  • add():增加一个新元素,返回当前集合
  • delete():删除元素,返回 boolean 值
  • has():检测集合中是否包含某个元素,返回 boolean 值
  • clear():清空集合,返回 undefined
//创建一个空集合
let s = new Set();
//创建一个非空集合
let s1 = new Set([1, 2, 3, 1, 2, 3]);
//集合属性与方法
//返回集合的元素个数
console.log(s1.size);
//添加新元素
console.log(s1.add(4));
//删除元素
console.log(s1.delete(1));
//检测是否存在某个值
console.log(s1.has(2));
//清空集合
console.log(s1.clear());

(2)、遍历操作

Set 结构的实例有四个遍历方法,可以用于遍历成员。

  • Set.prototype.keys():返回键名的遍历器
  • Set.prototype.values():返回键值的遍历器
  • Set.prototype.entries():返回键值对的遍历器
  • Set.prototype.forEach():使用回调函数遍历每个成员

注: 

  • keys方法、values方法、entries方法返回的都是遍历器对象(详见《Iterator 对象》一章)。由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。
  • entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等

(3)、WeakSet 

eakSet 结构与 Set 类似,也是不重复的值的集合。

WeakSet 与 Set 有两个区别:

  • WeakSet 的成员只能是对象,而不能是其他类型的值
  • WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。 

WeakSet 结构有以下三个方法:

  • WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
  • WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
  • WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。

2. Map

ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键” 的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。

(1)、Map的基础使用

Map 的属性和方法:

  • size:返回 Map 的元素个数
  • set():增加一个新元素,返回当前 Map
  • get():返回键名对象的键值
  • has():检测 Map 中是否包含某个元素,返回 boolean 值
  • clear():清空集合,返回 undefined
//创建一个空 map
let m = new Map();
//创建一个非空 map
let m2 = new Map([
    ["name", "张三"],
    ["gender", "女"]
]);
//属性和方法
//获取映射元素的个数
console.log(m2.size);
//添加映射值
console.log(m2.set("age", 6));
//获取映射值
console.log(m2.get("age"));
//检测是否有该映射
console.log(m2.has("age"));
//清除
console.log(m2.clear());

如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefinednull也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键 

let map = new Map();

map.set(-0, 123);
map.get(+0) // 123

map.set(true, 1);
map.set('true', 2);
map.get(true) // 1

map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3

map.set(NaN, 123);
map.get(NaN) // 123

(2)、遍历方法

Map 结构原生提供三个遍历器生成函数和一个遍历方法。

  • Map.prototype.keys():返回键名的遍历器。
  • Map.prototype.values():返回键值的遍历器。
  • Map.prototype.entries():返回所有成员的遍历器。
  • Map.prototype.forEach():遍历 Map 的所有成员。

(3)、WeakMap

WeakMap结构与Map结构类似,也是用于生成键值对的集合

WeakMapMap的区别有两点:

  • WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名
  • WeakMap的键名所指向的对象,不计入垃圾回收机制。

3.  Class

(1)、Class的概述

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是 一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已,它的一些如下:

  • class:声明类
  • constructor:定义构造函数初始化
  • extends:继承父类
  • super:调用父级构造方法
  • static:定义静态方法和属性
//父类
class Phone {
    //构造方法
    constructor(brand, color, price) {
        this.brand = brand;
        this.color = color;
        this.price = price;
    }

    //对象方法
    call() {
        console.log("我可以打电话!!!")
    }
}

//子类
class SmartPhone extends Phone {
    constructor(brand, color, price, screen, pixel) {
        super(brand, color, price);
        this.screen = screen;
        this.pixel = pixel;
    }

    //子类方法
    photo() {
        console.log("我可以拍照!!");
    }

    playGame() {
        console.log("我可以玩游戏!!");
    }

    //方法重写
    call() {
        console.log("我可以进行视频通话!!");
    }

    //静态方法
    static run() {
        console.log("我可以运行程序")
    }

    static connect() {
        console.log("我可以建立连接")
    }
}

//实例化对象
const Nokia = new Phone("诺基亚", "灰色", 230);
const iPhone6s = new SmartPhone("苹果", "白色", 6088, "4.7inch", "500w");
//调用子类方法
iPhone6s.playGame();
//调用重写方法
iPhone6s.call();
//调用静态方法
SmartPhone.run();
/*
我可以玩游戏
我可以进行视频通话
我可以运行程序
*/

(2)、constructor 方法

constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加

class Point {
}

// 等同于
class Point {
  constructor() {}
}

(3)、静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”

class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

 静态方法也是可以从super对象上调用的。

class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
  static classMethod() {
    return super.classMethod() + ', too';
  }
}

Bar.classMethod() // "hello, too"

 静态属性:

静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。

// 老写法
class Foo {
  // ...
}
Foo.prop = 1;
//上面的写法为Foo类定义了一个静态属性prop

// 新写法
class Foo {
  static prop = 1;
}

(4)、Class 的继承

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多 

class Point {
}

class ColorPoint extends Point {
}

(5)、super 关键字

super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同

  • 第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。
class A {}

class B extends A {
  constructor() {
    super();
  }
}
  • 第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();

(6)、类的 prototype 属性和__proto__属性

  • 子类的__proto__属性,表示构造函数的继承,总是指向父类
  • 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
  • 子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。

5. Reflect 与 Proxy

(1)、概述

Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。

Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。

Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。

(2)、Proxy

一个 Proxy 对象由两个部分组成: target 、 handler 。

var proxy = new Proxy(target, handler);

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。 

拦截读取属性行为:

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

如果handler没有设置任何拦截,那就等同于直接通向原对象。

 Proxy 支持的拦截操作一览一共 13 种:

  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.fooproxy['foo']
  • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
  • has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
  • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

(3)、Proxy 实例的方法

get()方法:

get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。

get(target, propKey, receiver)

拦截读取操作:

var person = {
  name: "张三"
};

var proxy = new Proxy(person, {
  get: function(target, propKey) {
    if (propKey in target) {
      return target[propKey];
    } else {
      throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
    }
  }
});

proxy.name // "张三"
proxy.age // 抛出一个错误
/*
如果访问目标对象不存在的属性,会抛出一个错误。
如果没有这个拦截函数,访问不存在的属性,只会返回undefined。
*/

get方法可以继承。  

set()方法:

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

set(target, propKey, value, receiver)

第四个参数 receiver 表示原始操作行为所在对象,一般是 Proxy 实例本身。

const handler = {
    set: function(obj, prop, value, receiver) {
        obj[prop] = receiver;
    }
};
const proxy = new Proxy({}, handler);
proxy.name= 'Tom';
proxy.name=== proxy // true
 
const exam = {}
Object.setPrototypeOf(exam, proxy)
exam.name = "Tom"
exam.name === exam // true

apply() 方法:

apply方法拦截函数的调用、callapply操作。

apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。

var handler = {
  apply (target, ctx, args) {
    return Reflect.apply(...arguments);
  }
};

has()方法:

has()方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。

has()方法可以接受两个参数,分别是目标对象、需查询的属性名。

has(target, propKey)

construct()方法:

construct()方法用于拦截new命令,下面是拦截对象的写法。

const handler = {
  construct (target, args, newTarget) {
    return new target(...args);
  }
};

construct()方法可以接受三个参数:

  • target:目标对象。

  • args:构造函数的参数数组。

  • newTarget:创造实例对象时,new命令作用的构造函数

deleteProperty()方法:

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。

deleteProperty(target, propKey)

defineProperty()方法:

defineProperty()方法拦截了Object.defineProperty()操作。

defineProperty(target, propKey, propDesc)

getOwnPropertyDescriptor() 方法:

getOwnPropertyDescriptor()方法拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined

getPrototypeOf()方法:

getPrototypeOf()方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。

  • Object.prototype.__proto__

  • Object.prototype.isPrototypeOf()

  • Object.getPrototypeOf()

  • Reflect.getPrototypeOf()

  • instanceof

isExtensible()方法:

isExtensible()方法拦截Object.isExtensible()操作。

ownKeys()方法:

ownKeys()方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • for...in循环

preventExtensions()方法:

preventExtensions()方法拦截Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。

setPrototypeOf()方法:

setPrototypeOf()方法主要用来拦截Object.setPrototypeOf()方法。

Proxy.revocable()方法:

Proxy.revocable()方法返回一个可取消的 Proxy 实例。

(4)、Reflect

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。

Reflect对象的设计目的有这样几个:

  1. Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
  2.  修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false
  3.  让Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。
  4. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

(5)、Reflect静态方法

Reflect对象一共有 13 个静态方法:

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)

Reflect.get(target, name, receiver)方法:

Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefined

Reflect.set(target, name, value, receiver)方法:

Reflect.set方法设置target对象的name属性等于value

Reflect.has(obj, name)方法:

Reflect.has方法对应name in obj里面的in运算符。

Reflect.deleteProperty(obj, name)方法:

Reflect.deleteProperty方法等同于delete obj[name],用于删除对象的属性。

Reflect.construct(target, args)方法:

Reflect.construct方法等同于new target(...args),这提供了一种不使用new,来调用构造函数的方法。

Reflect.getPrototypeOf(obj)方法:

Reflect.getPrototypeOf方法用于读取对象的__proto__属性,对应Object.getPrototypeOf(obj)

Reflect.setPrototypeOf(obj, newProto)方法:

Reflect.setPrototypeOf方法用于设置目标对象的原型(prototype),对应Object.setPrototypeOf(obj, newProto)方法。它返回一个布尔值,表示是否设置成功。

Reflect.apply(func, thisArg, args)方法:

Reflect.apply方法等同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数。

Reflect.defineProperty(target, propertyKey, attributes)方法:

Reflect.defineProperty方法基本等同于Object.defineProperty,用来为对象定义属性。未来,后者会被逐渐废除,请从现在开始就使用Reflect.defineProperty代替它。

Reflect.getOwnPropertyDescriptor(target, propertyKey)方法:

Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象,将来会替代掉后者。

Reflect.isExtensible (target)方法:

Reflect.isExtensible方法对应Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展。

Reflect.preventExtensions(target)方法:

Reflect.preventExtensions对应Object.preventExtensions方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功。

Reflect.ownKeys (target)方法:

Reflect.ownKeys方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNamesObject.getOwnPropertySymbols之和。

6.Promise

是异步编程的一种解决方案。

从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

(1)、Promise的特点

Promise对象有以下两个特点:

  • 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

(2)、基本用法 

Promise对象是一个构造函数,用来生成Promise实例

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数。  

(3)、Promise的其他方法

  • Promise.catch()

用于指定发生错误时的回调函数

  • Promise.finally()

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。

  • Promise.all() 

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例

const p = Promise.all([p1, p2, p3]);
//p1、p2、p3都是 Promise 实例

p的状态由p1p2p3决定,分成两种情况:

  • 只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。
  • 只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
  • Promise.race()

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。 

  • Promise.allSettled()

用来确定一组异步操作是否都结束了(不管成功或失败)

  •  Promise.any()

该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回

  • Promise.resolve()

有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用

  • Promise.reject() 

会返回一个新的 Promise 实例,该实例的状态为rejected

7.Generator 函数(生成器)

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同

(1)、Generator 函数组成

Generator 有两个区分于普通函数的部分:

  • 一是在 function 后面,函数名之前有个 * ;

  • 函数内部有 yield 表达式。

其中 * 用来表示函数为 Generator 函数,yield 用来定义函数内部的状态

function* func(){
 console.log("one");
 yield '1';
 console.log("two");
 yield '2'; 
 console.log("three");
 return '3';
}

(2)、yield 表达式

由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志

遍历器对象的next方法的运行逻辑如下:

  1. 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
  2. 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
  3. 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
  4. 如果该函数没有return语句,则返回的对象的value属性值为undefined

注意:

yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行

(3)、next 方法

yield表达式本身没有返回值,或者说总是返回undefinednext方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

function* sendParameter(){
    console.log("start");
    var x = yield '2';
    console.log("one:" + x);
    var y = yield '3';
    console.log("two:" + y);
    console.log("total:" + (x + y));
}

//next不传参
var sendp1 = sendParameter();
sendp1.next();
// start
// {value: "2", done: false}
sendp1.next();
// one:undefined
// {value: "3", done: false}
sendp1.next();
// two:undefined
// total:NaN
// {value: undefined, done: true}

//next传参
var sendp2 = sendParameter();
sendp2.next(10);
// start
// {value: "2", done: false}
sendp2.next(20);
// one:20
// {value: "3", done: false}
sendp2.next(30);
// two:30
// total:50
// {value: undefined, done: true}

(4)、throw方法

可以在函数体外抛出错误,然后在 Generator 函数体内捕获

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('内部捕获', e);
  }
};

var i = g();
i.next();

try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b

遍历器对象i连续抛出两个错误。第一个错误被 Generator 函数体内的catch语句捕获。i第二次抛出错误,由于 Generator 函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。 

(5)、Generator.return()

可以返回给定的值,并且终结遍历 Generator 函数

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next()        // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next()        // { value: undefined, done: true }
/*
返回值的value属性就是return()方法的参数foo。
并且,Generator 函数的遍历就终止了,返回值的done属性为true,
以后再调用next()方法,done属性总是返回true
*/

(6)、yield* 表达式 

如果在 Generator 函数内部,调用另一个 Generator 函数。需要在前者的函数体内部,自己手动完成遍历。

function* foo() {
  yield 'a';
  yield 'b';
}

function* bar() {
  yield 'x';
  // 手动遍历 foo()
  for (let i of foo()) {
    console.log(i);
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// x
// a
// b
// y
/*
foo和bar都是 Generator 函数,在bar里面调用foo,就需要手动遍历foo。
如果有多个 Generator 函数嵌套,写起来就非常麻烦
*/

yield*表达式,作为解决办法,用来在一个 Generator 函数里面执行另一个 Generator 函数

function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

8.Iterator(迭代器)

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)

Iterator 的作用有三个:

  • 为各种数据结构,提供一个统一的、简便的访问接口;
  • 使得数据结构的成员能够按某种次序排列;
  • ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

Iterator 的遍历过程是这样的:

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

工作原理:

  1. 创建一个指针对象,指向当前数据结构的起始位置
  2. 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
  3. 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
  4. 每调用 next 方法返回一个包含 value 和 done 属性的对象

9.async 函数

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。

async 函数是什么?一句话,它就是 Generator 函数的语法糖。

(1)、async 函数基础

语法:

async function name([param[, param[, ... param]]]) { statements }
  • name: 函数名称。
  • param: 要传递给函数的参数的名称。
  • statements: 函数体语句。

返回值:

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。

async function helloAsync(){
    return "helloAsync";
  }
  
console.log(helloAsync())  // Promise {<resolved>: "helloAsync"}
 
helloAsync().then(v=>{
   console.log(v);         // helloAsync
})

async 函数有多种使用形式:

// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// 箭头函数
const foo = async () => {};

(2)、await

await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。

语法:

[return_value] = await expression;
  • expression: 一个 Promise 对象或者任何要等待的值。
async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123

await针对所跟不同表达式的处理方式:

  • Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
  • 非 Promise 对象:直接返回对应的值。

10.Module(模块化)

(1)、概述

JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。

在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入

由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。

除了静态加载带来的各种好处,ES6 模块还有以下好处。

  • 不再需要UMD模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。目前,通过各种工具库,其实已经做到了这一点。
  • 将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者navigator对象的属性。
  • 不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。

模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

(2)、严格模式

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";

严格模式主要有以下限制:

  • 变量必须声明后再使用
  • 函数的参数不能有同名属性,否则报错
  • 不能使用with语句
  • 不能对只读属性赋值,否则报错
  • 不能使用前缀 0 表示八进制数,否则报错
  • 不能删除不可删除的属性,否则报错
  • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
  • eval不会在它的外层作用域引入变量
  • evalarguments不能被重新赋值
  • arguments不会自动反映函数参数的变化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局对象
  • 不能使用fn.callerfn.arguments获取函数调用的堆栈
  • 增加了保留字(比如protectedstaticinterface

上面这些限制,模块都必须遵守。

注意this的限制。ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this

(3)、export 命令

export命令用于规定模块的对外接口

export 命令可以出现在模块的任何位置,但必需处于模块顶层

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。

需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

//写法一
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

//写法二
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export { firstName, lastName, year };

 as 的用法:

通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。

function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

(4)、import 命令

import命令用于输入其他模块提供的功能

// main.js
import { firstName, lastName, year } from './profile.js';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径。如果不带有路径,只是一个模块名,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置 

import { myMethod } from 'util';
//til是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块

 import 命令的特点:

  • 只读属性:不允许在加载模块的脚本里面,改写接口的引用指向,即可以改写 import 变量类型为对象的属性值,不能改写 import 变量类型为基本类型的值。
  • 单例模式:多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。import 同一模块,声明不同接口引用,会声明对应变量,但只执行一次 import
  • 静态执行特性:import 是静态执行,所以不能使用表达式和变量

(5)、export default命令

使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。

为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。

import命令可以为该匿名函数指定任意名字:

// import-default.js
import customName from './export-default';
customName(); // 'foo'

特点:

  • 在一个文件或模块中,export、import 可以有多个,export default 仅有一个。
  • export default 中的 default 是对应的导出接口变量。
  • 通过 export 方式导出,在导入时要加{ },export default 则不需要。
  • export default 向外暴露的成员,可以使用任意变量来接收。

(6)、export 与 import 的复合写法

export { foo, bar } from 'my_module';

// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };

export 与 import 可以在同一模块使用,使用特点:

  • 可以将导出接口改名,包括 default。
  • 复合使用 export 与 import ,也可以导出全部,当前模块导出的接口会覆盖继承导出的。

(7)、import() 

支持动态加载模块

概述:

  • import()返回一个 Promise 对象。
  • import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。
  • import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同。
  • import()类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载。

适用场合:

  • 按需加载

import()可以在需要的时候,再加载某个模块。

button.addEventListener('click', event => {
  import('./dialogBox.js')
  .then(dialogBox => {
    dialogBox.open();
  })
  .catch(error => {
    /* Error handling */
  })
});
  • 条件加载

import()可以放在if代码块,根据不同的情况,加载不同的模块。

if (condition) {
  import('moduleA').then(...);
} else {
  import('moduleB').then(...);
}
  • 动态的模块路径

import()允许模块路径动态生成

import(f())
.then(...);

根据函数f的返回结果,加载不同的模块 


本文由博主在学习过程中所写,如有错误和问题,望理解。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萌新小吉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值