数组的扩展

扩展运算符
扩展运算符(spread)是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
替代函数的 apply 方法
由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。
扩展运算符的应用
(1)复制数组
数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
const a1 = [1, 2];
// 写法一
const a2 = […a1];
// 写法二
const […a2] = a1;
(2)合并数组
(3)与解构赋值结合(如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。)
const [first, …rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]

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

    const [first, ...rest] = ["foo"];
    first  // "foo"
    rest   // []
(4)字符串
    扩展运算符还可以将字符串转为真正的数组。
    [...'hello']
(5)实现了 Iterator 接口的对象
    任何 Iterator 接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。
(6)Map 和 Set 结构,Generator 函数
    扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。

Array.from
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)
Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
Array.from(arrayLike, x => x x);
// 等同于
Array.from(arrayLike).map(x => x
x);

    Array.from([1, 2, 3], (x) => x * x)
    // [1, 4, 9]
如果map函数里面用到了this关键字,还可以传入Array.from的第三个参数,用来绑定this。

Array.of
Array.of方法用于将一组值,转换为数组。
数组实例的 copyWithin()
数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
数组实例的 find() 和 findIndex()
数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
数组实例的 fill()
fill方法使用给定值,填充一个数组。
fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。
let arr = new Array(3).fill({name: “Mike”});
arr[0].name = “Ben”;
arr
// [{name: “Ben”}, {name: “Ben”}, {name: “Ben”}]

let arr = new Array(3).fill([]);
arr[0].push(5);
arr
// [[5], [5], [5]]

数组实例的 entries(),keys() 和 values()
ES6 提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
数组实例的 includes()
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。ES2016 引入了该方法。
数组的空位
ES6会处理空位

module

概述
严格模式
export 命令
export {}
export 直接定义变量
动态改变 setTimeout
import 命令
大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。
只读
import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
import命令具有提升效果,会提升到整个模块的头部,首先执行。
import语句会执行所加载的模块,因此可以有下面的写法。(多次输入,只执行一次)
因为静态,所以最早形成的
模块的整体加载
export default 命令
使用了default就不会在import中使用{}
可以在default后加函数表达式,也可以加函数,相当于加函数表达式
export {d as default} 等同于 export default d
export 与 import 的复合写法
export {q,w} from ‘df’;等同于 export {q,w}; import {q,w} from ‘df’;但是导入模块dw,不会被使用
模块的继承
跨模块常量
import()

extends

简介
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。
Object.getPrototypeOf()
Object.getPrototypeOf方法可以用来从子类上获取父类。
super 关键字
super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B,因此super()在这里相当于A.prototype.constructor.call(this)。
第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
ES6 规定,通过super调用父类的方法时,方法内部的this指向当前的子类实例。
类的 prototype 属性和proto属性
(1)子类的proto属性,表示构造函数的继承,总是指向父类。
(2)子类prototype属性的proto属性,表示方法的继承,总是指向父类的prototype属性。
class A {
}

class B extends A {
}

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

原生构造函数的继承
Mixin 模式的实现
Mixin 指的是多个对象合成一个新的对象,新对象具有各个组成成员的接口。它的最简单实现如下。

对象的扩展

属性的简洁表示法
属性名表达式
方法的 name 属性
Object.is()
ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
Object.assign()
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
1.浅拷贝
Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
2.同名属性的处理
3.数组处理
4.取值函数get的处理
属性的可枚举性和遍历
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。
属性的遍历
ES6 一共有 5 种方法可以遍历对象的属性。

(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 或字符串,也不管是否可枚举。

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

Object.getOwnPropertyDescriptors()
前面说过,Object.getOwnPropertyDescriptor方法会返回某个对象属性的描述对象(descriptor),参数分别是要查找的对象和要检索的属性名。ES2017 引入了Object.getOwnPropertyDescriptors方法,返回指定对象所有自身属性(非继承属性)的描述对象。
proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()
super 关键字
我们知道,this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。
Object.keys(),Object.values(),Object.entries()
let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
console.log(key); // 'a', 'b', 'c'
}

for (let value of values(obj)) {
console.log(value); // 1, 2, 3
}

for (let [key, value] of entries(obj)) {
console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}
Object.values会过滤属性名为 Symbol 值的属性。

对象的扩展运算符
拷贝
// 写法一
const clone1 = {
proto: Object.getPrototypeOf(obj),
…obj
};

// 写法二
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);

// 写法三
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)

class

简介
由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign方法可以很方便地一次向类添加多个方法。
另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
严格模式
类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
constructor 方法
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
类的实例对象
必须new
与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
可以通过实例的proto属性为“类”添加方法。但是所添加的所有方法都会给所有被实例对象使用
Class 表达式
与函数一样,类也可以写成表达式
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是MyClass而不是Me,Me只在 Class 的内部代码可用,指代当前类。
如果类的内部没用到的话,可以省略Me,也就是可以写成下面的形式。
const MyClass = class { // };
用Class表达式,可以写出立即执行的class
let person = new class {
constructor(name) {
this.name = name;
}

    sayName() {
        console.log(this.name);
    }
}('张三');

person.sayName(); // "张三"

不存在变量提升
私有方法和私有属性
一种是通过把私有方法写在类外面,通过call(this)调用。
另一种是通过symbol来私有化变量,在通过变量来调用方法和属性变量
const bar = Symbol(‘bar’);
const snaf = Symbol(‘snaf’);

    export default class myClass{

        // 公有方法
        foo(baz) {
            this[bar](baz);
        }

        // 私有方法
        [bar](baz) {
            return this[snaf] = baz;
        }

        // ...
    };
私有属性和私有方法在前面家#

this 的指向
class test {
constructor(){
//为了printName被分离出去后也能被调用,this指向还是test
this.printName = this.printName.bind(this);
}
}
如果是箭头函数的话,this就是指向声明时的this
还有一种解决方法是使用Proxy,获取方法的时候,自动绑定this。
name 属性
由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。
Class 的取值函数(getter)和存值函数(setter)
都是被默认添加到了描述对象prototypedescription上
Class 的 Generator 方法
前面加个*
Class 的静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。静态方法可以和普通方法重名
父类的静态方法可以被继承
Class 的静态属性和实例属性
可在类里直接定义实例属性
静态属性直接在前面加个static
new.target 属性
new是从构造函数生成实例对象的命令。ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
class类中调用new.target返回当前class,在子类中调用则父类也返回子类的class
利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error(‘本类不能实例化’);
}
}
}

class Rectangle extends Shape {
    constructor(length, width) {
        super();
        // ...
    }
}

var x = new Shape();  // 报错
var y = new Rectangle(3, 4);  // 正确