ES6常用知识点梳理

let & const

let

  • let 声明的变量只在let命令所在的代码块内有效。
  • for 循环 let 声明的 i,当前 i 只在本轮循环有效
  • let **不存在变量声明***,变量一定要在声明后使用,否则报错
  • let不可以在相同作用域内重复声明同一个变量

const

  • const用来声明常量,一旦声明,其值就不可改变。这意味着,const一旦声明常量,就必须立即初始化,不能以后赋值。只声明不赋值不报错
  • 对于对象而已,常量存储的是一个地址,指向一个对象。地址不可变,但对象本身是可变的。
  • 想将对象冻结,应该用const p = Object.freeze(obj)

解构赋值

从对象和对象中提取值,来对变量进行赋值,称之为解构(Destructuring)

数组的解构赋值

  • 只要模组数据结构具有 Iterator 接口(可遍历),就可以采用数组形式的解构赋值
  • 如果解构不成功,变量的值就等于undefined
  • 也可以 不完全解构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let [, , c] = [1, 2, 4];
c; // 4

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

let [a, b] = [1];
a; // 1
b; // undefined

let [a, [b], c] = [1, [2, 3], 4];
b; // 2
c; // 4
指定默认值
  • ES6 使用 ===判断一个位置是否有值。当一个数组成员=== undefined,默认值才生效,是null则不会生效。
  • 如果默认值是一个表达式,则该表达式是惰性求值的,在用到时才会求值。
  • 默认值可以引用解构赋值的其他已经声明的变量
1
2
3
4
5
6
7
8
9
let [x = 1] = []; // 默认值
x; // 1

let [x = 1, y = 2, z = 3] = [12, undefined, null]; // null 默认值不会生效
y; // 2
z; // null
let [x = 1, y = x] = [];
x; // 1
y; // 1

对象的解构赋值

  • 变量必须同属性同名,才能取到正确的值;没有同名属性,为 undefined

  • 如果变量名与属性名不同,则必须是以下形式。实际上 对象解构赋值的内部机制,是先找到同名属性,然后再赋值给对应的变量,即(被赋值的是后者,而非前者)

    1
    2
    3
    4
    5
    6
    7
    let { a: b } = { a: "aaa", b: "bbbb" };
    b; // 'aaa'
    a; // error: a is not defined

    let { a, b } = { a: "aaaa", b: "bbbb" }; // {a:a, b:b} = {a:'aaaa', b:'bbbbb'}
    a; // 'aaaa'
    b; // 'bbbb'
指定默认值
  • 默认值生效的条件,对象的属性严格等于 undefined
  • 如果解构失败,变量的值等于 undefined
1
2
3
let { x = 3, y = 4 } = { x: undefined };
x; // 3
y; // 4

字符串的解构赋值

字符串的解构赋值,此时字符串被转换成一个类数组对象,有length属性

1
2
3
4
let [a, b, c] = "abc";
a; // a
let { length: len } = "abc";
len; // 3

布尔值、数值的解构赋值

解构赋值的规则: 只要等号右边不是对象,就将其转换为对象。布尔值,数值被转换为对应的包装对象

1
2
3
4
let { toString: s } = 123;
s.name; // 'toSting'
let { valueOf: v } = true;
v.name; // 'valueOf'

函数参数的解构赋值

  • 参数也可以解构赋值,通过解构得到各变量的值

    1
    2
    3
    4
    5
    // 函数参数 不是 一个数值,而是通过解构得到的变量 x, y
    function add([x, y]) {
    return x + y;
    }
    add([1, 2]);
函数参数的 默认值
函数参数是对象, 2 种写法,不同结果
  1. 为 x 和 y 指定默认值

    1
    2
    3
    4
    5
    6
    7
    function fn({ x = 0, y = 0 } = {}) {
    return [x, y];
    }
    fn({ x: 3, y: 8 }); // [3, 8]
    fn({ x: 3 }); // [3, 0]
    fn({}); // [0,0]
    fn(); // [0, 0]
  1. 为 参数 指定 默认值,而非为变量 x、y。

    1
    2
    3
    4
    5
    6
    7
    function fn({ x, y } = { x: 0, y: 0 }) {
    return [x, y];
    }
    console.log(fn({ x: 3, y: 8 })); // [3, 8]
    console.log(fn({ x: 3 })); // [3, undefined]
    console.log(fn({})); // [undefined,undefined]
    console.log(fn()); // [0, 0]

数组的扩展

  1. Array.from(): 将 类数组对象(NodeList,arguments 对象)可遍历对象(Set, Map, String)转换为数组。例外 扩展运算符(...)也可以将某些数据结构转换为数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // NodeList
    let links = document.querySelectorAll("a");
    let arrayLinks = Array.from(as);

    function fn() {
    var args = [...arguments];
    }

    // ES 5 方法
    let arr = [].slice.call(arguments);

    Array.from()还可接受第二各个参数,作用类似数组的 map 方法,用来对每个数组元素进行处理。

    1
    2
    3
    4
    5
    6
    7
    8
    Array.from([1, 2, 3], (x) => x + 1); // [2, 3, 4]

    // 取出DOM节点内容
    let spans = document.querySelectorAll("span");
    // es5
    Array.prototype.map.call(spans, (s) => s.textContent);
    // es6
    Array.form(spans, (s) => s.textContent);
  1. Array.of(),总是返回参数值组成的数组,没有参数,返回空数组

  2. find(callback),findIndex(callback), 参数是一个回调函数。比indexOf方法的优势:都可以发现NaN

    1
    2
    [NaN].indexOf(NaN); // -1
    [1, 2, NaN].findIndex((s) => Object.is(s, NaN)); // 2
  3. fill(value[, start[, end]]),使用给定值填充数组,数组中已有的元素会被抹去。

  4. entires(),keys(),values(),都会返回一个遍历器对象,可用于for...of循环遍历

  5. includes(valueToFind[, fromIndex]),返回一个布尔值,表示数组是否包含给定的值

函数的扩展

  1. 函数参数可设置默认值

  2. 非尾部的参数设置默认值,实际上这个参数是无法省略的。通常情况下,定义了默认值的参数应该是函数的为参数

  3. 函数的length属性是预期传入的参数个数,不包含指定了默认值的参数

  4. rest参数fn(a, b, ...rest),用于获取函数的多余参数,其变量是一个数组。rest 参数之后不能再有其他参数,否则报错函数的length属性不包括 rest 参数

  5. 扩展运算符(...),将一个数组转换为逗号分隔的参数序列。只要具有 Iterator 接口的对象,都可以使用

    1
    2
    3
    Math.max(...[1, 2, 3]); // es6
    Math.max(1, 2, 3); // es5
    [..."abcd"]; // ['a','b','c','d']

// Generator 函数返回 一个遍历器对象
let go = function*() {
yield 1;
yield 2;
yield 3;
}
[…go()] // [1,2,3]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

##### 箭头函数

- 箭头函数不会创建自己的 this,它只会从自己的**作用域链**的上一层继承 this
- `arguments`在箭头函数内部是不存在的,它指向**外层函数的对应变量**。可使用`rest`参数代替
- 由于箭头函数没有自己的 this,所以不可以当构造函数,也不能用`call()`,`apply()`,`bind()`方法取改变 this 的值

```js
function foo(e) {
return (d) => {
return (c) => {
return (a, b) => {
console.log("id: ", this.id);
console.log("arguments: ", arguments);
};
};
};
}
foo.call({ id: 123 }, "e")("d")("c")("a", "b");
// id: 123
// arguments: ['e']
尾调用优化

函数调用会在内存中形成一个”调用记录”,即调用帧(call frame),保存着调用位置和内部变量等信息。例如,在函数 A 中调用函数 B,则在 A 的调用帧上方,形成 B 的调用帧,等到函数 B 运行结束,将结果返回到 A,B 的调用帧才移除。如果函数 B 内还调用了函数 C,那就还有一个 C 的调用帧,在 B 上。依此类推,所以的调用帧就形成了一个调用栈(call stack)

尾调用由于是函数的最后一步操作,所以不在需要保留外层函数的调用帧,因为调用位置、内部变量等信息不会再用到了,所以可以直接将内层函数的调用帧取代外层函数

1
2
3
4
5
6
7
let g = (m, n) => m + n;
function f() {
let m = 1;
let n = 2;
return g(m, n);
}
f();

如果函数 g 不是尾调用,函数 f 就要保存内部的变量 m 和 n 的值,g 的调用位置等信息。但是由于调用 g 之后,函数 f 就结束了,所以执行到最后一步,完全可以删除 f()的调用帧,只保留 g(3)的调用帧。如下动图

tailCall

尾递归

函数调用自身称为递归。递归因为要同时保存成百上千个调用帧,非常消耗内存,且很容易发生栈溢出(stack overflow)。但是,如果尾调用自身(尾递归)的话,只需保存一个调用帧,则永远不会发生栈溢出错误

1
2
3
4
5
6
7
8
function factorial(n) {
if (n === 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
factorial(5);

上述阶乘函数,最多需要保存 n 个调用帧,O(n)。如果改为 尾递归的话,则需保存 一个调用帧,,O(1)

1
2
3
4
5
6
7
8
9
function factorial2(n, total) {
"use strict";
if (n === 1) {
return total;
} else {
return factorial2(n - 1, n * total);
}
}
factorial2(5, 1);
柯里化

实现尾递归需要改写递归函数,就是要把所有用的的内部变量改写成函数的参数。但是这样做后,函数的作用就不太直观了,很难理解,计算 5 的阶乘,为什么要传参数 5 和 1。

柯里化是函数编程中一个概念,将多个参数的函数转换成单个参数的形式

1
2
3
4
5
6
7
function currying(fn, n) {
return function (m) {
return fn.call(this, m, n);
};
}
let factorial3 = currying(factorial2, 1);
factorial3(5);

对象的扩展

  • ES6 允许在对象中只写属性名,不写属性名,此时属性值等于属性名所代表的变量

    1
    2
    3
    4
    5
    let person = {
    name: "jiang",
    age, // 等同于 age:age
    hello() {}, // 等同于 hello: function() {}
    };
  • 在用字面量定义对象时,可使用表达式作为对象的属性名、方法名

    1
    2
    3
    4
    let person = {
    ["n" + "ame"]: "jiang",
    ["h" + "ello"]() {}, // 等同于 hello: function() {}
    };
  • Object.is(value1, vaule2),比较两个值是否严格相等,除两点不同之外。

    • +0不等于-0
    • NaN等于自身
  • Object.assign(target, ...sources),将所有可枚举的属性的值 从一个或多个源对象 复制到目标对象。返回目标对象。

    • 如果有同名属性,则后面的属性会覆盖前面的属性

    • 只复制自身可枚举的属性,不可枚举的属性、继承的属性不会被复制

    • 可用于处理数组,但会将其视为对象

      1
      2
      Object.assign([1, 2, 3, 4, 5], [7, 8]);
      // [7,8,3,4,5]
属性的描述对象

Object.getOwnPropertyDescriptor(obj, prop)获取对象上一个自有属性的描述对象

1
2
3
4
5
6
7
let obj = {
a: 123,
b: 345,
};
let descriptor = Object.getOwnPropertyDescriptor(obj, "a");
console.log(descriptor);
// {value: 123, writable: true, enumerable: true, configurable: true}
属性的遍历

ES6 可遍历对象的属性的方法

  1. for in 循环遍历对象的 自身的 和 继承的可枚举 属性
  2. Object.keys(obj), 返回一个数组,包含 对象自身的可枚举属性
  3. Object.getOwnPropertyNames(obj),返回一个数组,包含 对象自身所有属性(包括不可枚举的属性)
  4. Object.getOwnPrropertySymbols(obj),返回一个数组,包含 对象自身的所有 Symbol 属性
__proto__属性 & Object.setPrototypeOf() & Object.getPrototypeOf() & Object.create()
  • __proto__属性是浏览器内,用来读取和设置当前对象的 原型(prototype)对象。因为只要浏览器内部署该属性,其他环境不一定支持,可使用Object.create() (生成 原型对象),Object.getPrototypeOf() (读取 原型对象)Object.setPrototypeOf() (设置 原型对象)3 个方法替代。在实际上,__proto__调用的是object.prototype.__proto__

  • Object.create(prototype[, propertiesObject]),使用指定的原型对象 prototype 及其属性 propertiesObject 去创建一个新的对象。具体 3 个步骤:创建一个新对象,设置新对象的原型对象,为新对象扩展新属性。

    1
    let obj = Object.create(Object.prototype); // 等同于  let obj = {}  创建要给空对象,空对象的原型是 Object.prototype (即 obj.__proto__ === Object.prototype)
  • Object.getPrototypeOf(obj),返回指定对象的原型对象 (内部 [[Prototype]]属性的值

    1
    Object.prototype === Object.getPrototypeOf({}); // true
  • Object.setPrototypeOf(obj, prototype),设置指定对象的原型 为 另一个对象或 null.

    1
    2
    3
    4
    let obj = { a: 1 };
    console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
    Object.setPrototypeOf(obj, null);
    console.log(Object.getPrototypeOf(obj) === null); // true

字符串的扩展

  • includes(searchString[, fromIndex]),一个字符串是否包含另一个字符串,返回 Boolean。

  • startWith()endWith(),判断一个字符串是否在另一个字符头部或尾部

  • repeat(count),返回一个重复count的 新字符串

  • padStart(len[, padString]),padStart(len[, padString]),字符串的补全功能。padString默认为 空格" "

    1
    2
    "abcd", padEnd(5); // 'abcd '
    "abc".padStart(5, "01234"); // '01abc'

Iterator & for … of 循环

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

要想成为可迭代的对象,该对象(或其原型链中的任意对象)必须有实现Iterator接口的方法,通常使用常量Symbol.iterator访问该属性

内置可迭代对象

String,Array,Map,Set以及 类数组的对象都是内置可迭代对象,因为它们的原型对象都有一个Symbol.iterator方法。 返回一个符合有 next()的对象 具体内容

Set & Map

Set

  • Set 类似于数组,但其成员的值是唯一的,无论是原始值或是对象引用。

  • Set 本身就是一个构造函数,可接受一个可迭代对象作为参数,可迭代对象中所有元素将不重复地被添加到新的 Set 实例中。

  • 向 Set 添加值时,不会发生类型转换。Set 内部判断两个值是否相等,使用类似于全等运算符(===),NaN 之间视为相同的值

Set 实例 - 操作方法
  • add(value): 末尾添加某个值,返回 Set
  • delete(value): 移除 Set 中与参数相等的成员,返回一个布尔值。
  • has(value): 返回一个布尔值,表示参数是否为 Set 的成员
  • clear(): 移除 Set 中所有成员
Set 实例 - 遍历方法
  • keys(): 返回一个 键名 的迭代器
  • values(): 返回一个 键值 的迭代器
  • entries(): 返回一个 键值对 的迭代器
  • forEach(): 使用回调函数遍历每个成员,按照插入顺序
Set - 使用场景
  • 去重

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    var arr = [2, 4, 3, 3, 4, 2, 3, 254, 234, 4];
    // ES6 使用Set 去重
    function unique3(arr) {
    return [...new Set(arr)];
    }

    // ES5 过滤掉 下标不同 元素
    function unique(arr) {
    return arr.filter((item, index, self) => {
    return self.indexOf(item) == index;
    });
    }

    // ES5 使用 reduce方法,遍历数组,只要不在新数组中的元素 才 添加
    function unique2(arr) {
    return arr.reduce((pre, cur) => {
    if (!pre.includes(cur)) {
    pre.push(cur);
    }
    return pre;
    }, []);
    }

    console.log(unique(arr));
    console.log(unique2(arr));
    console.log(unique3(arr));
  • 并集

    1
    2
    3
    4
    function union(set1, set2) {
    return new Set([...set1, ...set2]);
    }
    console.log(union(new Set([1, 2, 3]), new Set([2, 3, 4, 5]))); // [1,2,3,4,5]
  • 交集

    1
    2
    3
    4
    function intersect(set1, set2) {
    return new Set([...set1].filter((x) => set2.has(x)));
    }
    console.log(intersect(new Set([1, 2, 3]), new Set([2, 3, 4, 5]))); // [2,3]
  • 差集

    1
    2
    3
    4
    function difference(set1, set2) {
    return new Set([...set1].filter((x) => !set2.has(x)));
    }
    console.log(difference(new Set([1, 2, 3]), new Set([2, 3, 4, 5]))); // [1]

Map

在 JavaScript 的对象本质上是键值对的集合(Hash 结构),但是只能 String、Symbol 类型作为键,所以使用起来有很大限制。

Map 作为构造函数,可接受要给数组或其他iterable对象。例如 new Map([['name','lzj'],['age', 25]])

Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。键的比较是基于 sameValueZero算法, NaNNaN相等

ES6 新增的 Map 结构,也类似于对象,是键值对的集合,但是键的类型没有限制,可以是任何类型的值,包括对象

Map 和 Object 的比较
Map Object
键的类型 任意值,包括函数、对象或任何基本类型 String 或 Symbol
键的顺序 Map 中的 key 是有序的。因此,迭代时,以插入的顺序返回 无序的
Size Map 键值对个数可以通过size属性获取 需手动计算
迭代 Map 是 iterable(可迭代的),所以可直接被迭代 需手动获取它的键,然后迭代
性能 在 频繁 增、删键值对的场景下 表现更好 未作优化
Map 实例 - 操作方法
  • set(key, value): 设置 Map 对象中键的值,返回该 Map 对象。如果 key 已经有值,则键值被更新,否则生成新的键。
  • get(key): 读取 key 对应的键值,如果不存在,返回 undefined
  • has(key): 返回一个布尔值,表示 Map 对象是否包含该键
Map 实例 - 遍历方法
  • entries(): 返回所有成员的迭代器,[key, vlaue]数组
  • keys(): 返回键名的迭代器
  • values(): 返回值的迭代器
  • forEach(): 使用回调函数遍历每个成员
Map 与 其他数据结构的转换
  • Map & Object (所有键都是字符串的对象)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function strMapToObject(strMap) {
    let obj = Object.create(null); // 原型为null的 空对象
    strMap.forEach((v, k) => {
    obj[k] = v;
    });
    return obj;
    }

    function objectToStrMap(obj) {
    let strMap = new Map();
    for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
    }
    return strMap;
    }

    console.log(strMapToObject(myMap));
    console.log(objectToStrMap({ name: "jiang", age: 25 }));
  • Map & Array

    1
    2
    3
    4
    5
    6
    7
    8
    // 数组作为参数传入Map构造函数
    let myMap = new Map([
    ["name", "lzj"],
    ["age", 25],
    ]);

    // 使用扩展运算符
    let myArray = [...myMap];
  • Map(键都是字符串) & JSON

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function strMapToObject(strMap) {
    let obj = Object.create(null); // 原型为null的 空对象
    strMap.forEach((v, k) => {
    obj[k] = v;
    });
    return obj;
    }

    function objectToStrMap(obj) {
    let strMap = new Map();
    for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
    }
    return strMap;
    }

    // Map 转 JSON, Map 键都是字符串
    function strMapToJSON(strMap) {
    return JSON.stringify(strMapToObject(strMap));
    }

    // JSON 转 Map
    function jsonToStrMap(jsonStr) {
    return objectToStrMap(JSON.parse(jsonStr));
    }
    console.log(jsonToStrMap('{"name":"lzj","age":25}'));

Promise

Promise,是一个对象。用来传递异步操作的消息。它代表了 某个未来才知道结果的事件,并为这个事件提供了统一的 API,可进一步处理该事件。

用法

通过 Promise 构造函数生成 Promise 实例,实例生成后,可以用then方法指定 Resolved状态Rejected状态的回调函数。

1
2
3
4
5
6
7
8
9
10
new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "done!");
}).then(
(data) => {
console.log("Resolved>>", data);
},
(error) => {
console.log("Rejected>>", error);
}
);

如果调用 resolve 函数和 reject 函数时带有参数,那么这些参数就会被传递给回调函数。

  • reject 函数的参数通常 是 Error 对象的实例,表示抛出错误。

  • resolve 函数的参数除了正常想传递的值外,还可以是另一个 Promise 实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
    reject(new Error("fail"));
    }, 3000);
    });
    let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve(p1);
    }, 1000);
    });
    p2.then(
    (resolve) => console.log(resolve),
    (error) => console.log(error)
    );
    // 3S 后
    // Error: fail

    p1 是一个 Promise,3s 后状态改为 Rejected。p2 的状态由 p1 决定,1s 后,p2 调用resolve(p1),此时 p1 的状态还没发生改变,因此 p2 状态也不会变,又过了 2s,p1 变为 Rejectd,p2 也跟着变为 Rejected

Promise.all(iterable)

Promise.all 方法将多个 Promise 实例包装成一个 Promise 实例。例如let p = Promise.all[p1,p2,p3]。 p 的状态由 p1, p2, p3 决定,2 中情况:

  1. 只有 p1, p2, p3 的状态都变成 Resolved, p 的状态才变成 Resolved,此时 p1,p2,p3 的返回值组成一个数组,传递给 p 的回调函数。
  2. 只要 p1,p2,p3 中任意一个的状态变成 Rejected, p 的状态就变成 Rejected,此时第一个被 Rejected 的实例的返回值会传递给 p 的回调函数。
Promise.race(iterable)

Promise.race 方法同样是将多个 Promise 实例包装成一个新的 Promise 实例。例如 let p = Promise.race([p1,p2,p3]),p1,p2,p3 之间是竞争关系,z 只要有一个实例的状态率先发生改变,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。

Class

ES6 中的 Class 可以看作是一个语法糖,它只 ES5 的构造函数的一层包装,新的 class 写法只是让对象原型的写法更加清晰,更像面向对象的语法罢了。

  • “类”的数据类型就是函数,”类”本身就指向构造函数

  • “类”的所有方法都定义在“类”的 prototype 属性上,同 ES5 构造函数的 prototype 属性一致。

  • 在“类”的实例上调用方法,其实就是调用原型上的方法

  • “类”内定义的所有方法都是不可枚举的,此点同 ES5 不一致。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class B {
    constructor(x, y) {
    this.x = x;
    this.y = y;
    }
    toString() {
    return `${this.x}, ${this.y}`;
    }
    }
    let b = new B();
    typeof B; // "function"
    B.prototype.constructor === B; // true
    b.constructor === B.prototype.constructor; // true

    Object.keys(B.prototype); // []
    Object.getOwnPropertyNames(B.prototype)[("constructor", "toString")];
  • 一个类必须有 constructor 方法,如果没有显示声明,则默认添加一个空的 constructor 方法。constructor 方法默认返回实例对象(即 this),也可以指定返回另外一个对象

  • 类不存在变量提升

  • 类内部默认就是严格模式

Class 的 继承

Class 之间通过extends关键字实现继承。

  • 子类必须在 constructor 方法中调用super方法,否则新建实例时报错。只是因为:子类没有自己家的 this 的对象,而是继承了父类的 this 对象,然后再对其修改
  • 只要调用 super 方法才能返回父类的实例(this),之后子类才可以使用 this,所有必须要调用 super 方法,且最后放于子类构造函数首行,之后就可以使用 this
  • 子类中 super关键字代表 父类实例
类的 prototype 属性 和 __proto__属性

ES5 多数浏览器中,每个对象都有__proto__属性,指向对应的构造函数的 prototype 属性。Class 作为构造函数的语法糖,同时具有__proto__prototype属性,因此同时存在两条继承链

  1. 子类的__proto__属性表示构造函数的继承,总指向父类
  2. 子类prototype属性的__proto__属性表示方法的继承,总指向父类的prototype属性. 这是因为类的继承模式如下:
1
2
3
4
5
6
7
class A {}
class B extends A {}
// 实现模式
Object.setPrototypeOf(B.prototype, A.prototype); // 等同于 B.prototype.__proto__ === A.prototype
Object.setPrototypeOf(B, A); // 等同于 B.__proto__
B.__proto__ === A; // true
B.prototype.__proto__ === A.prototype; // true