JS之ES6学习笔记

参考阮一峰ECMAScript 6 入门http://es6.ruanyifeng.com/#docs/let

目录

一、let和const命令

1.let

  • 只在代码块内有效
{
  let a = 10;
  var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
  • 设置循环变量的部分是父作用域,循环体内部是单独子作用域
for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
  • 不存在变量提升
console.log(foo); // 输出undefined
var foo = 2;

//实际为
var foo;
console.log(foo);
foo=2;

故为undefined

console.log(bar); // 报错ReferenceError
let bar = 2;

无提升,打印前并无变量bar ,故报错

  • 暂时性死区(temporal dead zone,TDZ)
    在代码块内,使用let声明变量前,变量不可用
    死区内typeof也不可用
if (true) {
  // TDZ开始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError
 typeof tmp;// ReferenceError
  let tmp; // TDZ结束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}
  • 注意情况
// 不报错
var x = x;

// 报错
let x = x;
// ReferenceError: x is not defined
  • let不允许在相同作用域内,重复声明同一个变量。
    // 报错
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() // 不报错

2.块级作用域

  • 块级作用域内声明的函数类似于let,对作用域外没有影响。
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
  • 函数声明还会提升到所在的块级作用域的头部。
function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }

  f();
}());

运行到ES6环境中为

// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
  var f = undefined;
  if (false) {
    function f() { console.log('I am inside!'); }
  }

  f();
}());
// Uncaught TypeError: f is not a function
  • 块级作用域需要大括号

3.const命令

  • 声明只读常量,声明后不可修改
  • 一旦声明,必须立刻初始化,不可后续赋值
  • 作用域与let一致
  • 不提升,存在暂时性死区,不可重复声明
  • 本质
    对数值、字符串、布尔值等简单数据,内存地址中的值不改变,等同常量。
    对对象和数组等复合型数据,保证的是指向内存地址的指针不可变,但指针指向的数据可变。
    例如对于对象:
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

数组同理。

  • 可用Object.freeze冻结对象
const foo = Object.freeze({});

// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

4.顶层对象属性

  • ES5中,浏览器环境中window,它还具有实体含义,指窗口对象,Node是global,顶层对象属性等价于全局变量,
  • ES6中,var function 声明的全局变量是顶层对象属性;let const class 声明的全局变量不是顶层对象属性。

globalThis对象

顶层对象提供全局作用域,但
在这里插入图片描述

  • 同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。
  • 全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
    函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined
    不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了
    CSP(Content Security Policy,内容安全策略),那么evalnew Function这些方法都可能无法使用。
  • ES2020在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this。

二、变量的解构赋值

1.数组的解构赋值

  • 模式匹配,从数组中提取值对变量赋值,解构不成功为undefiend
 let [a, b, c] = [1, 2, 3];
 let [x, , y] = [1, 2, 3];//x =1;y=3
//剩余模式
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
  • 右边是不可遍历解构将会报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
  • 默认值
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x = 1] = [null];//x=null

如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。

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

let [x = f()] = [1];

因为x能取到值,所以函数f根本不会执行。上面的代码其实等价于下面的代码。

let x;
if ([1][0] === undefined) {
  x = f();
} else {
  x = [1][0];
}
  • 嵌套赋值

2.对象的解构赋值

  • 数组的元素有序,变量的取值由它的位置决定;而对象的属性无序,变量必须与属性同名,才能取到正确的值。
let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined
  • 解构失败等于undefiend
    -将对象方法赋值到变量上
// 将Math的对数正弦余弦赋值到变量
let { log, sin, cos } = Math;

// console.log赋值给log
const { log } = console;
log('hello') // hello
  • 内部机制:先找到同名属性,然后再赋给对应的变量
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined

foo是模式,baz才是变量

  • 嵌套赋值,注意模式与变量
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} 是模式
  1. 默认值:生效条件为对象属性值===undefiend
  2. 注意

1)代码块

let x;
{x} = {x: 1};//SyntaxError: syntax error
// 正确的写法
let x;
({x} = {x: 1});

2)允许左边不放变量名

({} = [true, false]);

3)数组也是一种对象,键值对

let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3

3.字符串解构赋值

字符串转换成类数组对象属性也可解构赋值

const [a, b] = 'hi';//a=h  b=i
let {length : len} = 'hello';//len = 5

4.数值和布尔值的解构赋值

  • 将其转为对象再赋值,undefiendnull不能转换成对象
let {toString: s} = 123;
s === Number.prototype.toString // true

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

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

5.函数参数的解构赋值

 [[1, 2], [3, 4]].map(([a, b]) => a + b);
  • 指定默认值
    给函数函数指定默认值
//给函数指定默认值
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]

给变量指定默认值

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]

6.括号

  • 变量声明不允许带括号
//报错
let [(a)] = [1];
function f([(z)]) { return z; }//函数参数也是变量声明
  • 模式不允许放括号
[({ p: a }), { x: c }] = [{}, {}];
  • 赋值语句和非模式可以使用括号
[(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});
  • map中使用 遍历

三、字符串的拓展

1.Unicode表示法

  • \uxxxx:\u0000-\uFFFF
"\uD842\uDFB7"// "𠮷" 超过用双字节表示
"\u20BB7"// " 7"  \u20bb不可打印,显示为空格 后面跟一个7
  • ES6的大括号表示法,本质与四字节的UTF-16编码等价
"\u{20BB7}" // "𠮷"
let hello = 123;
hell\u{6F} // 123

'\u{1F680}' === '\uD83D\uDE80'
// true
  • JS共有6种方法表示一个字符
1 '\z' === 'z'  //转义  
2 '\172' === 'z' //八进制
3 '\x7A' === 'z' // 十六进制
4 '\u007A' === 'z' // unicode码
5 '\u{7A}' === 'z' // ES6大括号

2字符串的遍历器接口

for…of循环遍历

for (let codePoint of 'foo') {
  console.log(codePoint)
}//码点循环遍历
// "f"
// "o"
// "o"
//码点转换为字符,识别大于oxFFFF的码点
let text = String.fromCodePoint(0x20BB7);
for (let i of text) {
  console.log(i);
}// "𠮷"

//使用for循环会认为是有两个字符
for (let i = 0; i < text.length; i++) {
  console.log(text[i]);
}
// " "
// " "

3.直接输入U+2028 和 U+2029

  • 可正确解析json

4.JSON.stringify() 的改造

UTF-8 标准规定,0xD8000xDFFF之间的码点,不能单独使用,必须配对使用

  • ES2019 改变了JSON.stringify()的行为。如果遇到0xD800到0xDFFF之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理。
JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""

5.模板字符串

6.模板编译

7.模板标签

8.模板字符串的限制

  • jQuery

四、字符串新增方法

1.String.fromCodePoint()

  • ES5码点返回对应字符,>0xFFFF的部分舍弃,方法定义再字符串的实例上
 String.fromCharCode(0x20BB7)  // "ஷ"为0x20BB
  • ES6String.fromCodePoint(),可以识别 ,定义在String上
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'// true

2.String.raw()

  • 模板字符处理方法

3.实例方法,codePointAt

  • ES5
var s = "𠮷";

s.length // 2 两个字符
s.charAt(0) // '' 无法读取整个字符
s.charAt(1) // ''
s.charCodeAt(0) // 55362 返回前两个字节
s.charCodeAt(1) // 57271 返回后两个字节

-ES6 正确处理四字节
codePointAt()方法

let s = '𠮷a';

s.codePointAt(0) // 134071正确返回十进制码点
s.codePointAt(1) // 57271 与codePointAt()相同
s.codePointAt(2) // 97 位置是1 但必须传入2
s.codePointAt(0).toString(16) // "20bb7"
  • 补充
    1)for of 正确识别
let s = '𠮷a';
for (let ch of s) {
  console.log(ch.codePointAt(0).toString(16));
}
// 20bb7
// 61

2)拓展运算符(

let arr = [...'𠮷a']; // arr.length === 2
arr.forEach(
  ch => console.log(ch.codePointAt(0).toString(16))
);
// 20bb7
// 61

3)测试字符是否是四字节

function is32Bit(c) {
  return c.codePointAt(0) > 0xFFFF;
}

is32Bit("𠮷") // true
is32Bit("a") // false

4.实例方法:normalize()

  • 将字符的不同表示方法统一为同样的形式
    不能识别三个
'\u01D1'.normalize() === '\u004F\u030C'.normalize()

5.实例方法:includes(),startsWith(),endsWith()

let s = 'Hello world!';

s.startsWith('world'6) // true 参数字符在原字符串头部,第六个开始
s.endsWith('Hello'5) // true 参数字符在原字符串尾部,前5个
s.includes('o'6) // true 字符串中有 第6开始

6.实例方法:repeat()

  • 返回新字符串,将原字符串重复n次
1 'x'.repeat(3) // "xxx"
2 'na'.repeat(2.9) // "nana" 向下取整
3 'na'.repeat(Infinity) // RangeError 无穷报错
4 'na'.repeat(-1) // RangeError 负数报错
5 'na'.repeat(-0.9) //  "" 取整为-0,然后为0
6 'na'.repeat('na') // "" 字符串转为数字0
7 'na'.repeat(NaN) // "" nan=0

7.实例方法:padStart(),padEnd()

  • 头尾部补全
'x'.padStart(5, 'ab') // 'ababx' 5是补全长度 ab是补全字符
'x'.padEnd(4, 'ab') // 'xaba'
'xxx'.padEnd(2, 'ab') // 'xxx' 超过长度不生效
'abc'.padStart(10, '0123456789') // '0123456abc' 截位补全
'x'.padStart(4) // '   x' 空格补全
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"  提示字符串格式

8.实例方法:trimStart(),trimEnd()

  • 头尾部消除空格
const s = '  abc  ';

s.trim() // "abc" 均消除
s.trimStart() // "abc  "  头部消除 trimLeft()
s.trimEnd() // "  abc  尾部消除 trimRight()

五、正则的拓展

1.RegExp构造函数

  • ES5中
var regex = new RegExp(/xyz/i);
var regex = new RegExp('xyz', 'i');
var regex = new RegExp(/xyz/, 'i');//错误模式
  • ES6中
new RegExp(/abc/ig, 'i').flags   // "i"  将ig覆盖  flag方法返回正则表达式修饰符

2字符串的正则方法

  • String.prototype.match 调用 RegExp.prototype[Symbol.match]
  • String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
  • String.prototype.search 调用 RegExp.prototype[Symbol.search]
  • String.prototype.split 调用 RegExp.prototype[Symbol.split]

3u修饰符

正确处理四字节UTF-16编码

  • 点字符,码点大于0xFFFF的字符不能识别
var s = '𠮷';

/^.$/.test(s) // false 认为是两个字符,匹配失败
/^.$/u.test(s) // true  
  • Unicode字符表示法,识别大括号,避免解读为量词
- /\u{61}/.test('a') // false  不加u 被识别成61个u
/\u{61}/u.test('a') // true
/\u{20BB7}/u.test('𠮷') // true
  • 量词
    识别码点大于0xFFFF
/𠮷{2}/.test('𠮷𠮷') // false
/𠮷{2}/u.test('𠮷𠮷') // true
  • 预定义模式
    预定义 \S 匹配非空白字符 ,识别码点大于0xFFFF,
/^\S$/u.test('𠮷') // true
  • i修饰符
/[a-z]/i.test('\u212A') // false 无法识别非规范的K字符
/[a-z]/iu.test('\u212A') // true
  • 转义
/\,/ // /\,/   无效
/\,/u // 报错

4.RegExp.prototype.unicode属性

  • unicode属性看出是否设置了u修饰符
const r1 = /hello/;
const r2 = /hello/u;

r1.unicode // false
r2.unicode // true

5.y修饰符

  • g的区别,y从剩余字符头部开始
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;

r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]
//剩余_aa_a
r1.exec(s) // ["aa"] //剩余的里面有就可以
r2.exec(s) // null //剩余的里面第一个不是a  失败
  • y会发现非法字符

6.RegExp.prototype.sticky属性

  • sticky属性,检测是否设置了y修饰符
var r = /hello\d/y;
r.sticky // true

7.RegExp.prototype.flags属性

  • flags属性,返回正则表达式的修饰符。
/abc/ig.source
// "abc"
/abc/ig.flags
// 'gi'

8.s修饰符:dotAll模式

点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用u修饰符解决;另一个是行终止符(line terminator character)。

  • 行终止符
    U+000A 换行符(\n
    U+000D 回车符(\r
    U+2028 行分隔符(line separator)
    U+2029 段分隔符(paragraph separator)
  • s 修饰符 任意匹配多个字符,点代表一切字符
/foo.bar/s.test('foo\nbar') // true

是否处在dotAll模式

re.dotAll // true

9.后行断言

  • 先行(否定)断言
    /x(?=y)/ x在y前面
    /x(?!y)/ x不在y前面
  • 后行(否定)断言,先匹配右边x 再左边y
    /(?<=y)x/ x在y后面
    /(?<!y)x/ x不在y后面

10.Unicode属性类

  • \p{…}和\P{…}
    匹配希腊字母,以u修饰符结尾
const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true

11.具名组匹配

  • 可以为每一组匹配指定名字 ?<组名>,加了id
const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31
  • 解构赋值和替换
    有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值。
let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
one  // foo
two  // bar

字符串替换时,使用$<组名>引用具名组。

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;

'2015-01-02'.replace(re, '$<day>/$<month>/$<year>')
// '02/01/2015'
  • 引用,\k<组名>
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc') // true  
///^(?<word>[a-z]+)!\k<word>$/=/^(?<word>[a-z]+)!\<word>[a-z]$/

12.String.prototype.matchAll()

循环去除每一轮的正则匹配

var regex = /t(e)(st(\d?))/g;
var string = 'test1test2test3';

var matches = [];
var match;
while (match = regex.exec(string)) {
  matches.push(match);
}

matches
// [
//   ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"],
//   ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"],
//   ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]

String.prototype.matchAll()方法,可以一次性取出所有匹配。不过,它返回的是一个遍历器(Iterator),而不是数组。

// 转为数组方法一
[...string.matchAll(regex)]

// 转为数组方法二
Array.from(string.matchAll(regex))

六、数值的拓展

1.二进制和八进制表示法

  • 二进制0b(0B),八进制0o(0O)
  • 转换
Number('0b111')  // 7
Number('0o10')  // 8

2.Number.isFinite(),Number.isNaN()

  • 检查一个数值是否为有限的(finite)
Number.isFinite(15); // true
Number.isFinite(-Infinity); // false
Number.isFinite("25")// false false 类型不是数值返回false
  • 检查一个值是否为NaN。参数类型不是NaN一律返回false
 Number.isNaN('15') // false
 Number.isNaN(9/NaN) // true
  • 以上方法只对数值有效,传统全局方法会将非数值转换为数值
isFinite("25") // true
isNaN("NaN") // true

3.Number.parseInt(),Number.parseFloat()

  • 将全局方法移植到Number对象上
Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true

parseInt('12.34') // 12
Number.parseInt('12.34') // 12

parseFloat('123.45#') // 123.45
Number.parseFloat('123.45#') // 123.45

4.Number.isInteger

  • 数值是否为整数
Number.isInteger(25.1) // false
Number.isInteger(25.0) // true
Number.isInteger('15') // false
  • 数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)
Number.isInteger(3.0000000000000002) // true 16个十进制位转换成二进制超过53位
  • 数值的绝对值小于Number.MIN_VALUE(5E-324)会被自动转为 0
Number.isInteger(5E-325) // true

5.Number.EPSILON

  • 对于 64 位浮点数来说,大于 1 的最小浮点数相当于二进制的1.00…001,小数点后面有连续 51 个零。这个值减去 1 之后,就等于 2 的 -52 次方。
Number.EPSILON === Math.pow(2, -52)
  • 设置“能够接受的误差范围”
    误差范围设为 2 的-50 次方(即Number.EPSILON * Math.pow(2, 2)
5.551115123125783e-17 < Number.EPSILON * Math.pow(2, 2)
// true
  • 6.误差检查函数。
function withinErrorMargin (left, right) {
  return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}

0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true

6.安全整数和Number.isSafeInteger()

  • 安全整数Number.MAX_SAFE_INTERGERNumber.MIN_SAFE_INTERGER
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1
  • 判断一个整数是否落在这个范围之内
Number.isSafeInteger(9007199254740990) // true
Number.isSafeInteger('a') // false
Number.isSafeInteger(null) // false
Number.isSafeInteger(NaN) // false

7.Math 对象的扩展

  • Math.trunc() 截取整数部分,非数值转换成数值,
Math.trunc(-0.1234) // -0
Math.trunc(false) // 0
Math.trunc(null) // 0
//对于空值和无法截取整数的值,返回NaN。
Math.trunc(NaN);      // NaN
Math.trunc('foo');    // NaN
Math.trunc();         // NaN
Math.trunc(undefined) // NaN

代码模拟

Math.trunc = Math.trunc || function(x) {
  return x < 0 ? Math.ceil(x) : Math.floor(x);
};
  • Math.sign()判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。无法转为数值的值,会返回NaN。
 Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN

代码模拟

Math.sign = Math.sign || function(x) {
  x = +x; // convert to a number
  if (x === 0 || isNaN(x)) {
    return x;
  }
  return x > 0 ? 1 : -1;
};
  • Math.cbrt()计算一个数的立方根。对于非数值,Math.cbrt方法内部也是先使用Number方法将其转为数值。
 Math.cbrt(-1) // -1

代码模拟。

Math.cbrt = Math.cbrt || function(x) {
  var y = Math.pow(Math.abs(x), 1/3);
  return x < 0 ? -y : y;
};
  • Math.clz32() count leading zero 参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0。
 Math.clz32(0) // 32
 Math.clz32(1 << 1) // 30 左移
 Math.clz32(3.2) // 30 只考虑整数部分
 //其他值转换成数值
 Math.clz32('foo') // 32
 Math.clz32(true) // 31
  • Math.imul() 返回两个数以 32 位带符号整数形式相乘的32位带符号结果
 Math.imul(-1, 8)  // -8

超过 2 的 53 次方的值无法精确表示。这就是说,对于那些很大的数的乘法,低位数值往往都是不精确的,Math.imul方法可以返回正确的低位数值。

  • Math.fround() 返回一个数的32位单精度浮点数形式。
    对于 -224 至 224 之间的整数(不含两个端点),返回结果与参数本身一致
Math.fround(2 ** 24 + 1)   // 16777216 丢失精度

对于 NaN 和 Infinity,此方法返回原值。对于其它类型的非数值,Math.fround 方法会先将其转为数值,再返回单精度浮点数。
模拟

Math.fround = Math.fround || function (x) {
  return new Float32Array([x])[0];
};
  • Math.hypot()返回所有参数的平方和的平方根
 Math.hypot(3, 4, 'foo'); // NaN
  • Math.expm1()返回 ex - 1即Math.exp(x) - 1。
  • Math.log1p()即Math.log(1 + x)
  • Math.log10()返回以 10 为底的x的对数。如果x小于 0,则返回 NaN。
  • Math.log2()返回以 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)

8.指数运算符

  • 从最右边开始计算
// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512
  • 赋值运算符
let b = 4;
b **= 3;
// 等同于 b = b * b * b;

9.BigInt数据类型

  • 没有位数限制数据后面加后缀n
1234 // 普通整数
1234n // BigInt
  • typeof返回bigint
typeof 123n // 'bigint'
  • BigInt 可以使用负号(-),但是不能使用正号(+),因为会与 asm.js 冲突。
-42n // 正确
+42n // 报错
  • BigInt对象
BigInt('123') // 123n

构造函数必须有参数,参数必须可以正常转为数值

new BigInt() // TypeError
BigInt(undefined) //TypeError
BigInt(null) // TypeError
BigInt('123n') // SyntaxError  无法解析成Number类型
BigInt('abc') // SyntaxError
BigInt(1.5) // RangeError 小数也报错

BigInt 对象继承了 Object 对象的两个实例方法。

BigInt.prototype.toString()
BigInt.prototype.valueOf()

它还继承了 Number 对象的一个实例方法。本地时间转换成字符串

BigInt.prototype.toLocaleString()

三个静态方法

const max = 2n ** (64n - 1n) - 1n;

BigInt.asIntN(64, max)  // -2width - 1到2width - 1 - 1对应的数
// 9223372036854775807n

BigInt.asUintN(64, max + 1n) //  0 到 2width - 1 之间对应的值。
// 9223372036854775808n

BigInt.parseInt('9007199254740993', 10)
  • 转换规则
Boolean(0n) // false
Boolean(1n) // true
Number(1n)  // 1
String(1n)  // "1"

//取反运算
!0n // true
!1n // false
  • 数学运算
    +-*** 这四个二元运算符,与 Number 类型的行为一致。除法运算/会舍去小数部分,返回一个整数。
9n / 5n
// 1n

不带符号的右移位运算符>>>,一元的求正运算符+会报错

  • 其他运算
    BigInt 对应的布尔值,与 Number 类型一致,即0n会转为false,其他值转为true。
if (0n) {
  console.log('if');
} else {
  console.log('else');
}
// else

比较运算符(比如>)和相等运算符(==)允许 BigInt 与其他类型的值混合计算,因为这样做不会损失精度。

0n < 1 // true
0n < true // true
0n == 0 // true
0n == false // true
0n === 0 // false

BigInt 与字符串混合运算时,会先转为字符串,再进行运算。

'' + 123n // "123"

七、函数的扩展

1.参数默认值

  • 在函数体中,不能用let或const再次声明,否则会报错。
function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}
  • 不能有同名参数
// 报错
function foo(x, x, y = 1) {
  // ...
}
  • 与解构赋值结合使用
function foo({x, y = 5} = {}) {
  console.log(x, y);
}

foo() // undefined 5
  • 函数参数默认值是一个有具体属性的对象,没设置解构
function m2({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}
  • 函数参数的默认值是空对象,设置了对象解构赋值的默认值
function m1({x = 0, y = 0} = {}) {
  return [x, y];
}

// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]

// x 和 y 都有值的情况
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]

// x 有值,y 无值的情况
m1({x: 3}) // [3, 0] //多一个解构赋值,没有参数就解构里面的数
m2({x: 3}) // [3, undefined] 

// x 和 y 都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]

m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]
  • 参数默认位置
function f(x, y = 5, z) {
  return [x, y, z];
}
//非尾部的参数设置默认值,实际上这个参数是没法省略的
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]
function foo(x = 5, y = 6) {
  console.log(x, y);
}
//undefined触发了默认值
foo(undefined, null) // 5 null
  • 函数length属性
    返回没有默认值的参数个数
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
  • 作用域
    参数默认值,单独作用域
var x = 1;
function foo(x, y = function() { x = 2; }) {
  var x = 3;
  y();
  console.log(x);
}

foo() // 3
x // 1
  • 应用,指定参数不可省略,否则抛出错误

function throwIfMissing() {
throw new Error(‘Missing parameter’);
}

function foo(mustBeProvided = throwIfMissing()) { //函数名后有括号,运行时执行
return mustBeProvided;
}

foo() //没有赋值,找默认值里的函数
// Error: Missing parameter

2.rest参数

  • 获取函数多余参数,就不需要使用argument对象了,rest是数组
function add(...values) {  //引入任意数目的参数
  let sum = 0;

  for (var val of values) {
    sum += val;
  }

  return sum;
}

add(2, 5, 3) // 10
  • length属性不包括rest参数

3.严格模式

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

  • 规避方法

'use strict'; //全局严格

function doSomething(a, b = a) {
  // code
}

函数包在立即执行函数里面

const doSomething = (function () {
  'use strict';
  return function(value = 42) {
    return value;
  };
}());

4.name属性

  • 返回函数的函数名
    匿名函数不一样,具名函数一样
var f = function () {};
//匿名函数赋值给变量
// ES5
f.name // ""

// ES6
f.name // "f"
  • 注意 构造函数 bind
(new Function).name // "anonymous"
function foo() {};
foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound "

5.箭头函数

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};
  • 返回对象须加括号
let getTempItem = id => ({ id: id, name: "Temp" });
  • 与变量解构结合使用
const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person) {
  return person.first + ' ' + person.last;
}
  • 注意

函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

function foo() {
  setTimeout(() => {
    console.log('id:', this.id); //指向函数定义生效时所在的对象
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// id: 42 //普通函数则输出21
  • 让this指向固定化
function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数
  setInterval(() => this.s1++, 1000); //this timer定义时的作用域
  // 普通函数
  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
  • 三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:argumentssupernew.target
  • 箭头函数没有自己的this,所以当然也就不能用call()apply()bind()这些方法去改变this的指向
  • 不适合场合
    1)
    定义方法对象
    2)
    需要动态this
  • 嵌套的箭头函数

6.尾调用优化

  • 调用帧形成的调用栈
function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同于
function f() {
  return g(3);
}
f();

// 等同于  只保留内层函数调用帧
g(3);
  • 尾递归,只有一个调用帧,不容易发生“栈溢出”
    将内部变量改写成函数的参数
    阶乘
function tailfactorial(n, total) {
  if (n === 1) return total;
  return tailfactorial(n - 1, n * total);
}
function factorial(n) {
  return tailFactorial(n, 1); //方便阅读检查
}

factorial(5) // 120

斐波那契

function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) {return ac2};

  return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}

与柯里化结合

function currying(fn, n) {
  return function (m) {
    return fn.call(this, m, n);
  };
}

function tailFactorial(n, total) {
  if (n === 1) return total;
  return tailFactorial(n - 1, n * total);
}

const factorial = currying(tailFactorial, 1);

factorial(5) // 120   变成了只接收一个参数
  • 严格模式

ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。

func.arguments:返回调用时函数的参数。
func.caller:返回调用当前函数的那个函数。

尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真

  • 正常模式下的尾递归优化
    1)蹦床函数
function trampoline(f) {
  while (f && f instanceof Function) { //返回一个函数就继续执行该函数
    f = f();
  }
  return f;
}

7.函数参数尾逗号

function clownsEverywhere(
  param1,
  param2, //添加新参数直接添加,不用在上一行加逗号
) { /* ... */ }

clownsEverywhere(
  'foo',
  'bar',
);

8.Function.prototype.toString()

  • 注释 空格全部包含
function /* foo comment */ foo () {}

foo.toString()
// "function /* foo comment */ foo () {}"

catch命令的参数省略

  • 允许catch省略语句
try {
  // ...
} catch {
  // ...
}

八、数组的扩展

1.扩展运算符

  • 将数组转换成参数序列
function add(x, y) {
  return x + y;
}

const numbers = [4, 38];
add(...numbers) // 42
  • 只有函数调用时,扩展运算符才可以放到圆括号中
(...[1, 2])
// Uncaught SyntaxError: Unexpected number

console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number

console.log(...[1, 2])
// 1 2
  • 取代apply
//js不提供求数组中最大元素的方法
// ES5 的写法
Math.max.apply(null, [14, 3, 77])

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

// 等同于
Math.max(14, 3, 77);
Array.prototype.push.apply(arr1, arr2);//ES5,push不能是数组
arr1.push(...arr2); //ES6
  • 应用
    (1)复制数组
    ES5,复制只会复制指针,不是全新数组
const a2 = a1;

变通方法

const a2 = a1.concat();

ES6扩展运算符

const a2 = [...a1];
const [...a2] = a1;

(2)
合并数组

[...arr1, ...arr2, ...arr3]  新数组是原数组的浅拷贝,只是引用

(3)
与解构赋值生成数组

const [first, ...rest] = [];
first // undefined
rest  // []

const [first, ...rest] = ["foo"]; //拓展运算符只能放在最后一位
first  // "foo"
rest   // []

(4)
字符串

[...'hello']
// [ "h", "e", "l", "l", "o" ]
[...'x\uD83D\uDE80y'].length // 3 正确识别四字节字符串

(5)遍历器
(6)Map 、Set、Generator,均是Iterator接口的对象
没有的使用扩展运算符会报错

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let arr = [...map.keys()]; // [1, 2, 3]

const go = function*(){
  yield 1;
  yield 2;
  yield 3;
};

[...go()] // [1, 2, 3] //转为一个数组

转为数组

// arguments对象
function foo() {
  const args = [...arguments];
}

// NodeList对象
[...document.querySelectorAll('div')]

2.Array.from

  • 将类似数组的对象(array-like object)和可遍历(iterable)的对象,转为真正的数组
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
function foo() {
  var args = Array.from(arguments);
  // ...
}

字符串

Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']

set

let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']

有length属性的对象,类数组

Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
  • 替代方法 [].slice.call(obj)
const toArray = (() =>
  Array.from ? Array.from : obj => [].slice.call(obj)
)();
  • 第二个参数,提供map功能
Array.from(arrayLike, x => x * x);

3.Array.of

  • Array.of基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。
Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1, 2) // [1, 2]
  • 模拟
function ArrayOf(){
  return [].slice.call(arguments);
}

4.数组实例的copyWithin

Array.prototype.copyWithin(target, start = 0, end = this.length)
//target必选
// -2相当于3号位,-1相当于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]
// 将3号位复制到0号位
[].copyWithin.call({length: 5, 3: 1}, 0, 3)
// {0: 1, 3: 1, length: 5}
//理解
({0:undefined,1:undefined,2:undefined,3: 1,4:undefined,5:undefined,length: 5}).copyWithin(0,3,5);
结果为:
{0:1,1:undefined,2:undefined,3: 1,4:undefined,5:undefined,length: 5};
也就是
{0:1,3:1,length:5}

5.数组实例的find()和findIndex()

  • 找出第一个符合条件的数组成员
    返回成员
[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10  不符合条件返回undefiend

返回成员位置

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2  不符合条件返回-1

接受第二个参数,对象。绑定回调函数的this对象

function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person); 

6.数组实例的fill

  • 三个参数,填充内容、起始位置、结束位置
['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']
  • 填充类型位对象,为浅拷贝,被赋值的对象指的是同一个内存地址
let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]

7.数组实例的entries(),keys()和values()

  • 利用for of 进行遍历
for (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"
  • 利用遍历器对象的next方法遍历
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c'] 

8.数组实例的includes()

-某个数组是否包含给定的值

[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true 倒数位置
[1, 2, 3].includes(3, -4);//true 超过数组长度重置为0

-替代

const contains = (() =>
  Array.prototype.includes
    ? (arr, value) => arr.includes(value)
    : (arr, value) => arr.some(el => el === value)
)();
contains(['foo', 'bar'], 'baz'); // => false

9.flat(),fiatMap()

  • 拉平数组
[1, 2, [3, [4, 5]]].flat(2) //两层需要2
// [1, 2, 3, 4, 5]  返回新数组

[1, 2, , 4, 5].flat()
// [1, 2, 4, 5] //遇空位会跳过去
  • 对原数组的每个成员执行一个函数,然后对返回值组成的数组执行flat()方法
// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]

10.数组的空位

  • ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。

forEach(), filter(), reduce(), every() 和some()都会跳过空位。
map()会跳过空位,但会保留这个值
join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。

  • ES6 则是明确将空位转为undefined

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

九、对象的扩展

1.属性的简洁表示法{x,y}

  • 大括号,写入变量和函数,作为对象属性和方法,但不能用作构造函数
function getPoint() {
  const x = 1;
  const y = 10;
  return {x, y}; //大括号
}
getPoint()  // {x:1, y:10}
let birth = '2000/01/01';

const Person = {

  name: '张三',

  //等同于birth: birth
  birth,

  // 等同于hello: function ()...
  hello() { console.log('我的名字是', this.name); }
};

2.属性名表达式[a]:2

  • 字面量定义对象, 表达式放在方括号内作为对象的属性名
    let propKey = ‘foo’;
let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};
obj['abc'] //123
  • 定义方法名
let obj = {
  ['h' + 'ello']() {
    return 'hi';
  }
};
obj.hello() // hi
  • 属性名表达式与简洁表示法,不能同时使用,会报错。
const foo = 'bar';
const baz = { [foo] };
  • 属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]
const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
};
//[keyA]和[keyB]得到的都是[object Object],所以[keyB]会把[keyA]覆盖掉,而myObject最后只有一个[object Object]属性。
myObject // Object {[object Object]: "valueB"}

3.方法的name属性

返回函数名

f.name//f
  • bind方法创造的函数,name属性返回bound加上原函数的名字
var doSomething = function() {
  // ...
};
doSomething.bind().name // "bound doSomething"
  • Function构造函数创造的函数,name属性返回anonymous
(new Function()).name // "anonymous"
  • 对象的方法是一个 Symbol 值,那么name属性返回的是这个 Symbol 值的描述
const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
  [key1]() {},
  [key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""

4.属性的可枚举和遍历

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。

let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
//    value: 123,
//    writable: true,
//    enumerable: true,  //可枚举性
//    configurable: true
//  }
  • 作用是不想让一些操作遍历到自己的这个属性
  • 属性的遍历
    (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 键,按照加入时间升序排列。

Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()] 数值 字符 symbol

5.super关键字

  • this关键字总是指向函数所在的当前对象,super,指向当前对象的原型对象
const proto = {
  x: 'hello',
  foo() {
    console.log(this.x);
  },
};

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

Object.setPrototypeOf(obj, proto); //将obj的原型对象设置为proto

obj.foo() // "world"  绑定的this却还是当前对象obj,super.foo指向原型对象proto的foo方法

6.对象的扩展运算符

  • 解构赋值,等号右边必须是对象,将右边的键和值拷贝到左边,且必须是最后一个参数,
- let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

//右边不能转化为对象
let { ...z } = null; // 运行时错误
let { ...z } = undefined; // 运行时错误

//不是最后一个参数
let { x, ...y, ...z } = someObject; // 句法错误
  • 浅拷贝,是引用不是副本
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2  x是解构赋值所在的对象,拷贝了对象obj的a属性,解构赋值拷贝的是这个值的引用,而不是这个值的副本
  • 解构赋值只能读取属性,读不到属性值
const o = Object.create({ x: 1, y: 2 });
o.z = 3;

let { x, ...newObj } = o;
let { y, z } = newObj;
newObj//{}
x // 1
y // undefined
z // 3
  • 扩展运算符
    取出对象所有可遍历属性
    拓展运算符后面必须是变量名,不能是解构赋值表达式
//数组是特殊对象
let foo = { ...['a', 'b', 'c'] };
foo // {0: "a", 1: "b", 2: "c"}
  • 转换
//1自动转换为Number{1},没有自身属性,返回空对象
// 等同于 {...Object(1)}
{...1} // {}
//字符串转换成类数组对象
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
  • 克隆对象(不会)
  • 合并俩对象
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。

7.链判断运算符

  • 链判断运算符?.
    判断是否有对象
const firstName = message?.body?.user?.firstName || 'default';
//等于
const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName) || 'default';

判断对象方法是否存在,如果存在就立即执行的例子

iterator.return?.()
  • 常用形式
```javascript
a?.b
// 等同于
a == null ? undefined : a.b

a?.[x]
// 等同于
a == null ? undefined : a[x]

a?.b()
// 等同于
a == null ? undefined : a.b() a.b不是函数会报错

a?.()
// 等同于
a == null ? undefined : a()
  • 注意
    (1)短路机制
    (2)
delete a?.b
// 等同于
a == null ? undefined : delete a.b //如果a是undefined或null,会直接返回undefined,而不会进行delete运算。

(3)括号影响

(a?.b).c
// 等价于
(a == null ? undefined : a.b).c //只对内部有影响,.c会永远执行

(4)报错

// 构造函数
new a?.()
new a?.b()

// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`

// 链判断运算符的左侧是 super
super?.()
super?.foo

// 链运算符用于赋值运算符左侧
a?.b = c

(5)右侧不得为十进制数值
为了保证兼容以前的代码,允许foo?.3:0被解析成foo ? .3 : 0,因此规定如果?.后面紧跟一个十进制数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数

8.null判断运算符??

  • 运算符左侧的值为null或undefined时,返回右侧的值

十、对象新增的方法

1.Object.is()同值相等

==:自动转换类型
===:Nan不等于自身,0+等于0-

  • 比较两个值是否严格相等
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

2.Object.assign()

将源对象(source)的所有可枚举属性,复制到目标对象(target)

const target = { a: 1, b: 1 };

const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3} 同名属性会覆盖

由于undefined和null无法转成对象,所以如果它们作为参数,就会报错,一参数会转成对象

Object.assign(undefined) // 报错
Object.assign(null) // 报错

多参数的,如果无法转成对象,就会跳过

const v1 = 'abc';
const v2 = true;
const v3 = 10;

const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
  • 只拷贝自身属性,不拷贝继承属性
  • 注意点
    (1)浅拷贝
    (2)同名属性替换
const v1 = 'abc';
const v2 = true;
const v3 = 10;

const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

(3)数组的处理

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]  视为对象

(4)取值函数的处理
求值后再赋值,而不是复制函数

  • 用途
    (1)为对象添加属性
 Object.assign(this, {x, y})

(2)为对象添加方法

Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});

(3)克隆对象

function clone(origin) {
  return Object.assign({}, origin);
}//原始对象拷贝到一个空对象,就得到了原始对象的克隆

(4)合并对象

const merge =
  (target, ...sources) => Object.assign(target, ...sources);

(5)为属性指定默认值

3.Object.getOwnPropertyDescriptors()

  • 返回指定对象所有自身属性(非继承属性)的描述对象。
const obj = {
  foo: 123,
  get bar() { return 'abc' }
};

Object.getOwnPropertyDescriptors(obj)
// { foo:
//    { value: 123,
//      writable: true,
//      enumerable: true,
//      configurable: true },
//   bar:
//    { get: [Function: get bar],
//      set: undefined,
//      enumerable: true,
//      configurable: true } }

4.__proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()

  • Object.setPrototypeOf()设置一个对象的prototype对象,返回参数对象本身
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);//返回object本身

proto.y = 20;
proto.z = 40;

obj.x // 10
obj.y // 20
obj.z // 40
  • Object.getPrototypeOf(),读取对象原型
  • 参数不是对象会被转为对象,返回第一个参数,undefiend和null无法转为对象

5.Object.keys(),Object.values(),Object.entries()

  • Object.keys(),返回数组,参数对象自身的可遍历属性的键名,无继承
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]
  • Object.values(),返回数组,参数对象本身可遍历属性的键值,无继承
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)
// ["b", "c", "a"]  属性名为数值的属性,是按照数值大小,从小到大遍历的
  • Object.entries()返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

6.Object.fromEntries(),将一个键值对数组转为对象

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }

十一、Symbol

  • 防止属性名的冲突,Symbol 值不是对象,是一种类似于字符串的数据类型
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();

s1 === s2 // false

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

s1 === s2 // false
  • 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.prototype.description

const sym = Symbol('foo');
String(sym) // "Symbol(foo)"
sym.toString() // "Symbol(foo)"
//直接使用
sym.description // "foo"

3.属性名Symbol

  • 可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性
let a = {};
a[mySymbol] = 'Hello!'

let a = {
  [mySymbol]: 'Hello!'
};
//错误写法
const a = {};
a.mySymbol = 'Hello!'; //此行为导致是字符串而不是symbol值
  • 定义常量,常量值不相等
const log = {};

log.levels = {
  DEBUG: Symbol('debug'),
  INFO: Symbol('info')
  }

4.消除魔术字符串

5.属性名的遍历

  • Object.getOwnPropertySymbols()方法,返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值
const obj = {};
const foo = Symbol('foo');

obj[foo] = 'bar';

for (let i in obj) {
  console.log(i); //  无输出
}

Object.getOwnPropertyNames(obj) // [] 得不到Symbol键名
Object.getOwnPropertySymbols(obj) // [Symbol(foo)]
  • Reflect.ownKeys()方法可以返回所有类型的键名
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 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');

s1 === s2 // true

调用Symbol.for(“cat”)30 次,每次都会返回同一个 Symbol 值,供全局环境搜索,但是调用Symbol(“cat”)30 次,会返回 30 个不同的 Symbol 值

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

let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

7.模块的Singleton模式

8.内置的Symbol值

十二、Set和Map数据结构

1.Set

  • 类似于数组,但是成员的值都是唯一的,没有重复的值。
    可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5

// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56
  • 去重
 [...new Set('ababbc')].join('')
// "abc"

NaN等于自身,对象不相等
let set = new Set();

set.add({});
set.size // 1

set.add({});
set.size // 2

l

et set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
  • Set 实例的属性和方法
    Set 结构的实例有以下属性。
  1. Set.prototype.constructor:构造函数,默认就是Set函数。
  2. Set.prototype.size:返回Set实例的成员总数。

Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。

  1. Set.prototype.add(value):添加某个值,返回 Set 结构本身。
  2. Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  3. Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
  4. Set.prototype.clear():清除所有成员,没有返回值。
    Array.from方法可以将 Set 结构转为数组。
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
  • 遍历操作
  1. Set.prototype.keys():返回键名的遍历器
  2. Set.prototype.values():返回键值的遍历器
  3. Set.prototype.entries():返回键值对的遍历器
  4. Set.prototype.forEach():使用回调函数遍历每个成员
  • 遍历应用
    扩展运算符(…)内部使用for…of循环,所以也可以用于 Set 结构
 let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']

扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。

 let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]

数组的map和filter方法也可以间接用于 Set

 let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}

let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}

并集 、交集 、差集

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

2.WeakSet

  • WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。WeakSet 的成员只能是对象,而不能是其他类型的值
  • WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中
  • 任何具有 Iterable 接口的对象,都可以作为 WeakSet 的参数
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]} //是数组成员,不是数组本身
  • 方法
  1. WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
  2. WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
  3. WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
  • WeakSet 没有size属性,没有办法遍历它的成员

3.Map

  • 它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现
  • Map 可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

对同一个键多次赋值,后面的值将覆盖前面的值

const map = new Map();

map
.set(1, 'aaa')
.set(1, 'bbb');

map.get(1) // "bbb"

如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。
-Map 结构的实例有以下属性和操作方法

  1. size属性返回 Map 结构的成员总数
  2. Map.prototype.set(key, value),设置键名key对应的键值为value,然后返回整个 Map 结构
let map = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c'); //返回的是当前的Map对象,因此可以采用链式写法
  1. Map.prototype.get(key)
    读取key对应的键值,如果找不到key,返回undefined
const m = new Map();

const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // 键是函数

m.get(hello)  // Hello ES6!
  1. Map.prototype.has(key)返回一个布尔值,表示某个键是否在当前 Map 对象之中
const m = new Map();

m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');

m.has('edition')     // true
m.has('years')       // false
m.has(262)           // true
m.has(undefined)     // true
  1. Map.prototype.delete(key)删除某个键,返回true。如果删除失败,返回false
const m = new Map();
m.set(undefined, 'nah');
m.has(undefined)     // true

m.delete(undefined)
m.has(undefined)       // false
  1. Map.prototype.clear(),清除所有成员,没有返回值
let map = new Map();
map.set('foo', true);
map.set('bar', false);

map.size // 2
map.clear()
map.size // 0
  • 遍历方法
    Map.prototype.keys():返回键名的遍历器。
    Map.prototype.values():返回键值的遍历器。
    Map.prototype.entries():返回所有成员的遍历器。
    Map.prototype.forEach():遍历 Map 的所有成员。
  • 使用扩展运算符转为数组结构
const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

[...map.keys()]
// [1, 2, 3]

[...map.values()]
// ['one', 'two', 'three']

[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]

[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
  • 结合数组的map方法、filter方法,可以实现 Map 的遍历和过滤(Map 本身没有map和filter方法)
const map0 = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');

const map1 = new Map(
  [...map0].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}

const map2 = new Map(
  [...map0].map(([k, v]) => [k * 2, '_' + v])
    );
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}
  • forEach方法
  • 转换
  1. Map转为数组
[...myMap]
  1. 数组转为Map
new Map([
  [true, 7],
  [{foo: 3}, ['abc']]
])
//
[[Entries]]: Array(2)
0: {true => 7}
key: true
value: 7
1: {Object => Array(1)}
key: {foo: 3}
value: ["abc"]
length: 2
  1. Map 转为对象, Map 的键都是字符串,它可以无损地转为对象
  2. 通过Object.entries()
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
  1. Map 转为 JSON
  2. JSON 转为 Map

4.WeakMap

  • WeakMap结构与Map结构类似,也是用于生成键值对的集合
    WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名
    WeakMap的键名所指向的对象,不计入垃圾回收机制
const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null, 2)
// TypeError: Invalid value used as weak map key
  • 只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用
  • 没有keys()、values()和entries()方法,也没有size属性

十三、Proxy

1.Proxy 对象

var proxy = new Proxy(target, handler);//拦截对象,拦截行为
var proxy = new Proxy({}, {
  get: function(target, propKey) { //配置对象有一个get方法,参数为目标对象和要访问的属性
    return 35;
  }
});

proxy.time // 35 //拦截函数返回值为35,固访问任何属性都返回35
proxy.name // 35
proxy.title // 35
  • 将 Proxy 对象,设置到object.proxy属性,从而可以在object对象上调用
 var object = { proxy: new Proxy(target, handler) };
  • Proxy 实例也可以作为其他对象的原型对象
 var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

let obj = Object.create(proxy);
obj.time // 35

2.Proxy 支持的拦截操作

  1. get(target, propKey, receiver):拦截对象属性的读取,目标对象、属性名和 proxy 实例本身
  2. set(target, propKey, value, receiver):拦截对象属性的设置,目标对象、属性名、属性值和 Proxy 实例本身
  3. has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。目标对象、需查询的属性名
  4. deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
  5. ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  6. getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  7. defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
  8. preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
  9. getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
  10. isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
  11. setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  12. apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
  13. construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)目标对象、构造函数的参数对象、创造实例对象时,new命令作用的构造函数

3.Proxy.revocable()

  • 返回一个可取消的 Proxy 实例
let {proxy, revoke} = Proxy.revocable(target, handler);
//Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例
proxy.foo = 123;
proxy.foo // 123

revoke();//执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。
proxy.foo // TypeError: Revoked

4.this问题

Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。

十四、Reflect

1.概述

  • 从Reflect对象上可以拿到语言内部的方法
  • 修改某些Object方法的返回结果,让其变得更合理。
  • 让Object的命令式操作都变成函数行为
// 老写法
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true
  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

2.静态方法

3观察者模式

const queuedObservers = new Set();

const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});

function set(target, key, value, receiver) {
  const result = Reflect.set(target, key, value, receiver);
  queuedObservers.forEach(observer => observer());
  return result;
}

十五、Promise对象

1.Promise的含义

  • 异步编程,是一个容器,保存未来才会结束的事件,是异步操作
  • 特点
  1. 有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败),只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
  2. 称为 resolved(已定型),从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
  3. 无法取消Promise,一旦新建它就会立即执行,无法中途取消。如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

2.基本用法

  • Promise对象是一个构造函数,用来生成Promise实例
const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){ //将Promise对象的状态从“未完成”变为“成功”
    resolve(value); //将异步操作的结果,作为参数传递出去
  } else { //将Promise对象的状态从“未完成”变为“失败”
    reject(error); //在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
  }
});
//当前脚本同步任务执行完毕后输出
promise.then(function(value) { //参数是回调函数
  // success状态的回调函数
}, function(error) { //可选
  // failure状态的回调函数
});
  • 异步加载图片例子
function loadIamgeAsyny(url){
    return new Promise(function(resolve,reject){
    	const image=new Iamge();
    	image.onload=functiong(){
    	resolve(image);
    	};
    	image.onerror = function() {
             reject(new Error('Could not load image at ' + url));
        };
    	 image.src = url;
	});
}
  • Promise对象实现Ajax
  • resolve参数情况
const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
}) //3s后改变状态

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)  //p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态
}) //1s后改变状态

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail

3.Promise.prototype.then

  • 采用链式的then
getJSON("/post/1.json").then(
  post => getJSON(post.commentURL)
).then(
  comments => console.log("resolved: ", comments),
  err => console.log("rejected: ", err)
);

4.Promise.prototype.catch()

  • getJSON方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 处理 getJSON 和 前一个回调函数运行时发生的错误
  console.log('发生错误!', error);
});
  • 如果 Promise 状态已经变成resolved,再抛出错误是无效的。
const promise = new Promise(function(resolve, reject) {
  resolve('ok');
  throw new Error('test');
});
promise
  .then(function(value) { console.log(value) })
  .catch(function(error) { console.log(error) });
// ok

不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });
  • someAsyncThing函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。

5.Promise.prototype.finally

  • finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
server.listen(port)
  .then(function () {
    // ...
  })
  .finally(server.stop);

6.Promise.all()

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result) //p1会resolved
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');//p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例
})
.then(result => result)
.catch(e => e);
//Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]

7.Promise.race()

  • 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
const p = Promise.race([p1, p2, p3]);

8.Promise.allSettled()

  • 只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束
const promises = [
  fetch('/api-1'),
  fetch('/api-2'),
  fetch('/api-3'),
];
//对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失。
await Promise.allSettled(promises);
removeLoadingIndicator();
//返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected

9.Promise.any()

  • Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。该方法目前是一个第三阶段的提案 。

10.Promise.resolve()

  • 将现有对象转为 Promise 对象
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
  • 参数

(1)参数是一个 Promise 实例

如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
(2)参数是一个thenable对象

//thenable对象指的是具有then方法的对象
let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});

(3)参数不是具有then方法的对象,或根本就不是对象

const p = Promise.resolve('Hello');
//回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数。
p.then(function (s){
  console.log(s)
});
// Hello

(4)不带有任何参数

const p = Promise.resolve();

p.then(function () {
  // ...
});//返回一个resolved状态的 Promise 对象。

11.Promise.reject()

  • 生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
  console.log(s)
});
// 出错了

12.应用

const preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    const image = new Image();
    image.onload  = resolve; 
    image.onerror = reject;//一旦加载完成,Promise的状态就发生变化。
    image.src = path;
  });
};

13.Promise.try()

十六、Iterator和for…of循环

1.Iterator概念

  • Iterator 接口
function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++]} :
        {done: true};
    }
  };
}

2.默认Iterator接口

  • Symbol.iterator属性
const obj = {
  [Symbol.iterator] : function () { //预定义好的,类型为Symbol的特殊值,要放在方括号内
    return {
      next: function () {
        return {
          value: 1,  //当前成员值
          done: true  //表示遍历是否结束
        };
      }
    };
  }
};
  • 原生具备 Iterator 接口的数据结构如下,不用自己写遍历器生成函数,for...of循环会自动遍历它们
    Array
    Map
    Set
    String
    TypedArray
    函数的 arguments 对象
    NodeList 对象
  • 数组的Symbol.iterator属性
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator](); //调用属性
//得到遍历器对象
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
  • 部署生成器方法
class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; } //返回当前函数遍历器对象

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false, value: value};
    }
    return {done: true, value: undefined};
  }
}

function range(start, stop) {
  return new RangeIterator(start, stop);
}

for (var value of range(0, 3)) {
  console.log(value); // 0, 1, 2
}
  • 调用数组的Symbol.iterator方法
    类数组对象调用
let iterable = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
  console.log(item); // 'a', 'b', 'c'
}

3.调用Iterator接口的场合

(1)解构赋值,Symbol.iterator方法
默认调用Symbol.iterator方法

let [first, ...rest] = set;
// first='a'; rest=['b','c'];

(2)yield*

(3)其他场合
for…of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(比如new Map([[‘a’,1],[‘b’,2]]))
Promise.all()
Promise.race()

4.字符串的Iterator 接口

是类数组对象

var someString = "hi";
typeof someString[Symbol.iterator]
// "function"

var iterator = someString[Symbol.iterator]();

iterator.next()  // { value: "h", done: false }
iterator.next()  // { value: "i", done: false }
iterator.next()  // { value: undefined, done: true }

5.Iterator 接口与 Generator 函数

6.遍历器对象的 return(),throw()

7.for…of 循环

  • 数组
const arr = ['red', 'green', 'blue'];

for(let v of arr) {
  console.log(v); // red green blue
}

const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);
//空对象obj部署了数组arr的Symbol.iterator属性
for(let v of obj) {
  console.log(v); // red green blue
}

  • for...infor...of
var arr = ['a', 'b', 'c', 'd'];

for (let a in arr) {
  console.log(a); // 0 1 2 3
} //获取键名

for (let a of arr) {
  console.log(a); // a b c d
}//获取键值
let arr = [3, 5, 7];
arr.foo = 'hello';

for (let i in arr) {
  console.log(i); // "0", "1", "2", "foo"
}

for (let i of arr) {
  console.log(i); //  "3", "5", "7"
}
  • Set 和 Map 结构
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
  console.log(e);
}
// Gecko
// Trident
// Webkit

var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
  console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262
  • 计算生成的数据结构
    entries()
    返回一个遍历器对象,用来遍历[键名, 键值]组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用entries方法。
    keys()
    返回一个遍历器对象,用来遍历所有的键名。
    values()
    返回一个遍历器对象,用来遍历所有的键值。
let arr = ['a', 'b', 'c'];
for (let pair of arr.entries()) {
  console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']
  • 类数组对象
    字符串,会正确识别 32 位 UTF-16 字符。
for (let x of 'a\uD83D\uDC0A') {
  console.log(x);
}
// 'a'
// '\uD83D\uDC0A'

DOM NodeList对象

let paras = document.querySelectorAll("p");

for (let p of paras) {
  p.classList.add("test");
}

arguments对象

function printArgs() {
  for (let x of arguments) {
    console.log(x);
  }
}
printArgs('a', 'b');
// 'a'
// 'b'

使用Array.from方法将其转为数组。

let arrayLike = { length: 2, 0: 'a', 1: 'b' };
for (let x of Array.from(arrayLike)) {
  console.log(x);
} //a,b
  • 对象,for…of结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。但是,这样情况下,for…in循环依然可以用来遍历键名
let es6 = {
  edition: 6,
  committee: "TC39",
  standard: "ECMA-262"
};
for (let e in es6) {
  console.log(e);
}
// edition
// committee
// standard

使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组。

for (var key of Object.keys(someObject)) {
  console.log(key + ': ' + someObject[key]);
}
  • 比较遍历语法
    for循环
for (var index = 0; index < myArray.length; index++) {
  console.log(myArray[index]);
}

数组提供内置的forEach方法

myArray.forEach(function (value) {
  console.log(value);
});

十七、Generator

1.简介

  • 状态机,封装多个内部状态;会返回一个遍历器对象,是一个遍历对象生成函数
function* helloWorldGenerator() {  //关键字与函数名中间有星号,
  yield 'hello'; //用yield表达式,定义不同内部状态
  yield 'world'; //yield表达式是暂停执行的标记
  return 'ending';
}

var hw =helloWorldGenerator();//函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象
hw.next() //只有next时,函数才会执行
// { value: 'hello', done: false }//而next方法可以恢复执行

hw.next()
// { value: 'world', done: false }

hw.next() //没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

yield表达式如果用在另一个表达式之中,必须放在圆括号里面

function* demo() {
  console.log('Hello' + (yield)); // OK
  console.log('Hello' + (yield 123)); // OK
}

yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。

function* demo() {
  foo(yield 'a', yield 'b'); // OK
  let input = yield; // OK
}
  • 与 Iterator 接口的关系
var myIterable = {};
myIterable[Symbol.iterator] = function* () {//Generator 函数赋值给Symbol.iterator属性,从而使得myIterable对象具有了 Iterator 接口,可以被...运算符遍历了
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]
function* gen(){
  // some code
}

var g = gen(); //调用时生成遍历器对象g

g[Symbol.iterator]() === g 
// true

2.next方法的参数

function* f() {
  for(var i = 0; true; i++) {
    var reset = yield i; //表达式本身没有返回值,故reset的值是undefiend
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false } //有参数,即表达式有了返回值,为true

3.for…of 循环

-不需要调用next方法,for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5
  • 斐波那契
function* fibonacci() {
  let [prev, curr] = [0, 1];
  for (;;) {
    yield curr; //产出一个curr
    [prev, curr] = [curr, prev + curr];
  }
}

for (let n of fibonacci()) {
  if (n > 1000) break;
  console.log(n);
}
  • 不具备接口的
    将 Generator 函数加到对象的Symbol.iterator属性上面。
function* objectEntries() {
  let propKeys = Object.keys(this);

  for (let propKey of propKeys) {
    yield [propKey, this[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

jane[Symbol.iterator] = objectEntries;

for (let [key, value] of jane) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
  • 其他可将 Generator 函数返回的 Iterator 对象,作为参数。
function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}

// 扩展运算符
[...numbers()] // [1, 2]

// Array.from 方法
Array.from(numbers()) // [1, 2]

// 解构赋值
let [x, y] = numbers();
x // 1
y // 2

4.Generator.prototype.throw()

  • Generator 函数返回的遍历器对象,都有一个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);
}
//catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。
// 内部捕获 a
// 外部捕获 b
  • 内外部throw
    因为第一次执行next方法,等同于启动执行 Generator 函数的内部代码,否则 Generator 函数还没有开始执行,这时throw方法抛错只可能抛出在函数外部。
    如果 Generator 函数内部和外部,都没有部署try…catch代码块,那么程序将报错,直接中断执行。
var g = function* () {
  while (true) {
    try {
      yield;
    } catch (e) {
      if (e != 'a') throw e;
      console.log('内部捕获', e);
    }
  }
};

var i = g();
i.next(); //不执行next则无法启动内部代码,try catch不会捕获到

try { //若只有throw没有try catch,代码会报错
  throw new Error('a'); //是全局的throw命令,
  throw new Error('b');
} catch (e) { //捕获到错误后就不会继续执行try后面的语句
  console.log('外部捕获', e);
}
// 外部捕获 [Error: a]

throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。

var gen = function* gen(){
  try {
    yield console.log('a');
  } catch (e) {
    // ...
  }
  yield console.log('b');
  yield console.log('c');
}

var g = gen();
g.next() // a
g.throw() // b
g.next() // c

Generator 函数体内抛出的错误,也可以被函数体外的catch捕获。

function* foo() {
  var x = yield 3;
  var y = x.toUpperCase(); //数值是没有toUpperCase方法,抛出 TypeError 错误
  yield y;
}

var it = foo();

it.next(); // { value:3, done:false }

try {
  it.next(42);
} catch (err) {
  console.log(err);
}

5.Generator.prototype.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 } //Generator 函数的遍历终止,无参数返回value属性为undefined
g.next()        // { value: undefined, done: true }

Generator 函数内部有try…finally代码块

function* numbers () {
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }
  yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false } //直接进入finally
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }//最后才返回return指定的

6.next()、throw()、return() 的共同点

7.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
  • yield*表达式
    等同于在 Generator 函数内部,部署一个for…of循环
    有return语句时,则需要用var value = yield* iterator的形式获取return语句的值
function* bar() {
  yield 'x';
  // 手动遍历 foo()
  for (let i of foo()) { //手动
    console.log(i);
  }
  yield 'y';
}
//替换成
function* bar() {
  yield 'x';
  yield* foo(); //等同于在 Generator 函数内部,部署一个for...of循环。
  yield 'y';
}

yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员

function* gen(){
  yield* ["a", "b", "c"];
}

gen().next() // { value:"a", done:false }

任何数据结构只要有 Iterator 接口,就可以被yield*遍历

let read = (function* () {
  yield 'hello';
  yield* 'hello';
})();

read.next().value // "hello"
read.next().value // "h"

8.作为对象属性的Generator函数

  • 简写
let obj = {
  * myGeneratorMethod() {
    ···
  }
};
//等价于
let obj = {
  myGeneratorMethod: function* () {
    // ···
  }
};

9.Generator函数的this

  • Generator 函数总是返回一个遍历器,这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。
function* g() {
  this.a = 11;
}

let obj = g(); //g()返回的是遍历器对象,不是this
obj.next();
obj.a // undefined //拿不到属性

Generator 函数也不能跟new命令一起用,会报错

function* F() {
  yield this.x = 2;
  yield this.y = 3;
}

new F() //他不是构造函数
// TypeError: F is not a constructor
  • 绑定 Generator 函数内部的this的方法
function* F() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}
var obj = {};//成一个空对象,使用call方法绑定 Generator 函数内部的this
var f = F.call(obj);

f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}

obj.a // 1
obj.b // 2
obj.c // 3

obj换成F.prototype

function* F() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}
将F改成构造函数
function F() {
  return gen.call(gen.prototype); 
}

var f = new F();

f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3

10.含义

  • 状态机
var clock = function* () {
  while (true) {
    console.log('Tick!');
    yield;
    console.log('Tock!');
    yield;
  }
};
  • Generator 与协程
  • Generator 与上下文

11.应用

  • 异步操作的同步化表达
function* loadUI() {
  showLoadingScreen();
  yield loadUIDataAsynchronously();
  hideLoadingScreen();
}
var loader = loadUI();//函数不执行,返回遍历器
// 加载UI
loader.next() //开始执行,显示+加载

// 卸载UI
loader.next()
  • 控制流管理
    看书
  • 部署Iterator 接口
    for of 可以自动遍历 yield语句
function* iterEntries(obj) {
  let keys = Object.keys(obj);
  for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
  }
}

let myObj = { foo: 3, bar: 7 };

for (let [key, value] of iterEntries(myObj)) { //for of 可以自动遍历 yield语句
  console.log(key, value);
}

// foo 3
// bar 7
  • 作为数据结构
function* doStuff() {
  yield fs.readFile.bind(null, 'hello.txt');
  yield fs.readFile.bind(null, 'world.txt');
  yield fs.readFile.bind(null, 'and-such.txt');
}
for (task of doStuff()) {
  // task是一个函数,可以像回调函数那样使用它
}

十八、Generator 函数的异步应用

1.传统方法
回调函数
事件监听
发布/订阅
Promise 对象

2.基本概念
看书

十九、async 函数

1.特点

  • async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。functionName();
  • async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
  • async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。用then方法指定下一步的操作

2.用法

3.语法

  • 返回 Promise 对象
async function f() { 
  return 'hello world'; //then方法回调函数的参数
}//返回 Promise 对象

f().then(v => console.log(v))
// "hello world"
async function f() {
  throw new Error('出错了');
}

f().then(
  v => console.log(v),
  e => console.log(e)
)
// Error: 出错了
  • Promise 对象的状态变化
    只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
  • await 命令
    await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值
async function f() {
  // 等同于
  // return 123;
  return await 123;
}

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

await命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象。

class Sleep {
  constructor(timeout) {
    this.timeout = timeout;
  }
  then(resolve, reject) { //then方法
    const startTime = Date.now();
    setTimeout(
      () => resolve(Date.now() - startTime),
      this.timeout
    );
  }
}

(async () => {
  const sleepTime = await new Sleep(1000);
  console.log(sleepTime);
})();
// 1000

任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行

async function f() {
  await Promise.reject('出错了'); //eject的参数会被catch方法的回调函数接收到。
  await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了

不中断执行方法

async function f() {
  try { //第一个await放在try...catch结构里面
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world
async function f() {
  await Promise.reject('出错了') //await后面的 Promise 对象再跟一个catch方法
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world
  • 错误处理
async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出错了'); //async函数返回的 Promise 对象被reject。
  });
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出错了
  • 使用注意点
    多个请求并发执行,Promise.all方法
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

4.async 函数的实现原理

将 Generator 函数和自动执行器,包装在一个函数里

async function fn(args) {
  // ...
}

// 等同于

function fn(args) {
  return spawn(function* () { //spawn函数是自动执行器
    // ...
  });
}

spawn源码

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}

5.与其他异步处理方法的比较

看书

6.实例:按顺序完成异步操作

没懂

7.顶层await

二十、Class 的基本语法

1.类的由来

  • ES5 的构造函数,对应 ES6 的类的构造方法。
class Point {
  constructor() { //类的方法都定义在prototype对象上面
    // ...
  }

  toString() { //类内部定义的方法,不可枚举
    // ...
  }

  toValue() {
    // ...
  }
}

// 等同于

Point.prototype = {
  constructor() {}, 
  toString() {},
  toValue() {},
};

class B {}
let b = new B();

b.constructor === B.prototype.constructor // true
  • Object.assign方法可以很方便地一次向类添加多个方法
class Point {
  constructor(){
    // ...
  }
}

Object.assign(Point.prototype, { //将sourse属性添加到target目标对象上
  toString(){},
  toValue(){}
});
  • constructor
class Point {
} //new的时候,会默认添加construcotr方法

// 等同于
class Point {
  constructor() {} //该方法默认返回实例对象,this
   //也可指定对象  return Object.create(null);
}
  • 取值函数(getter)和存值函数(setter)
    存值函数和取值函数是设置在属性的 Descriptor 对象上的
class MyClass {
  constructor() {
    // ...
  }
  get prop() { 自定义取值函数
    return 'getter';
  }
  set prop(value) { //自定义存值函数
    console.log('setter: '+value);
  }
}

let inst = new MyClass();

inst.prop = 123; //存值
// setter: 123

inst.prop //取值
// 'getter
  • 属性表达式
let methodName = 'getArea';

class Square {
  constructor(length) {
    // ...
  }

  [methodName]() {
    // ...
  }
}
  • Class 表达式
const MyClass = class Me {
  getClassName() {
    return Me.name; //Me只在 Class 的内部可用,指代当前类
  }
};
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
  • 注意点
  1. 严格模式(默认)
  2. 不存在提升
  3. name 属性
class Point {}
Point.name // "Point"
  1. Generator 方法:某个方法之前加上星号(*),就表示该方法是一个 Generator 函数
  2. this的指向
    箭头函数内部的this总是指向定义时所在的对象
this.getThis = () => this;

2.静态方法

静态方法可以与非静态方法重名 static
父类的静态方法,可以被子类继承

class Foo {
  static classMethod() { //该方法不会被实例继承
    return 'hello';
  }
}

Foo.classMethod() // 'hello'  //直接通过类来调用

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

3.实例属性的新写法

//旧写法
class IncreasingCounter {
  constructor() {
    this._count = 0;
  }
  //新写法
class IncreasingCounter {
  _count = 0;

4.静态属性

Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。属性不需要实例化类就可以通过类名直接使用

// 老写法
class Foo {
  // ...
}
Foo.prop = 1;

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

5.私有方法和私有属性

class Widget {
  foo (baz) {
    bar.call(this, baz);
  } //使得bar实际上成为了当前模块的私有方法。

  // ...
}

function bar(baz) {
  return this.snaf = baz;
}
  • 私有属性、方法的提案
    加#
    私有属性也可以设置 getter 和 setter 方法
    当前属性只能在当前类内访问
class Foo {
  #a;
  #b;
  constructor(a, b) {
    this.#a = a;
    this.#b = b;
  }
  #sum() {
    return #a + #b;
  }

6.new.target属性

返回new命令作用于的那个构造函数

function Person(name) {
  if (new.target !== undefined) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}

// 另一种写法
function Person(name) {
  if (new.target === Person) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}

var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三');  // 报错

二十一、Class的继承

1.简介

  • 通过extends关键字实现继承
    super关键字,表示父类的构造函数,用来新建父类的this对象。
    实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y),在子类constructor之中
    this.color = color; //只有super后才可以使用this
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

静态方法也会被继承

2.Object.getPrototypeOf()

从子类获取父类

Object.getPrototypeOf(ColorPoint) === Point

3.super关键字

  • 子类的构造函数必须执行一次super函数
class A {}

class B extends A {
  constructor() {
    super();  //返回的是子类B的实例,即super内部的this指的是B的实例
  }
}
  • super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类
//普通方法
class A {
  p() {
    return 2;
  }
  constructor(){ //a是父类A实例的属性,super.a就引用不到它。
  	this.a=2;
  }
}

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

let b = new B();
b.a //undefined
  • 通过super对某个属性赋值,这时super就是this
class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;  //=this.x=3
    console.log(super.x); // undefined
    console.log(this.x); // 3
  }
}

let b = new B();
  • 子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例
class A {
  constructor() {
    this.x = 1;
  }
  print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  m() {
    super.print(); //super.print.call(this)
  }
}

let b = new B();
b.m() // 2
  • 在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例
class Parent {
  static myMethod(msg) { //静态方法为类所有
    console.log('static', msg);
  }

  myMethod(msg) { //此为实例方法
    console.log('instance', msg);
  }
}

class Child extends Parent {
  static myMethod(msg) {
    super.myMethod(msg);
  }

  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1); // static 1  //this指向Child.不是Child的实例

var child = new Child();
child.myMethod(2); // instance 2
  • 清晰地表明super的数据类型,就不会报错。
class A {}

class B extends A {
  constructor() {
    super();
    console.log(super.valueOf() instanceof B); // true
  }
}

let b = new B();

4.类的prototype 属性和__proto__属性

  • 作为一个对象,子类(B)的原型(__proto__属性)是父类(A);作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例。
class A {
}

class B extends A {
}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
  • 实例的 proto 属性
var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');

p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true

5.原生构造函数的继承

  • 原生构造函数
    Boolean()
    Number()
    String()
    Array()
    Date()
    Function()
    RegExp()
    Error()
    Object()
  • ES6 允许继承原生构造函数定义子类

6.Mixin 模式的实现

  • 多个对象合成一个新的对象,新对象具有各个组成成员的接口
const a = {
  a: 'a'
};
const b = {
  b: 'b'
};
const c = {...a, ...b}; // {a: 'a', b: 'b'}

这块不懂

function mix(...mixins) {
  class Mix {
    constructor() {
      for (let mixin of mixins) {
        copyProperties(this, new mixin()); // 拷贝实例属性
      }
    }
  }

  for (let mixin of mixins) {
    copyProperties(Mix, mixin); // 拷贝静态属性
    copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性
  }

  return Mix;
}

function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if ( key !== 'constructor'
      && key !== 'prototype'
      && key !== 'name'
    ) {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}

二十二、Module的语法

1.概述

import { stat, exists, readFile } from 'fs';

上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载

2.严格模式

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

3.export命令

  • export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
    export 输出变量
// profile.js  独立的用户信息文件 模块
export var firstName = 'Michael'; //使用export关键字输出该变量,使外部能获取
export var lastName = 'Jackson';
export var year = 1958;

另一种写法

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
//可以在脚本尾部,一眼看清楚输出了哪些变量。
export { firstName, lastName, year };

export输出函数或类(class)

export function multiply(x, y) {
  return x * y;
};

as关键字重命名

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};
  • 需要处于模块顶层
function foo() {
  export default 'bar' // SyntaxError
}
foo()

4.import命令

  • 具有提升到整个模块的效果
import { lastName as surname } from './profile.js';
  • 允许在加载模块的脚本里面,改写接口。a的属性可以成功改写
import {a} from './xxx.js'

a = {}; // Syntax Error : 'a' is read-only;
a.foo = 'hello'; // 合法操作

5.模块的整体加载

  • 用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
import * as circle from './circle';

console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));

6.export default 命令

指定模块的默认输出

// export-default.js
export default function foo() {
  console.log('foo');
}
import foo from export-default//不用大括号
// 或者写成

function foo() {
  console.log('foo');
}
export default foo; //本质是将后面的值,赋给default变量
//默认输出是一个函数

7.export与import的复合写法

没看懂、 看书

8.模块的继承

// circleplus.js

export * from 'circle';  //再输出circle模块的所有属性和方法
export var e = 2.71828182846;
export default function(x) {
  return Math.exp(x);
}

9.跨模块常量

// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;

// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3

// test2.js 模块
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3

10.import()

  • import()返回一个 Promise 对象
  • 运行时执行,什么时候运行到这一句,就会加载指定的模块,异步加载
  • 适用场合
    (1)按需加载。
button.addEventListener('click', event => {
  import('./dialogBox.js') //用户点击了按钮,才会加载这个模块
  .then(dialogBox => {
    dialogBox.open();
  })
  .catch(error => {
    /* Error handling */
  })
});

(2)条件加载

if (condition) {
  import('moduleA').then(...);
} else {
  import('moduleB').then(...);
}

(3)动态的模块路径

import(f()) //根据函数f的返回结果,加载不同的模块
.then(...);
  • 注意

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值