es6之变量,扩展,symbol,set,map,proxy,reflect及class

# 概括

ECMAScript 5 (ES5):ECMAScript 的第五版,于2009年标准化,该标准已在所有现代浏览器中完全支持。

ECMAScript 6 (ES6)/ ECMAScript 2015 (ES2015):ECMAscript 第 6 版,2015 年标准化。这个标准已经在大多数现代浏览器中部分实现。相关历史可查看此章节《ES6-ECMAScript6简介》 (opens new window)

用正确的概念来说ES6目前涵盖了ES2015ES2016ES2017ES2018ES2019

ES6更新的内容主要分为以下几点

  • 表达式:声明、解构赋值
  • 内置对象
    • 字符串扩展、数值扩展、对象扩展、数组扩展、函数扩展、正则扩展
    • Symbol、Set、Map、Proxy、Reflect
  • 语句与运算:Class、Module、Iterator
  • 异步编程:Promise、Generator、Async

10-18-43

# let, const

# 定义

  • const命令:声明常量
  • let命令:声明变量

# 作用域

  • 全局作用域

  • 函数作用域function() {}

  • 块级作用域{}

    • 暂时性死区:在代码块内使用let命令声明变量之前,该变量都不可用
  • 作用范围

    • var命令在全局代码中执行
    • const命令let命令只能在代码块中执行
  • 赋值使用

    • const命令声明常量后必须立马赋值
    • let命令声明变量后可立马赋值或使用时赋值
  • 声明方法:varconstletfunctionclassimport

# 比较

  • let添加了块级作用域; 约束了变量提升; 有暂时性死区; 禁止重复声明变量; 不会成为全局对象的属性;
  • const声明的变量不能重新赋值,也是由于这个规则,const变量声明时必须初始化,不能留到以后赋值;
function hoistVariable() {
    var foo;
    console.log('foo:', foo); // foo: undefined
    foo = 3;
}
hoistVariable();
function nonHoistingFunc() {
    console.log('foo:', foo); // Uncaught ReferenceError
    let foo = 3;
    //let foo = 3;
    //console.log('foo:', foo); // 3
}
nonHoistingFunc();

//ES6规定如果块内存在let命令,那么这个块就会成为一个封闭的作用域,并要求let变量先声明才能使用,如果在声明之前就开始使用,它并不会引用外部的变量。
var foo = 3;
if (true) {//暂时性死区
    foo = 5; // Uncaught ReferenceError
    let foo;
}

//使用var可以重复声明变量,但let不允许在相同作用域内重复声明同一个变量,下面的代码会引发错误:
// SyntaxError
function func() {
    let foo = 3;
    var foo = 5;
}
// SyntaxError
function func() {
    let foo = 3;
    let foo = 5;
}
// SyntaxError
function func(arg) {
    let arg;
}

//let不会成为全局对象的属性
//变量会自动成为全局对象的属性(在浏览器和Node.js环境下,这个全局对象分别是window和global),但let是独立存在的变量,不会成为全局对象的属性:
var a = 3;
console.log(window.a); // 3
let b = 5;
console.log(window.b); // undefined

//const示例
const a = 3;
a = 5;   // Uncaught TypeError: Assignment to constant variable
const b; // Uncaught SyntaxError: Missing initializer in const declaration
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 变量、函数提升

# 原因

函数提升就是为了解决相互递归的问题,大体上可以解决像ML语言这样自下而上的顺序问题。 变量提升是人为实现的问题,而函数提升在当初设计时是有目的的

# 变量提升

# 块级作用域

在ES6之前,JavaScript没有块级作用域,只有全局作用域函数作用域 其实从 ES3 发布以来,JavaScript 就有了块级作用域(withcatch分句),而 ES6 中 let, const 会创建块级作用域,不会像 var 声明变量一样会被提升,临时死区(TDZ)

ES6前:js没有块级作用域{}的概念。(有函数作用域、全局作用域、eval作用域) ; var关键字声明变量。无论声明在何处,都会被视为声明在函数的最顶部; ES6后:let和const的出现,js有了块级作用域的概念。 在 ES6 中,letconstvarclassfunction一样也会被提升,只是在进入作用域和被声明之间有一段时间不能访问它们,这段时间是临时死区(TDZ)

ReferenceError: xxx is not defined //TDZ区域;

var foo = 3;
function hoistVariable() {
    var foo = foo || 5;
    console.log(foo); // 5
}
hoistVariable();
//foo || 5这个表达式的结果是5而不是3,虽然外层作用域有个foo变量,但函数内是不会去引用的,因为预编译之后的代码逻辑是这样的:
// 预编译之后
var foo = 3;
function hoistVariable() {
    var foo;
    foo = foo || 5;
    console.log(foo); // 5
}
hoistVariable();

var foo = 'hello';
(function() {
  var foo= foo || 'world';
  console.log(foo);//world
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function hoistVariable() {
    var foo = 3;
    {
        var foo = 5;
    }
    console.log(foo); // 5
}
hoistVariable();
// ES6之前;由于JavaScript没有块作用域,只有全局作用域和函数作用域,所以预编译之后的代码逻辑为:
// 预编译之后
function hoistVariable() {
    var foo;
    foo = 3;
    {
        foo = 5;
    }
    console.log(foo); // 5
}
hoistVariable();

function varTest(params) {
  console.log(aLet); // undefined
  var aLet;
  console.log(aLet); // undefined
  aLet = 10;
  console.log(aLet); // 10
}
function letTest(params) {
  console.log(aLet); // ReferenceError: aLet is not defined //TDZ区域;
  let aLet;
  console.log(aLet); // undefined
  aLet = 10;
  console.log(aLet); // 10
}
varTest()
letTest()
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
27
28
29
30
31
32
33
34
35
36

# 函数提升

函数声明和变量声明都会被提升。但是有一个需要注意的细节是函数会首先被提升,然后才是变量。 此外,在函数作用域中,局部变量的优先级比同名的全局变量高

函数声明会被提升,但是(匿名)函数表达式会被提升(变量形式提升),没有函数的优先级高

如果是两个函数声明,出现在后面的函数声明可以覆盖前面的

函数会首先被提升,然后才是变量

console.log(typeof a === 'function')
var a = 1;
function a() {}
console.log(a == 1);
//会打印 true true

var a = true;
foo();
function foo() {
    if(a) {
        var a = 10;
    }
    console.log(a);
}
//最终的答案是 undefined; 实际会被 JavaScript 执行的样子:
function foo() {
    var a;
    if(a) {
        a = 10;
    }
    console.log(a);
}
var a;
a = true;
foo();
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

函数声明会被提升,但是(匿名)函数表达式会被提升(变量形式提升),没有函数的优先级高

因为JavaScript中的函数是一等公民,函数声明的优先级最高,会被提升至当前作用域最顶端

foo();
function foo() {
	console.log('1');
}
var foo = function() {
	console.log('2');
}
//会输出 1 而不是 2!这个代码片段会被引擎理解为如下形式:
function foo() {
	console.log('1');
}
foo();
foo = function() {//注意这里的调用,在赋值;
	console.log('2');
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function hoistFunction() {
    foo(); // 2  第一次调用
    var foo = function() {
        console.log(1);
    };
    foo(); // 1  第一次调用
    function foo() {
        console.log(2);
    }
    foo(); // 1
}
hoistFunction();
//输出的结果依次是2 1 1 ; 预编译之后
function hoistFunction() {
    var foo;//注意这里的赋值;
    foo = function foo() {
        console.log(2);
    }
    foo(); // 2
    foo = function() {
        console.log(1);
    };
    foo(); // 1
    foo(); // 1
}
hoistFunction();
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 foo = 3;
function hoistFunction() {
    console.log(foo); // function foo() {}
    foo = 5;
    console.log(foo); // 5
    function foo() {}
}
hoistFunction();
console.log(foo); // 3

// 预编译之后
var foo = 3;
function hoistFunction() {
   var foo;
   foo = function foo() {};
   console.log(foo); // function foo() {}
   foo = 5;
   console.log(foo); // 5
}
hoistFunction();
console.log(foo);    // 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 实践案例

具体的做法是:无论变量还是函数,都必须先声明后使用。

值得一提的是,ES6中的class声明也存在提升,不过它和let、const一样,被约束和限制了,其规定,**如果再声明位置之前引用,则是不合法的,会抛出一个异常,直接报错。**var 没有定义时是不会报错,为undefined;

var name = 'samy';
var sayHello = function(guest) {
    console.log(name, 'says hello to', guest);
};
var i;
var guest;
var guests = ['John', 'Tom', 'Jack'];
for (i = 0; i < guests.length; i++) {
    guest = guests[i];
    // do something on guest
    sayHello(guest);
}

//ES6优化后:如果对于新的项目,可以使用let替换var,会变得更可靠,可维护性更高:
let name = 'samy';
let sayHello = function(guest) {
    console.log(name, 'says hello to', guest);
};
let guests = ['John', 'Tom', 'Jack'];
for (let i = 0; i < guests.length; i++) {
    let guest = guests[i];
    // do something on guest
    sayHello(guest);
}
// samy says hello to John
// samy says hello to Tom
// samy says hello to Jack
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
27

# 普通函数与箭头函数的区别

两者最关键的区别就是this指向的区别

普通函数中的this指向函数被调用的对象,因此对于不同的调用者,this的值是不同的

而箭头函数中并没有自己的this(同时,箭头函数中也没有其他的局部变量,如this,argument,super等),所以箭头函数中的this是固定的,它指向定义该函数时所在的对象; 箭头函数中的this始终指向其父级作用域中的this

# 普通函数

  1. 以函数的形式调用(this指向window/global

    在严格模式下,没找到直接调用者,则函数中的this是undefined。

    function fn () {
        console.log(this, 'fn');
        function subFn () {
            console.log(this, 'subFn');
        }
        subFn(); // window
    }
    fn(); // window
    
    1
    2
    3
    4
    5
    6
    7
    8
    function func1(){
      console.log(this === global);//true
    }
    func1()
    const func2 =  function(){
      console.log(this === global);//true
    }
    func2()
    function func3(){
      "use strict"
      console.log(this === global);//false
      console.log(this);//undefined
    }
    func3()
    function func4(){
      console.log("---func4---", this === global);//true
      function subFunc4(params) {
        console.log(this === global);//true
      }
      subFunc4()
    }
    func4()
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
  2. 以方法的形式调用 (this指向调用函数的对象

    var a  = 3;
    var obj = {
        a : 1,
        foo : function(){
            console.log(this.a);
        }
    }
    obj.foo(); //1
    var bar = obj;
    bar.a = 2;
    bar.foo(); //2
    var baz = obj.foo;
    baz(); //undefined
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

上述代码中,出现了三种情况:

  1. 直接通过obj调用其中的方法foo,此时,this就会指向调用foo函数的对象,也就是obj;
  2. 将obj对象赋给一个新的对象bar,此时通过bar调用foo函数,this的值就会指向调用者bar
  3. 将obj.foo赋给一个新对象baz,通过baz()调用foo函数,此时的this指向window

# this的指向

由此我们可以得出结论:普通函数的this总是指向它的直接调用者

  • 普通函数的this总是指向它的直接调用者。
  • 在严格模式下,没找到直接调用者,则函数中的this是undefined。
  • 在默认模式下(非严格模式),没找到直接调用者,则函数中的this指向window。
  • javascript 的this可以简单的认为是后期绑定,没有地方绑定的时候,默认绑定window或undefined。

# “严格”模式

严格模式是在代码中引入更好的错误检查的一种方法。use strict是一种ECMAscript 5 添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行, 提高编译器效率,增加运行速度;为未来新版本的Javascript标准化做铺垫。

  • 当使用严格模式时,不能使用隐式声明的变量,或为只读属性赋值,或向不可扩展的对象添加属性
  • 可以通过在文件,程序或函数的开头添加“use strict”来启用严格模式
function test0(params) {
  var obj = {
    a : 1,
    foo : function(){
        setTimeout(function(){console.log(this.a),3000})
    }
  }
  obj.foo(); //undefined
  //例中setTimeout中的function未被任何对象调用,因此它的this指向还是window对象。
  //希望可以在上例的setTimeout函数中使用this要怎么做呢?
}
//方式一:that
function test1(params) {
  var obj = {
    a : 1,
    foo : function(){
        var that  = this;
        setTimeout(function(){console.log(that.a),3000})
    }
  }
  obj.foo(); //1
}
//方式二:bind
function test2(params) {
  var obj = {
    a : 1,
    foo : function(){
        setTimeout(function(){console.log(this.a),3000}.bind(this))
    }
  }
  obj.foo(); //1
}
//方式三:arrow
function test3(params) {
  var obj = {
    a : 1,
    foo : function(){
        setTimeout(() => {console.log(this.a),3000})
    }
  }
  obj.foo(); //1
}

// test0()
// test1()
// test2()
test3()
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 箭头函数

箭头函数是函数式编程的一种体现,函数式编程将更多的关注点放在输入和输出的关系,省去了过程的一些因素,因此箭头函数中没有自己的this,arguments,new target(ES6)和 super(ES6)。箭头函数相当于匿名函数,因此不能使用new来作为构造函数使用。 箭头函数中的this始终指向定义时其父级作用域中的this。换句话说,箭头函数会捕获其所在的上下文的this值,作为自己的this值。任何方法都改变不了其指向,如call(), bind(), apply()。在箭头函数中调用 this 时,仅仅是简单的沿着作用域链向上寻找,找到最近的一个 this 拿来使用,它与调用时的上下文无关

# 说一下箭头函数This指向问题?

默认指向在定义它时,它所处的对象,而不是执行时的对象,定义它的时候,可能环境是window(即继承父级的this)。

# 箭头函数好处

  • 作用域安全性: 当箭头函数被一致使用时,所有东西都保证使用与根对象相同的thisObject。如果一个标准函数回调与一堆箭头函数混合在一起,那么作用域就有可能变得混乱。
  • 紧凑性: 箭头函数更容易读写。
  • 清晰度: 使用箭头函数可明确知道当前 this 指向。

# 不应该使用箭头函数一些情况

  • 当想要函数被提升时(箭头函数是匿名的)
  • 要在函数中使用this/arguments,由于箭头函数本身不具有this/arguments,因此它们取决于外部上下文; 箭头函数没有 arguments,需要手动使用 ...args 参数代替
  • 使用命名函数(箭头函数是匿名的)
  • 使用函数作为构造函数时(箭头函数没有构造函数); 由于this指向问题,所以:箭头函数不能当作构造函数,不能使用new命令
  • 当想在对象字面是以将函数作为属性添加并在其中使用对象时,因为咱们无法访问 this 即对象本身。

示例:

var obj = {
    a: 10,
    b: () => {
      console.log(this.a); // undefined
      console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
    },
    c: function() {
      console.log(this.a); // 10
      console.log(this); // {a: 10, b: ƒ, c: ƒ}
    },
    d:function(){
        return ()=>{
            console.log(this.a); // 10
        }
    }
  }
  obj.b(); 
  obj.c();
  obj.d()();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

分析一下代码,obj.b()中的this会继承父级上下文中的this值,也就是与obj有相同的this指向,为全局变量window。obj.c()的this指向即为调用者obj,obj.d().()中的this也继承自父级上下文中的this,即d的this指向,也就是obj。

# 解构赋值

# 各个类型的解构

字符串解构const [a, b, c, d, e] = "hello"

数值解构const { toString: s } = 123

布尔值解构const { toString: b } = true

对象解构

  • 形式:const { x, y } = { x: 1, y: 2 }
  • 默认:const { x, y = 2 } = { x: 1 }
  • 改名:const { x, y: z } = { x: 1, y: 2 }

数组解构

  • 规则:数据结构**具有Iterator接口**可采用数组形式的解构赋值
  • 形式:const [x, y] = [1, 2]
  • 默认:const [x, y = 2] = [1]

函数参数解构

  • 数组解构:function Func([x = 0, y = 1]) {}
  • 对象解构:function Func({ x = 0, y = 1 } = {}) {}

# 应用场景

  • 交换变量值:[x, y] = [y, x]
  • 返回函数多个值:const [x, y, z] = Func()
  • 定义函数参数:Func([1, 2])
  • 提取JSON数据:const { name, version } = packageJson
  • 定义函数参数默认值:function Func({ x = 1, y = 2 } = {}) {}
  • 遍历Map结构:for (let [k, v] of Map) {}
  • 输入模块指定属性和方法:const { readFile, writeFile } = require("fs")

# 字符串扩展

扩展方法均可作用于由4个字节储存Unicode字符

Unicode表示法大括号包含表示Unicode字符(\u{0xXX}\u{0XXX})

字符串遍历:可通过for-of遍历字符串

字符串模板:可单行可多行可插入变量的增强版字符串

// es5
var name1 = "bai";
console.log('hello' + name1);
// es6
const name2 = "du";
console.log(`hello ${name2}`);

// es5
var msg = "Hi \
man!";
// es6
const template = `<div>
<span>hello world</span>
</div>`;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

标签模板:函数参数的特殊调用

# 各个方法

String.raw():返回把字符串所有变量替换且对斜杠进行转义的结果

String.fromCodePoint():返回码点对应字符

codePointAt():返回字符对应码点(String.fromCodePoint()的逆操作)

normalize():把字符的不同表示方法统一为同样形式,返回新字符串(Unicode正规化)

repeat()把字符串重复n次,返回新字符串

matchAll():返回正则表达式在字符串的所有匹配

includes()是否存在指定字符串

startsWith():是否存在字符串头部指定字符串

endsWith():是否存在字符串尾部指定字符串

# 数值扩展

二进制表示法0b或0B开头表示二进制(0bXX0BXX)

八进制表示法0o或0O开头表示二进制(0oXX0OXX)

# 各个方法

Number.EPSILON:数值最小精度

Number.MIN_SAFE_INTEGER:最小安全数值(-2^53)

Number.MAX_SAFE_INTEGER:最大安全数值(2^53)

Number.parseInt():返回转换值的整数部分

Number.parseFloat():返回转换值的浮点数部分

Number.isFinite():是否为有限数值

Number.isNaN():是否为NaN

Number.isInteger():是否为整数

Number.isSafeInteger():是否在数值安全范围内

Math.trunc():返回数值整数部分

Math.sign():返回数值类型(正数1负数-1零0)

Math.cbrt():返回数值立方根

Math.clz32():返回数值的32位无符号整数形式

Math.imul():返回两个数值相乘

Math.fround():返回数值的32位单精度浮点数形式

Math.hypot():返回所有数值平方和的平方根

Math.expm1():返回e^n - 1

Math.log1p():返回1 + n的自然对数(Math.log(1 + n))

Math.log10():返回以10为底的n的对数

Math.log2():返回以2为底的n的对数

Math.sinh():返回n的双曲正弦

Math.cosh():返回n的双曲余弦

Math.tanh():返回n的双曲正切

Math.asinh():返回n的反双曲正弦

Math.acosh():返回n的反双曲余弦

Math.atanh():返回n的反双曲正切

# 对象扩展

简洁表示法:直接写入变量和函数作为对象的属性和方法({ prop, method() {} })

属性名表达式:字面量定义对象时使用[]定义键([prop],不能与上同时使用)

//对象初始化简写
function people(name, age) {// ES5
  return {
    name: name,
    age: age
  };
}
function people(name, age) {// ES6
  return {
    name,
    age
  };
}

//对象 字面量简写 (省略 冒号 与 function关键字)
var people1 = {// ES5
  name: 'bai',
  getName: function () {
    console.log(this.name);
  }
};
let people2 = {// ES6
  name: 'bai',
  getName () {
    console.log(this.name);
  }
};
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
27

方法的name属性:返回方法函数名

  • 取值函数(getter)和存值函数(setter):get/set 函数名(属性的描述对象在getset上)
  • bind返回的函数:bound 函数名
  • Function构造函数返回的函数实例:anonymous

属性的可枚举性和遍历:描述对象的enumerable

super关键字:指向当前对象的原型对象(只能用在对象的简写方法中method() {})

Object.is()对比两值是否相等

Object.assign():把任意多个源对象自身可枚举的属性拷贝给目标对象,然后返回目标对象。

​ 合并对象(浅拷贝),返回原对象; 需注意的是目标对象只有一层的时候,是深拷贝

Object.getPrototypeOf():返回对象的原型对象

Object.setPrototypeOf():设置对象的原型对象

__proto__:返回或设置对象的原型对象

// 给对象添加属性
this.seller = Object.assign({}, this.seller, response.data)

const commonRed = {
    // host: 'redis',
    host: conf.redisHost || '127.0.0.1',
    port: conf.redisPort || 6379,
    password: conf.redisPwd || null
  }
  config.redis = {
    client: Object.assign(commonRed, { db: 10 }),
    constant: {
      routeList: `${config.app.name}_routeList`,
      deviceList: `${config.app.name}_deviceList`,
      devSetList: `${config.app.name}_devSetList`
    },
    app: true,
    agent: true
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 属性遍历

描述:自身可继承可枚举非枚举Symbol

遍历

  • for-in:遍历对象自身可继承可枚举属性
  • Object.keys():返回对象自身可枚举属性的键组成的数组
  • Object.getOwnPropertyNames():返回对象自身可继承可枚举非枚举属性的键组成的数组
  • Object.getOwnPropertySymbols():返回对象Symbol属性的键组成的数组
  • Reflect.ownKeys():返回对象自身可继承可枚举非枚举Symbol属性的键组成的数组

规则

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

# 数组扩展

扩展运算符(...):转换数组为用逗号分隔的参数序列([...arr],相当于rest/spread参数的逆运算)

Array.from():转换具有 Iterator接口的数据结构为真正数组,返回新数组

  • 类数组对象:包含length的对象Arguments对象NodeList对象
  • 可遍历对象:StringSet结构Map结构Generator函数

Array.of():转换一组值为真正数组,返回新数组

copyWithin():把指定位置的成员复制到其他位置,返回原数组

find():返回第一个符合条件的成员

findIndex():返回第一个符合条件的成员索引值

fill():根据指定值填充整个数组,返回原数组

keys():返回以索引值为遍历器的对象

values():返回以属性值为遍历器的对象

entries():返回以索引值和属性值为遍历器的对象

数组空位:ES6明确将数组空位转为undefined(空位处理规不一,建议避免出现)

# 扩展应用

  • 克隆数组:const arr = [...arr1]
  • 合并数组:const arr = [...arr1, ...arr2]
  • 拼接数组:arr.push(...arr1)
  • 代替applyMath.max.apply(null, [x, y]) => Math.max(...[x, y])
  • 转换字符串为数组:[..."hello"]
  • 转换类数组对象为数组:[...Arguments, ...NodeList]
  • 转换可遍历对象为数组:[...String, ...Set, ...Map, ...Generator]
  • 与数组解构赋值结合:const [x, ...rest/spread] = [1, 2, 3]
  • 计算Unicode字符长度:Array.from("hello").length => [..."hello"].length
[1,2,3].includes(4) //false
[1,2,3].indexOf(4) //-1 如果存在换回索引
[1, 2, 3].find((item)=>item===3)) //3 如果数组中无值返回undefined
[1, 2, 3].findIndex((item)=>item===3)) //2 如果数组中无值返回-1
1
2
3
4

# 重点难点

  • 使用keys()values()entries()返回的遍历器对象,可用for-of自动遍历或next()手动遍历

# 函数扩展

# 参数默认值

为函数参数指定默认值

形式:function Func(x = 1, y = 2) {}

参数赋值:惰性求值(函数调用后才求值)

参数位置:尾参数

参数作用域:函数作用域

声明方式:默认声明,不能用constlet再次声明

length:返回没有指定默认值的参数个数

与解构赋值默认值结合:function Func({ x = 1, y = 2 } = {}) {}

# 应用

  • 指定某个参数不得省略,省略即抛出错误:function Func(x = throwMissing()) {}
  • 将参数默认值设为undefined,表明此参数可省略:Func(undefined, 1)
// ES5 给函数定义参数默认值
function foo(num) {
  num = num || 200;
  return num;
}

// ES6
function foo(num = 200) {
  return num;
}
1
2
3
4
5
6
7
8
9
10

# rest/spread参数(...)

返回函数多余参数

  • 形式:以数组的形式存在,之后不能再有其他参数
  • 作用:代替Arguments对象
  • length:返回没有指定默认值的参数个数但不包括rest/spread参数

rest arguments(rest剩余参数) : ES6 的 rest 语法提供了一种捷径,其中包括要传递给函数的任意数量的参数。

就像展开语法的逆过程一样,它将数据放入并填充到数组中而不是展开数组,并且它在函数变量以及数组和对象解构分中也经常用到。

//解决了es5复杂的arguments问题
function foo(x, y, ...rest) {
  return ((x + y) * rest.length);
}
foo(1, 2, 'hello', true, 7); // 9 

function addFiveToABunchOfNumbers(...numbers) {
  return numbers.map(x => x + 5);
}
const result = addFiveToABunchOfNumbers(4, 5, 6, 7, 8, 9, 10); 
// [9, 10, 11, 12, 13, 14, 15]
1
2
3
4
5
6
7
8
9
10
11

Spread Operator (展开运算符): ES6 的展开语法在以函数形式进行编码时非常有用,可以轻松地创建数组或对象的副本,而无需求助于Object.create,slice或库函数

//第一个用途:组装数组
let color = ['red', 'yellow'];
let colorful = [...color, 'green', 'blue'];
console.log(colorful); // ["red", "yellow", "green", "blue"]
//第二个用途::获取数组 除了某几项 的其他项
let num = [1, 3, 5, 7, 9];
let [first, second, ...rest] = num;
console.log(rest); // [5, 7, 9]

const person = {
  name: 'samy',
  age: 10,
};
const copyOfTodd = { ...person };  
console.log(copyOfTodd);//{ name: 'samy', age: 10 }

const [a, b, ...rest] = [1, 2, 3, 4]; // a: 1, b: 2, rest: [3, 4]
const { e, f, ...others } = {
  e: 1,
  f: 2,
  g: 3,
  h: 4,
}; // e: 1, f: 2, others: { g: 3, h: 4 }   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 严格模式

“use strict”是Es5中引入的js指令。 使用“use strict”指令的目的是强制执行严格模式下的代码。 在严格模式下,咱们不能在不声明变量的情况下使用变量。 早期版本的js忽略了“use strict”。

在严格条件下运行JS

  • 应用:只要函数参数使用默认值、解构赋值、扩展运算符,那么函数内部就不能显式设定为严格模式

ES6模块自动采用严格模式(不管模块头部是否添加use strict)

使用方法:进入严格模式的标志 'use strict', 语法更加严格,更安全,提高运行速度

# 严格模式和混杂模式

严格模式:浏览器最高标准呈现页面 混杂模式:页面以一种比较宽松的向后兼容的方式显示

# 严格模式的作用

1、全局变量显示声明 2、静态绑定:属性和方法归属的对象,在编译阶段就确定 3、增强的安全措施:①禁止this指向全局对象 ②禁止在函数内部遍历调用栈 4、禁止删除变量,除非创建对象使用configurable=true 5、显示报错:对对象的只读属性,只有getter的对象赋值,对禁止扩展的对象添加新属性都会报错 6、重名错误:对象不能有同名的参数,函数不能有重名的参数 7、禁止八进制表示法 8、禁止arguments赋值,不会追踪参数的变化,禁止使用arguments.callee 9、函数必须声明在顶层 10、新增保留字

# name属性

返回函数的函数名

  • 将匿名函数赋值给变量:空字符串(ES5)、变量名(ES6)
  • 将具名函数赋值给变量:函数名(ES5和ES6)
  • bind返回的函数:bound 函数名(ES5和ES6)
  • Function构造函数返回的函数实例:anonymous(ES5和ES6)

# 箭头函数(=>)

函数简写

  • 无参数:() => {}
  • 单个参数:x => {}
  • 多个参数:(x, y) => {}
  • 解构参数:({x, y}) => {}
  • 嵌套使用:部署管道机制
  • this指向固定化
    • 并非因为内部有绑定this的机制,而是根本没有自己的this,导致内部的this就是外层代码块的this
    • 因为没有this,因此不能用作构造函数

箭头函数误区

  • 函数体内的this定义时所在的对象而不是使用时所在的对象
  • 可让this指向固定化,这种特性很有利于封装回调函数
  • 不可当作构造函数,因此箭头函数不可使用new命令
  • 不可使用yield命令,因此箭头函数不能用作Generator函数
  • 不可使用Arguments对象,此对象在函数体内不存在(可用rest/spread参数代替)
  • 返回对象时必须在对象外面加上括号

# 尾调用优化

只保留内层函数的调用帧

  • 尾调用
    • 定义:某个函数的最后一步是调用另一个函数
    • 形式:function f(x) { return g(x); }
  • 尾递归
    • 定义:函数尾调用自身
    • 作用:只要使用尾递归就不会发生栈溢出,相对节省内存
    • 实现:把所有用到的内部变量改写成函数的参数并使用参数默认值

# 正则扩展

变更RegExp构造函数入参:允许首参数为正则对象,尾参数为正则修饰符(返回的正则表达式会忽略原正则表达式的修饰符)

正则方法调用变更:字符串对象的match()replace()search()split()内部调用转为调用RegExp实例对应的RegExp.prototype[Symbol.方法]

u修饰符:Unicode模式修饰符,正确处理大于 \uFFFF的 Unicode字符

  • 点字符(.)
  • Unicode表示法
  • 量词
  • 预定义模式
  • i修饰符
  • 转义

y修饰符:粘连修饰符,确保匹配必须从剩余的第一个位置开始全局匹配(与g修饰符作用类似)

unicode:是否设置u修饰符

sticky:是否设置y修饰符

flags:正则表达式的修饰符

# 重点难点

  • y修饰符隐含头部匹配标志^
  • 单单一个y修饰符match()只能返回第一个匹配,必须与g修饰符联用才能返回所有匹配

# Symbol

定义:独一无二的值

声明:const set = Symbol(str)

入参:字符串(可选)

# 方法

  • Symbol():创建以参数作为描述的Symbol值(不登记在全局环境)
  • Symbol.for():创建以参数作为描述的Symbol值,如存在此参数则返回原有的Symbol值(先搜索后创建,登记在全局环境)
  • Symbol.keyFor():返回已登记的Symbol值的描述(只能返回Symbol.for()key)
  • Object.getOwnPropertySymbols():返回对象中所有用作属性名的Symbol值的数组

内置

  • Symbol.hasInstance:指向一个内部方法,当其他对象使用instanceof运算符判断是否为此对象的实例时会调用此方法
  • Symbol.isConcatSpreadable:指向一个布尔值,定义对象用于Array.prototype.concat()时是否可展开
  • Symbol.species:指向一个构造函数,当实例对象使用自身构造函数时会调用指定的构造函数
  • Symbol.match:指向一个函数,当实例对象被String.prototype.match()调用时会重新定义match()的行为
  • Symbol.replace:指向一个函数,当实例对象被String.prototype.replace()调用时会重新定义replace()的行为
  • Symbol.search:指向一个函数,当实例对象被String.prototype.search()调用时会重新定义search()的行为
  • Symbol.split:指向一个函数,当实例对象被String.prototype.split()调用时会重新定义split()的行为
  • Symbol.iterator:指向一个默认遍历器方法,当实例对象执行for-of时会调用指定的默认遍历器
  • Symbol.toPrimitive:指向一个函数,当实例对象被转为原始类型的值时会返回此对象对应的原始类型值
  • Symbol.toStringTag:指向一个函数,当实例对象被Object.prototype.toString()调用时其返回值会出现在toString()返回的字符串之中表示对象的类型
  • Symbol.unscopables:指向一个对象,指定使用with时哪些属性会被with环境排除

# 数据类型

  • Undefined
  • Null
  • String
  • Number
  • Boolean
  • Object(包含ArrayFunctionDateRegExpError)
  • Symbol

# 应用场景

  • 唯一化对象属性名:属性名属于Symbol类型,就都是独一无二的,可保证不会与其他属性名产生冲突
  • 消除魔术字符串:在代码中多次出现且与代码形成强耦合的某一个具体的字符串或数值
  • 遍历属性名:无法通过for-infor-ofObject.keys()Object.getOwnPropertyNames()JSON.stringify()返回,只能通过Object.getOwnPropertySymbols返回
  • 启用模块的Singleton模式:调用一个类在任何时候返回同一个实例(windowglobal),使用Symbol.for()来模拟全局的Singleton模式

重点难点

  • Symbol()生成一个原始类型的值不是对象,因此Symbol()前不能使用new命令
  • Symbol()参数表示对当前Symbol值的描述,相同参数的Symbol()返回值不相等
  • Symbol值不能与其他类型的值进行运算
  • Symbol值可通过String()toString()显式转为字符串
  • Symbol值作为对象属性名时,此属性是公开属性,但不是私有属性
  • Symbol值作为对象属性名时,只能用方括号运算符([])读取,不能用点运算符(.)读取
  • Symbol值作为对象属性名时,不会被常规方法遍历得到,可利用此特性为对象定义非私有但又只用于内部的方法

# Set

# Set

定义:类似于数组的数据结构,成员值都是唯一且没有重复的值

声明:const set = new Set(arr)

入参:具有Iterator接口的数据结构

属性

  • constructor:构造函数,返回Set
  • size:返回实例成员总数

方法

  • add():添加值,返回实例; //Map对应的是set()
  • delete():删除值,返回布尔值
  • has():检查值,返回布尔值
  • clear():清除所有成员
  • keys():返回以属性值为遍历器的对象
  • values():返回以属性值为遍历器的对象
  • entries():返回以属性值和属性值为遍历器的对象
  • forEach():使用回调函数遍历每个成员

# 应用场景

  • 去重字符串[...new Set(str)].join("")
  • 去重数组[...new Set(arr)]Array.from(new Set(arr))
  • 集合数组
    • 声明:const a = new Set(arr1)const b = new Set(arr2)
    • 并集new Set([...a, ...b])
    • 交集new Set([...a].filter(v => b.has(v)))
    • 差集new Set([...a].filter(v => !b.has(v)))
  • 映射集合
    • 声明:let set = new Set(arr)
    • 映射:set = new Set([...set].map(v => v * 2))set = new Set(Array.from(set, v => v * 2))

# 重点难点

  • 遍历顺序:插入顺序
  • 没有键只有值,可认为键和值两值相等
  • 添加多个NaN时,只会存在一个NaN
  • 添加相同的对象时,会认为是不同的对象
  • 添加值时不会发生类型转换(5 !== "5")
  • keys()values()的行为完全一致,entries()返回的遍历器同时包括键和值且两值相等

# WeakSet

  • 定义:和Set结构类似,成员值只能是对象
  • 声明:const set = new WeakSet(arr)
  • 入参:具有Iterator接口的数据结构
  • 属性
    • constructor:构造函数,返回WeakSet
  • 方法
    • add():添加值,返回实例
    • delete():删除值,返回布尔值
    • has():检查值,返回布尔值

# 应用场景

  • 储存DOM节点:DOM节点被移除时自动释放此成员,不用担心这些节点从文档移除时会引发内存泄漏
  • 临时存放一组对象或存放跟对象绑定的信息:只要这些对象在外部消失,它在WeakSet结构中的引用就会自动消

# 重点难点

  • 成员都是弱引用垃圾回收机制不考虑WeakSet结构对此成员的引用
  • 成员不适合引用,它会随时消失,因此ES6规定WeakSet结构不可遍历
  • 其他对象不再引用成员时,垃圾回收机制会自动回收此成员所占用的内存,不考虑此成员是否还存在于WeakSet结构

# Map

# Map

定义:类似于对象的数据结构,成员键可以是任何类型的值

声明:const set = new Map(arr)

入参:具有Iterator接口且每个成员都是一个双元素数组的数据结构

属性

  • constructor:构造函数,返回Map
  • size:返回实例成员总数

方法

  • get():返回键值对
  • set():添加键值对,返回实例
  • delete():删除键值对,返回布尔值
  • has():检查键值对,返回布尔值
  • clear():清除所有成员
  • keys():返回以键为遍历器的对象
  • values():返回以值为遍历器的对象
  • entries():返回以键和值为遍历器的对象
  • forEach():使用回调函数遍历每个成员

# 重点难点

  • 遍历顺序:插入顺序
  • 对同一个键多次赋值,后面的值将覆盖前面的值
  • 对同一个对象的引用,被视为一个键
  • 对同样值的两个实例,被视为两个键
  • 键跟内存地址绑定,只要内存地址不一样就视为两个键
  • 添加多个以NaN作为键时,只会存在一个以NaN作为键的值
  • Object结构提供字符串—值的对应,Map结构提供值—值的对应

# WeakMap

WeakMaps 提供了一种从外部扩展对象而不影响垃圾收集的方法。当咱们想要扩展一个对象,但是因为它是封闭的或者来自外部源而不能扩展时,可以应用WeakMap

WeakMap只适用于 ES6 或以上版本。WeakMap是键和值对的集合,其中键必须是对象

WeakMaps的有趣之处在于,它包含了对map内部键的弱引用。弱引用意味着如果对象被销毁,垃圾收集器将从WeakMap中删除整个条目,从而释放内存。

定义:和Map结构类似,成员键只能是对象

声明:const set = new WeakMap(arr)

入参:具有Iterator接口且每个成员都是一个双元素数组的数据结构

# 属性/方法

  • constructor:构造函数,返回WeakMap, 属性;
  • get():返回键值对
  • set():添加键值对,返回实例
  • delete():删除键值对,返回布尔值
  • has():检查键值对,返回布尔值

# 应用场景

  • 储存DOM节点:DOM节点被移除时自动释放此成员键,不用担心这些节点从文档移除时会引发内存泄漏
  • 部署私有属性:内部属性是实例的弱引用,删除实例时它们也随之消失,不会造成内存泄漏

# 重点难点

  • 成员键都是弱引用,垃圾回收机制不考虑WeakMap结构对此成员键的引用
  • 成员键不适合引用,它会随时消失,因此ES6规定WeakMap结构不可遍历
  • 其他对象不再引用成员键时,垃圾回收机制会自动回收此成员所占用的内存,不考虑此成员是否还存在于WeakMap结构
  • 一旦不再需要,成员会自动消失,不用手动删除引用
  • 弱引用的只是键而不是值,值依然是正常引用
  • 即使在外部消除了成员键的引用,内部的成员值依然存在
var map = new WeakMap();
var pavloHero = {
    first: "Pavlo",
    last: "Hero"
};
var gabrielFranco = {
    first: "Gabriel",
    last: "Franco"
};
map.set(pavloHero, "This is Hero");
map.set(gabrielFranco, "This is Franco");
console.log(map.get(pavloHero)); //This is Hero
1
2
3
4
5
6
7
8
9
10
11
12

# Map与WeakMap的比较

WeakMap 允许垃圾收集器执行其回收任务,但Map不允许。对于手动编写的 Map,数组将保留对键对象的引用,以防止被垃圾回收。但在WeakMap中,对键对象的引用被“弱”保留,这意味着在没有其他对象引用的情况下,它们不会阻止垃圾回收。

var map = new Map()
var weakMap = new WeakMap()

var a = {
  x: 12
};
var b = {
  y: 12
};
map.set(a, 1);
weakMap.set(b, 2);
console.log(map);//Map { { x: 12 } => 1 }
console.log(weakMap);//WeakMap { [items unknown] }

console.log(map.get(a));//1
console.log(weakMap.get(b));//2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Proxy

定义:修改某些操作的默认行为

声明:const proxy = new Proxy(target, handler)

入参

  • target:拦截的目标对象
  • handler:定制拦截行为

方法

  • Proxy.revocable():返回可取消的Proxy实例(返回{ proxy, revoke },通过revoke()取消代理)

拦截方式

  • get():拦截对象属性读取
  • set():拦截对象属性设置,返回布尔值
  • has():拦截对象属性检查k in obj,返回布尔值
  • deleteProperty():拦截对象属性删除delete obj[k],返回布尔值
  • defineProperty():拦截对象属性定义Object.defineProperty()Object.defineProperties(),返回布尔值
  • ownKeys():拦截对象属性遍历for-inObject.keys()Object.getOwnPropertyNames()Object.getOwnPropertySymbols(),返回数组
  • getOwnPropertyDescriptor():拦截对象属性描述读取Object.getOwnPropertyDescriptor(),返回对象
  • getPrototypeOf():拦截对象原型读取instanceofObject.getPrototypeOf()Object.prototype.__proto__Object.prototype.isPrototypeOf()Reflect.getPrototypeOf(),返回对象
  • setPrototypeOf():拦截对象原型设置Object.setPrototypeOf(),返回布尔值
  • isExtensible():拦截对象是否可扩展读取Object.isExtensible(),返回布尔值
  • preventExtensions():拦截对象不可扩展设置Object.preventExtensions(),返回布尔值
  • apply():拦截Proxy实例作为函数调用proxy()proxy.apply()proxy.call()
  • construct():拦截Proxy实例作为构造函数调用new proxy()

# 应用场景

  • Proxy.revocable():不允许直接访问对象,必须通过代理访问,一旦访问结束就收回代理权不允许再次访问
  • get():读取未知属性报错、读取数组负数索引的值、封装链式操作、生成DOM嵌套节点
  • set():数据绑定(Vue数据绑定实现原理)、确保属性值设置符合要求、防止内部属性被外部读写
  • has():隐藏内部属性不被发现、排除不符合属性条件的对象
  • deleteProperty():保护内部属性不被删除
  • defineProperty():阻止属性被外部定义
  • ownKeys():保护内部属性不被遍历

# 重点难点

  • 要使Proxy起作用,必须针对实例进行操作,而不是针对目标对象进行操作
  • 没有设置任何拦截时,等同于直接通向原对象
  • 属性被定义为不可读写/扩展/配置/枚举时,使用拦截方法会报错
  • 代理下的目标对象,内部this指向Proxy代理

# Reflect

定义:保持Object方法的默认行为

方法

  • get():返回对象属性
  • set():设置对象属性,返回布尔值
  • has():检查对象属性,返回布尔值
  • deleteProperty():删除对象属性,返回布尔值
  • defineProperty():定义对象属性,返回布尔值
  • ownKeys():遍历对象属性,返回数组(Object.getOwnPropertyNames()+Object.getOwnPropertySymbols())
  • getOwnPropertyDescriptor():返回对象属性描述,返回对象
  • getPrototypeOf():返回对象原型,返回对象
  • setPrototypeOf():设置对象原型,返回布尔值
  • isExtensible():返回对象是否可扩展,返回布尔值
  • preventExtensions():设置对象不可扩展,返回布尔值
  • apply()绑定this后执行指定函数
  • construct():调用构造函数创建实例

# 设计目的

  • Object属于语言内部的方法放到Reflect
  • 将某些Object方法报错情况改成返回false
  • Object操作变成函数行为
  • ProxyReflect相辅相成

# 废弃方法

  • Object.defineProperty() => Reflect.defineProperty()
  • Object.getOwnPropertyDescriptor() => Reflect.getOwnPropertyDescriptor()

# 重点难点

  • Proxy方法Reflect方法一一对应
  • ProxyReflect联合使用,前者负责拦截赋值操作,后者负责完成赋值操作

数据绑定:观察者模式

const observerQueue = new Set();
const observe = fn => observerQueue.add(fn);
const observable = obj => new Proxy(obj, {
    set(tgt, key, val, receiver) {
        const result = Reflect.set(tgt, key, val, receiver);
        observerQueue.forEach(v => v());
        return result;
    }
});

const person = observable({ age: 10, name: "samy" });
const print = () => console.log(`${person.name} is ${person.age} years old`);
observe(print);
person.name = "samy2";
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Class

定义:对一类具有共同特征的事物的抽象(构造函数语法糖)

原理:类本身指向构造函数,所有方法定义在prototype上,可看作构造函数的另一种写法(Class === Class.prototype.constructor)

class Point {
  constructor() {// ...}
  toString() {// ... }
  toValue() {// ...}
}

// 等同于
Point.prototype = {
  constructor() {},
  toString() {},
  toValue() {},
};

class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 方法和关键字

  • constructor():构造函数,new命令生成实例时自动调用
    • constructor内定义的方法和属性是实例对象自己的
    • constructor外定义的方法和属性则是所有实例对象可以共享的
  • extends:继承父类
  • super:新建父类的this
  • static:定义静态属性方法
  • get:取值函数,拦截属性的取值行为
  • set:存值函数,拦截属性的存值行为

# 属性

  • __proto__构造函数的继承(总是指向父类)
  • __proto__.__proto__:子类的原型的原型,即父类的原型(总是指向父类的__proto__)
  • prototype.__proto__属性方法的继承(总是指向父类的prototype)

静态属性:定义类完成后赋值属性,该属性不会被实例继承,只能通过类来调用;

静态方法:使用static定义方法,该方法不会被实例继承,只能通过类来调用(方法中的this指向类,而不是实例);

# 继承【要点】

  • 实质
    • ES5实质:先创造子类实例的this,再将父类的属性方法添加到this上(Parent.apply(this))
    • ES6实质:先将父类实例的属性方法加到this上(调用super()),再用子类构造函数修改this
  • super
    • 作为函数调用:只能在构造函数中调用super(),内部this指向继承的当前子类(super()调用后才可在构造函数中使用this)
    • 作为对象调用:普通方法中指向父类的原型对象,在静态方法中指向父类
    • 子类必须在constructor方法中调用super方法,否则新建实例时会报错。 这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
  • 显示定义:使用constructor() { super(); }定义继承父类,没有书写则显示定义
  • 子类继承父类:子类使用父类的属性方法时,必须在构造函数中调用 super() ,否则得不到父类的this
    • 父类静态属性方法可被子类继承
    • 子类继承父类后,可从super上调用父类静态属性方法

ES5跟ES6实现继承的比较:

function Person(name, age) {
  this.name = name,
  this.age = age
}
Person.prototype.setAge = function () {
  console.log("111")
}

function Student(name, age, price) {
  Person.call(this, name, age)//要点
  this.price = price
  this.setScore = function () { }
}
function object(o) { //== Object.create()
  function F() {}
  F.prototype = o;
  return new F();
}

function inheritPrototype(child,parent){//要点
  // var prototype = object(parent.prototype);
	var pPrototype = Object.create(parent.prototype);//创建对象
	pPrototype.constructor = child;//增强对象
	child.prototype = pPrototype;//指定对象
}
inheritPrototype(Student,Person)
var s1 = new Student('Samy', 20, 15000)// 同样的,Student继承了所有的Person原型对象的属性和方法。目前来说,最完美的继承方法!
console.log(s1 instanceof Student, s1 instanceof Person) // true true
console.log(s1.constructor) //[Function: Student]
console.log(s1)//Student { name: 'Samy', age: 20, price: 15000, setScore: [Function] }
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
27
28
29
30
class Person {
  constructor(name, age) {//调用类的构造方法
    this.name = name
    this.age = age
  }
  showName() {//定义一般的方法
    console.log("调用父类的方法")
    console.log(this.name, this.age);
  }
}
let p1 = new Person('kobe', 39)
console.log(p1)//Person { name: 'kobe', age: 39 }

//定义一个子类
class Student extends Person {
  constructor(name, age, salary) {
    super(name, age)
    this.salary = salary
  }
  showName() { //在子类自身定义方法
    console.log("调用子类的方法")
    console.log(this.name, this.age, this.salary);//wade 38 1000000000
  }
}
let s1 = new Student('wade', 38, 1000000000)
let s2 = new Student('kobe', 40, 3000000000)
console.log(s1.showName === s2.showName)//true
console.log(s1)//Student { name: 'wade', age: 38, salary: 1000000000 }
s1.showName() 
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
27
28
29

# 实例

类相当于实例的原型,所有在类中定义的属性方法都会被实例继承; this关键字:则代表实例对象;

  • 显式指定属性方法:使用this指定到自身上(使用Class.hasOwnProperty()可检测到)
  • 隐式指定属性方法:直接声明定义在对象原型上(使用Class.__proto__.hasOwnProperty()可检测到)

# 表达式

  • 类表达式:const Class = class {}
  • name属性:返回紧跟class后的类名
  • 属性表达式:[prop]
  • Generator方法:* mothod() {}
  • Async方法:async mothod() {}

# this指向

解构实例属性或方法时会报错

  • 绑定this:this.mothod = this.mothod.bind(this)
  • 箭头函数:this.mothod = () => this.mothod()

属性定义位置

  • 定义在构造函数中并使用this指向
  • 定义在类最顶层

new.target:确定构造函数是如何调用

# 原生构造函数

  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function()
  • Date()
  • RegExp()
  • Error()

# 重点难点

  • 在实例上调用方法,实质是调用原型上的方法
  • Object.assign()可方便地一次向类添加多个方法(Object.assign(Class.prototype, { ... }))
  • 类内部所有定义的方法是不可枚举的(non-enumerable)
  • 构造函数默认返回实例对象(this),可指定返回另一个对象
  • 取值函数和存值函数设置在属性的Descriptor对象
  • 类不存在变量提升
  • 利用new.target === Class写出不能独立使用必须继承后才能使用的类
  • 子类继承父类后,this指向子类实例,通过super对某个属性赋值,赋值的属性会变成子类实例的属性
  • 使用super时,必须显式指定是作为函数还是作为对象使用
  • extends不仅可继承类还可继承原生的构造函数

# 私有属性方法

const name = Symbol("name");
const print = Symbol("print");
class Person {
    constructor(age) {
        this[name] = "Bruce";
        this.age = age;
    }
    [print]() {
        console.log(`${this[name]} is ${this.age} years old`);
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 继承混合类

function copyProperties(target, source) {
    for (const key of Reflect.ownKeys(source)) {
        if (key !== "constructor" && key !== "prototype" && key !== "name") {
            const desc = Object.getOwnPropertyDescriptor(source, key);
            Object.defineProperty(target, key, desc);
        }
    }
}
function MixClass(...mixins) {
    class Mix {
        constructor() {
            for (const mixin of mixins) {
                CopyProperties(this, new mixin());
            }
        }
    }
    for (const mixin of mixins) {
        copyProperties(Mix, mixin);
        copyProperties(Mix.prototype, mixin.prototype);
    }
    return Mix;
}
class Student extends MixClass(Person, Kid) {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 参考链接

https://es6.ruanyifeng.com/

https://juejin.im/post/5d9bf530518825427b27639d

上次更新: 2022/04/15, 05:41:26
×