读《ES6标准入门》笔记

《ES6 标准入门》笔记心得

第1章 ECMAScript 6 简介

ES6被认为是 下一代 javasciprt 的标准。

第2章 let 和 const 命令

let:

  1. 不存在变量提升
  2. 暂时性死区
  3. 不允许重复声明

const:

本质:变量指向的内存地址不得改动。

块级作用域:

块级作用域内声明函数的行为类似于var 声明。

function f() { console.log('I am outside!') }
(function() {
	if(false) {
		function f() { console.log('I am inside!'); }
	}
	f();
}());

顶层对象的属性:

ES5中,顶层对象的属性与全局变量是等价的。

ES6中,var 命令和 function 命令声明的全局变量依旧是顶层对象的属性;而 let 命令、const 命令、class 命令声明的全局变量不属于顶层对象的属性。

第3章 变量的解构赋值

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

只要某种数据结构具有 Iterator接口,都可以采用数组形式的解构赋值。

第4章 字符串的扩展

includes() startsWith() endsWith()

padStart() padEnd()

模板字符串 ``

第5章 正则的扩展

新增 u 修饰符,含义为 Unicode 模式;用于正确处理 UTF-16 编码

新增 y 修饰符,叫作“粘粘”修饰符

具名组匹配

正则表达式的分组

第6章 数值的扩展

Number.isInteger()

javascipt 能准确表示的整数范围在-2^53 到 2^53 之间(不含两个端点),超过这个范围就无法精确表示

Math 对象的扩展

Math.trunc():除去小数部分
Math.sign():判断正负零
Math.cbrt():计算立方根
Math.log10():log10(x)
Math.log2():log2(x)

指数运算符: 2 ** 3 === 8

第7章 函数的扩展

函数参数默认值

只有当传入的参数 强等于 undefined,才会触发默认值。

与解构赋值默认值结合使用

// 写法一
function m1({x = 0,y = 0} = {}) {
    return [x,y];
}

// 写法二
function m2({x, y} = {x: 0, y: 0}) {
    return [x,y];
}

通常情况下,默认值的参数应该是函数的尾参数。如果非尾参数的参数设置默认值,实际上这个参数无法省略。

function f(x = 1,y) {
	return [x,y];
}

f(); // [1,undefined]
f(2); // [2,undefined]
f(,1); // 报错
f(undefined,1);// [undefined,1]

默认参数作用域

参数的默认值不是在定义时执行,而是在运行时执行。

rest参数

*箭头函数

注意事项:

  1. this对象为定义时的对象,而不是使用时所在的对象
  2. 不可用作构造函数,即不可new(因为它根本没有this)
  3. 不可使用 arguments 对象。一定要用的话,可以用 rest 参数代替
  4. 不可用yield 命令,不可用作 Generator 函数
  5. 由于没有this,也就不能用call()、apply()、bind()

尾调用

某个函数的最后一步是调用另一个函数

尾递归

尾调用自身。可是由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

// 阶乘函数
function factorial(n) {
    if(n === 1) return 1;
    return n * factorial(n - 1);
}

// 改造为 尾递归
function factorial(n,total) {
    if(n === 1) return total;
    return factorial(n - 1,n * total);
}

函数柯里化

将多参数的函数转换成单参数的形式。

第8章 数组的扩展

扩展运算符 (spread) …

找出数组中的最大元素

Math.max(...[14,3,77]);

// 等同于
Math.max(14,3,77);

扩展运算符的应用

  1. 合并数组

    // es5
    arr1.concat(arr2,arr3);
    
    // es6
    [...arr1,...arr2,...arr3];
    
  2. 与解构赋值结合,生成数组

    如果扩展运算符用于数组赋值,则只能将其放在参数的最后一位,否则报错。

  3. 函数的返回值

  4. 将字符串转换成数组

    // es5
    const arr = str.split('');
    // es6
    [...'hello'];
    // ['h','e','l','l','o'];
    
  5. 实现了 Iterator 接口的对象都可以用扩展运算符转为真正的数组

  6. Map和Set结构,Generator函数 也可以使用扩展运算符

Array.from()

Array.from()方法用于将两类对象转换为真正的数组:

  1. 类数组对象(含 length 属性的对象)

    let arrayLike = {
        '0': 'a',
        '1': 'b',
        '2': 'c',
        length: 3
    };
    
    // es5 的写法
    var arr1 = [].slice.call(arrayLike);// ['a','b','c'];
    
    // es6 的写法
    let arr2 = Array.from(arrayLike);
    
    
  2. 可遍历对象(实现了接口 iterable)

参数列表:还接收第二个参数,作用类似于数组的map方法;第三个参数,绑定this

Array.from(arrayLike,function(item) {
   // map 
});

Array.of()

将一组值转换为数组;为了弥补数组构造函数Array()的不足

模拟实现:

function ArrayOf() {
    return [].slice.call(arguments);
}

数组实例的 entries()、keys() 和 values()

都返回一个遍历器对象,可用 for…of遍历;

还可以手动调用遍历器对象的 next 方法进行遍历。

数组实例的 includes()

在某种情况下,可以用于替换 indexOf ;indexOf 其内部用严格相等运算符,会导致 NaN 的误判

而 includes 使用的是不一样的判断算法,就没有这个问题

数组的空位

第9章 对象的扩展

属性的简洁表示法

属性名表达式

属性名表示法 和 简洁表示法

Object.is()

行为与 === 基本一样;不同之处只有两个:

  1. +0 不等于 -0
  2. NaN 等于自身

Object.assign()

执行的浅复制,而不是深复制。遇到同名属性,后面的属性替换前面的属性。

Object.assign()的常见用途

  1. 为对象添加属性

    class Point {
        constructor(x, y) {
            Object.assign(this,{x,y});
        }
    }
    

    相当于将x属性和y属性添加到Point类的对象实例中。

  2. 为对象添加方法

    Object.assign(SomeClass,prototype, {
        someMethod(arg1, arg2) {
            
        },
        anotherMethod() {
            
        }
    });
    
  3. 克隆对象

    function clone(origin) {
        let originProto = Object.getPrototypeOf(origin);
        return Object.assign({},assign);
    }
    
  4. 合并多个对象

  5. 为属性指定默认值

    const options = Object.assign({},DEFAULTS,options);
    

    由于Object.assign() 为浅复制,故会替换引用类型属性值,而不是拼接

属性的可枚举性

对象的每一个属性都有一个描述对象,用于控制该属性的行为

Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。如:

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

其中 enumerable 属性称为“可枚举性”,如果为false,则有的操作会忽略当前属性

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

从上述描述中可以看出:在遍历对象自身的属性时,尽量不用使用 for…in 循环,而用 Object.keys() 代替。

属性的遍历

  1. for…in:自身,继承,可枚举,不含Symbol
  2. Object.keys(obj):自身,可枚举,不含Symbol
  3. Object.getOwnPropertyNames(obj):自身,可枚举,不可枚举,不含Symbol
  4. Object.getOwnPropertySymbols(obj):自身的所有Symbol
  5. Reflect.ownKeys(obj):自身全部属性,除继承

_proto_属性

此处叫作隐式原型,本质上是一个内部属性,而不是一个正式的对外的API。

只有浏览器必须部署这个属性,其他运行环境不一定要部署。

因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性。取而代之用Object.setPrototypeOf()写操作、Object.getPrototypeOf()读操作或 Object.create()生成操作。

Object.setPrototypeOf()

Object.setPrototypeOf(object, prototype);

// 相当于
function (obj, prototype) {
    obj.__proto__ = prototype;
    return obj;
}

Object.keys()/values()/entries()

Object.keys() 返回 不含继承的,自身的,所有可遍历的 属性的键名

Object.values() 返回 对象自身的,过滤属性名为Symbol 的可遍历属性

Object.entries() 返回 对象自身的,不含继承的所有可遍历的属性的键值对数组

此方法的另一个用处是将对象转为真正的Map 结构

对象的扩展运算符

解构赋值的复制是浅复制

解构赋值必须是最后一个参数,否则会报错。

扩展运算符的用法:
  1. 用于合并两个对象:b 会覆盖 a 的同名属性

    let ab = { ...a, ...b };
    // 等同于
    let ab = Object.assgin({},a,b);
    
  2. 如果用户自定义的属性放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖。

    let aWithOverrides = { ...a, x: 1, y: 2};
    // 等同于
    let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });
    
  3. 如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值

    let aWithDefaults = { x: 1,y: 2, ...a};
    // 等同于
    let aWithDefaults = Object.assign({},{ x: 1, y: 2}, a);
    

Object.getOwnPropertyDescriptors()

ES2017引入该方法,返回指定对象所有属性(非继承)的描述对象

提案:Null 传导运算符

const firstName = message?.body?.user?.firstName || 'default';

第10章 Symbol

第11章 Set和Map 数据结构

Set

成员值唯一,没有重复。

Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

去除数组重复成员的方法:

[...new Set(array)]

注意:向Set加入值时不会发生类型转换。类似于精确相等运算符(===),主要区别是NaN 等于自身。

Set 实例的属性和方法

属性:
  • Set.prototype.constructor:构造函数,默认是Set函数
  • Set.prototype.size:返回Set 实例的成员总数
操作方法:
  • add(value):添加某个值,返回Set结构自身
  • delete(value):删除某个值,返回布尔值
  • has(value):返回布尔值,表示参数是否为Set的成员
  • clear(): void:清除所有成员
遍历方法:
  • keys()
  • values()
  • entries():返回键值对的遍历器,注意,Set的键等于值
  • forEach()
Set与离散数学

使用Set 可以很容易地实现并集(Union)、交集(Interset)和差集(Differrence)。

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

// 并集
let union = new Set([...a,...b]);

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));

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

WeakSet

  1. WeakSet的成员只能是对象,不能是其他类型
  2. WeakSet的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用

规定WeakSet 不可遍历

Map

Map 的键的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

Map 的键实际上是和内存地址绑定的,只要内存地址不同,就视为两个键。

Map 实例的属性和方法

属性:
  • size
操作方法:
  • set(key, value)
  • get(key)
  • has(key)
  • delete(key)
  • clear()
遍历方法:
  • keys()
  • values()
  • entries()
  • forEach():可以接受第二个参数,用于绑定 this

WeakMap

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

注意:WeakMap 弱引用的只是键名而不是键值。键值依然是正常引用的。

第12章 Proxy

第13章 Reflect

第14章 Promise

第15章 Iterator 和 for…of 循环

第16章 Generator 函数的语法

第17章 Generator 函数的异步应用

第18章 async 函数

第19章 class 的基本语法

ES6中的 class 可以看做只是一个语法糖

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

**注意:**类的内部定义的所有方法都是不可枚举的

类必须用 new 来调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用 new 也可以执行。

class 表达式

使用 class 表达式,可以写出立即执行的Class

不存在变量提升

类不存在变量提升,这点与ES5完全不同

私有方法

私有方法虽是常见需求,但是ES6 不提供。

私有属性

提案:在属性面前加 # 号

this的指向

name属性

类的 name 属性总是返回紧跟在 class 关键字后面的类名。

Class 的取值函数(getter)和存值函数(setter)

在类的内部可以使用 get 和 set 关键字对某个属性设置getter 和 setter ,拦截该属性的存取行为。

存值函数和取值函数是设置在属性的 Descriptor 对象上的。

Class 的 Generator 方法

Class 的静态方法

在方法前添加 static 关键字

父类的静态方法可以被子类继承

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

提案:Class的静态属性

提案:Class 内部只有静态方法,没有静态属性。

提案:Class的实例属性

提案:实例属性用等式写入类的定义之中。

目前如果要给类加入实例属性,需要在构造函数中往 this 挂载属性

new.target属性

new 是从构造函数生成实例的命令。

返回new 命令所作用的构造函数。如果不是通过 new 命令调用的,则返回 undefined。

通过这个属性,可以知道一个函数是作为构造函数使用了,还是作为普通函数调用了。

第20章 Class 的继承

子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。

这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。

ES5 和 ES6 的继承实质:

  • ES5的继承实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面 Parent.apply(this)
  • ES6的继承机制完全不同,实质是先创造父类的实例对象 this(所以必须先调用super 方法),然后再用子类的构造函数修改this。即 子类实例的构建是基于父类实例加工。

基于ES6的原理;只有调用 super 之后才可以使用 this 关键字

Object.getPrototypeOf()

此方法可以从子类上获取父类,因此可以使用这个方法判断一个类是否继承了另一个类。

Object.getPrototypeOf(ColorPoint) === Point;

super 关键字

super 关键字既可以当函数使用,也可以当对象使用。

  1. super 作为函数调用时代表父类的构造函数。

    **注意:**super 虽然代表了父类A 的构造函数,但是返回的是子类B 的实例,即 super 内部的 this 指向的是B ,因此 super() 在这里相当于 A.prototype.constructor.call(this)。

    **注意:**作为函数时,super() 只能用在子类的构造函数之中,用在其他地方就会报错。

  2. super 作为对象时在普通方法中指向父类的原型对象;在静态方法中指向父类

    **注意:**ES6规定,通过 super 调用父类的方法时,super 会绑定子类的 this。

类的 prototype 属性和 __proto__ 属性

Class 作为构造函数的语法糖,同时有 prototype 属性和 __proto__属性,因此同时存在两条继承链。

  • 子类的 __proto__ 属性表示构造函数的继承,总是指向父类。
  • 子类 prototype 属性的 __proto__ 属性表示方法的继承,总是指向父类的 prototype 属性。
class A {
    
}

class B extends A {
    
}

B.__proto__ === A; // true
B.prototype.__proto__ === A.prototype; // true

类的继承是按照如下模式实现的:

// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);

// B 的实例继承 A 的静态属性
Object.setPrototypeOf(B, A);

而上述代码等同于:

Object.setPrototypeOf(B.prototype, A.prototype);
// 等同于
B.prototype.__proto__ = A.prototype;

Object.setPrototypeOf(B, A);
// 等同于
B.__proto__ = A;

extends 的继承目标

有三种特殊情况需要注意:

  1. 子类继承 Object 类

    class A extends Object {
        
    }
    
    A.__proto__ === Object; // true
    A.prototype.__proto__ === Object.prototype; // true
    
  2. 不存在任何继承

    class A {
        
    }
    
    A.__proto__ === Function.prototype; // true
    A.prototype.__proto__ === Object.prototype; // true
    

    这种情况下,A作为一个基类(即不存在任何继承)就是一个普通函数,所以直接继承 Function.prototype。但是,A调用后返回一个空对象,即const a = new A(); a instanceof Object; //true,所以

    A.prototype.__proto__指向构造函数(Object)的prototype 属性。

  3. 子类继承 null

    class A extends null {
        
    }
    
    A.__proto__ === Function.prototype; // true
    A.prototype.__proto__ === undefined; // true
    

原生构造函数的继承

ECMAScript 的原生构造函数大致有:

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

在ES5的继承中,这些原生构造函数是无法继承的。

这是因为:原生构造函数会忽略 apply 方法传入的 this,也就是说,原生构造函数的 this 无法绑定,导致拿不到内部属性。

可是ES6的继承可以自定义原生数据结构(比如Array、String等)的子类,这是ES5无法做到的。

如下是使用ES6实现的带版本功能的数组:

class VersionedArray extends Array {
    constructor() {
        super();
        this.history = [];
    }
    commit() {
        this.history.push(this.slice());
    }
    revert() {
        this.splice(0,this.length,...this.history[this.history.length - 1]);
    }
}

const x = new VersionedArray();
x.push(1);
x.push(2);
x // [1, 2]
x.history // [[]]

x.commit();
x.history // [[], [1, 2]]

x.push(3);
x // [1, 2, 3]

x.revert();
x // [1, 2]

但是需要注意的是,继承Object的子类有一个行为差异

这是因为ES6改变了 Object 构造函数的行为,一旦发现Object 方法不是通过 new Object() 这种形式调用,ES6规定 Object构造函数会忽略参数

Mixin 模式的实现

Mixin 模式:将多个类的接口“混入”另一个类,ES6实现如下:

function mix(...mixins) {
    class Mix {}
    
    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);
        }
    }
}

class A {
    a() {
        console.log('A.a()');
    }
    b() {
        console.log('A.b()');
    }
}

class B {
    b() {
        console.log('B.b()');
    }
}

class AA extends A {
    aa() {
        console.log('AA.aa()');
    }
}

// A 将 覆盖 B的 同名方法
// 且只能 mix 子类的方法, 父类的方法无法获取
class C extends mix(B,AA) {

}

const c = new C();
c.a(); // 报错

你可能感兴趣的:(javascript)