es6基础

168 阅读12分钟

1.Set

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

  • Set本身是一个构造函数,用来生成 Set 数据结构。
  • Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
  • 向 Set 加入值的时候,不会发生类型转换,所以5"5"是两个不同的值。Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===),主要的区别是向 Set 加入值时认为NaN等于自身,而精确相等运算符认为NaN不等于自身。
  • 向 Set中加入的对象结构一样,因为引用地址不相等,会被视为不同的值。

Set 结构的实例有以下属性:

  • Set.prototype.constructor:构造函数,默认就是Set函数。
  • Set.prototype.size:返回Set实例的成员总数。 Set 实例的方法分为两大类:
  1. 操作方法(用于操作数据)
  • Set.prototype.add(value):添加某个值,返回 Set 结构本身。
  • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
  • Set.prototype.clear():清除所有成员,没有返回值。
  1. 遍历方法(用于遍历成员)
  • Set.prototype.keys():返回键名的遍历器
  • Set.prototype.values():返回键值的遍历器
  • Set.prototype.entries():返回键值对的遍历器
  • Set.prototype.forEach():使用回调函数遍历每个成员
  • Set.prototype[Symbol.iterator]: 返回键值的遍历器 需要特别指出的是,Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。
/*
* Set基本用法:
* 1.Set结构不会添加重复的值; 2.Set函数可以接受一个数组(或者具有 iterable接口的其他数据结构)
* 作为参数,用来初始化; 3.实现数组去重; 4.字符串字符去重; 5.在 Set内部,两个 NaN是相等的; 6.向 
* Set中加入的对象结构一样,因为引用地址不相等,会被视为不同的值; 7.Array.from方法可以将 Set结
* 构转为数组,从而实现数组去重。
*/
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
  console.log(i); // 2 3 5 4
}
-----------------------------------------------------------
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
console.log(items.size); // 5
console.log([...items]); // [1, 2, 3, 4, 5] (数组去重)
-----------------------------------------------------------
const set = new Set(document.querySelectorAll('div')); // 接受类似数组的对象作为参数
console.log(set.size); // 56
// 类似于
const set = new Set();
document.querySelectorAll('div').forEach(div => set.add(div));
console.log(set.size); // 56
-----------------------------------------------------------
console.log([...new Set('ababbc')].join('')); // abc
-----------------------------------------------------------
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
console.log(set); // Set {NaN}
console.log(typeof set); // object
-----------------------------------------------------------
let set = new Set();
let a = NaN, b = NaN, c = null, e = null, f = undefined, d = undefined, g = {},
h = {};
set.add(a);
set.add(b);
set.add(c);
set.add(d);
set.add(e);
set.add(f);
set.add(g);
set.add(h);
console.log(set); // Set {NaN, null, undefined, {…}, {…}}
console.log(set.size); // 5
console.log(typeof set); // object
-----------------------------------------------------------
function dedupe(array) {
    return Array.from(new Set(array));
}
console.log(dedupe([1, 1, 2, 3])); // [1, 2, 3]

/*
* Set.prototype.size: 返回 Set实例的成员总数。
*/
var mySet = new Set();
mySet.add(1);
mySet.add(5);
mySet.add("some text")
console.log(mySet); // Set(3) {1, 5, 'some text'}
console.log(mySet.size); // 3

/*
* Set.prototype.add(): 用来向一个 Set对象的末尾添加一个指定的值。
* 语法: var mySet = mySet.add(value);
* 参数: 1.mySet: Set对象本身; 2.value: 必需,需要添加到 mySet对象的元素的值。
* 返回值: Set 对象本身。
* 注意: 1.add()可以链式调用; 2.不能添加重复的值。
*/
var mySet = new Set();
mySet.add(1);
mySet.add(5).add(5).add("some text"); // 可以链式调用 (重复的值没有被添加进去)
console.log(mySet); // Set {1, 5, "some text"}

/*
* Set.prototype.delete(value): 从一个 Set对象中删除指定的元素,返回一个布尔值,表示删除是否
* 成功。
* 语法: var deleteBoolean = mySet.delete(value);
* 参数: 1.deleteBoolean: 删除是否成功; 2.mySet: Set对象本身; 3.value: 将要删除的元素。
* 返回值: 布尔值。(成功删除返回 true,否则返回 false)
*/
var s = new Set();
s.add(1).add(2).add(3);
console.log(s); // Set(3) {1, 2, 3}
console.log(s.delete(1)); // true
console.log(s); // Set(2) {2, 3}

/*
* Set.prototype.has(value): 返回一个布尔值,表示该值是否为 Set的成员。
* 语法: var hasBoolean = mySet.has(value);
* 参数: 1.hasBoolean: 是否存在; 2.mySet: Set对象本身; 3.value: 用来检测是否存在于 Set的值。
* 返回值: 布尔值。[如果该值(value)存在于 Set对象当中,返回true;否则返回 false]
*/
var s = new Set();
s.add(1).add(2).add(3);
console.log(s.has(1)); // true
console.log(s.has(2)); // true
console.log(s.has(5)); // false
s.delete(1);
console.log(s.has(1)); // false

/*
* Set.prototype.clear(): 清除所有成员,没有返回值。
* 语法: mySet.clear();
* 参数: 1.mySet: Set对象本身。
* 返回值: undefined。
*/
var s = new Set();
s.add(1).add(2).add(3);
console.log(s); // Set(3) {1, 2, 3}
console.log(s.clear()); // undefined
console.log(s); // Set(0) {size: 0}

/*
* Set.prototype.keys(): 返回键名的遍历器。
* 语法: var iterator = mySet.keys();
* 参数: 1.iterator: 键名遍历器; 2.mySet: Set对象本身。
* 返回值: 按照元素插入顺序返回一个包含给定的 Set对象中每个元素键名的全新 Iterator对象。
* 注意: 1.由于 Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以 keys方法和 values
* 方法的行为完全一致。
*/
let set = new Set(['red', 'green', 'blue']);
console.log(set.keys()); // SetIterator {'red', 'green', 'blue'}
for (let item of set.keys()) {
  console.log(item); // red green blue
}

/*
* Set.prototype.values(): 返回键值的遍历器。
* 语法: var iterator = mySet.values();
* 参数: 1.iterator: 键值遍历器; 2.mySet: Set对象本身。
* 返回值: 按照元素插入顺序返回一个包含给定的 Set对象中每个元素键值的全新 Iterator对象。
*/
let set = new Set(['red', 'green', 'blue']);
const setIterator = set.values();
console.log(setIterator); // SetIterator {'red', 'green', 'blue'}
for (let item of set.keys()) {
  console.log(item); // red green blue
}
console.log(setIterator.next()); // {value: 'red', done: false}
console.log(setIterator.next()); // {value: 'green', done: false}
console.log(setIterator.next()); // {value: 'blue', done: false}
console.log(setIterator.next()); // {value: undefined, done: true}

/*
* Set.prototype.entries(): 返回键值对的遍历器。
* 语法: var iterator = mySet.entries();
* 参数: 1.iterator: 键值对的遍历器; 2.mySet: Set对象本身。
* 返回值: 一个新的包含 [value,value]形式的数组迭代器对象,value是给定集合中的每个元素,迭代
* 器对象元素的顺序即集合对象中元素插入的顺序。
* 注意: 1.entries方法返回的遍历器,同时包括键名和键值,它的两个成员完全相等。
*/
let set = new Set(['red', 'green', 'blue']);
const setIterator = set.entries();
console.log(setIterator); // SetIterator {'red' => 'red', 'green' => 'green',
                          // 'blue' => 'blue'}
for (let item of set.entries()) {
  console.log(item); // ['red', 'red'] ['green', 'green'] ['blue', 'blue']
}
console.log(setIterator.next()); // {value: Array(2), done: false}
console.log(setIterator.next()); // {value: Array(2), done: false}
console.log(setIterator.next()); // {value: Array(2), done: false}
console.log(setIterator.next()); // {value: undefined, done: true}

/*
* Set.prototype.forEach(): 使用回调函数遍历每个成员,没有返回值。
* 语法: mySet.forEach(callback,thisArg);
* 参数: 1.mySet: Set对象本身; 2.callback: 可选,为集合中每个元素执行的回调函数; 3.thisArg: 
* 可选, 回调函数执行过程中的 this值。
* 返回值: undefined。
* 注意: 1.参数 callback函数接收三个参数。currentValue: 可选,正在被操作的元素;currentKey: 
* 可选,由于集合没有索引,所以也表示正在被操作的元素;set: 可选,调用当前 forEach 方法的集合对
* 象。
*/
let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9

/*
* Set.prototype[Symbol.iterator](): 返回键值的遍历器。
* 语法: var iterator = mySet[Symbol.iterator]();
* 参数: 1.iterator: 键值的遍历器; 2.mySet: Set对象本身。
* 返回值: 键值的遍历器对象。
* 注意: 1.Set结构的实例默认可遍历,它的默认遍历器生成函数就是它的 values方法(Set.prototype
* [Symbol.iterator] === Set.prototype.values),所以可以省略 values方法,直接用 for...of
* 循环遍历 Set。
*/
let set = new Set(['red', 'green', 'blue']);
for (let x of set) {
  console.log(x); // red green blue
}
-----------------------------------------------------------
const mySet = new Set();
mySet.add('0');
mySet.add(1);
mySet.add({});
const setIter = mySet[Symbol.iterator]();
console.log(setIter); // SetIterator {'0', 1, {…}}
  • 遍历的应用
/*
* Set中应用遍历:
* 1.扩展运算符(...)内部使用 for...of循环,所以也可以用于 Set结构; 2.扩展运算符和 Set结构相
* 结合,就可以去除数组的重复成员; 3.数组的 map和 filter方法也可以间接用于 Set; 4.Set可以很
* 容易地实现并集、交集和差集; 5.如果想在遍历操作中,同步改变原来的 Set结构,目前没有直接的方
* 法,但有两种变通方法。一种是利用原 Set结构映射出一个新的结构,然后赋值给原来的 Set结构,另一
* 种是利用 Array.from方法。
*/
let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
console.log(arr); // ['red', 'green', 'blue']
-----------------------------------------------------------
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
console.log(unique); // [3, 5, 2]
-----------------------------------------------------------
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
console.log(set); // Set(3) {2, 4, 6}

let set2 = new Set([1, 2, 3, 4, 5]);
set2 = new Set([...set2].filter(x => (x % 2) == 0));
console.log(set2); // Set(2) {2, 4}
-----------------------------------------------------------
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
console.log(union); // Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
console.log(intersect); // Set {2, 3}
// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
console.log(difference); // Set {1}
-----------------------------------------------------------
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
console.log(set); // Set(3) {2, 4, 6}
// 方法二
let set2 = new Set([1, 2, 3]);
set2 = new Set(Array.from(set2, val => val * 2));
console.log(set2); // Set(3) {2, 4, 6}

2.Proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

语法:const p = new Proxy(target, handler)

  • target:使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理), 即所要拦截的目标对象。
  • handler:一个通常以函数作为属性的对象,用来定制拦截行为。 注意,要使得Proxy起作用,必须针对Proxy实例(即p对象)进行操作,而不是针对目标对象(即target)进行操作。

Proxy 支持的拦截操作(总共 13 种):

  • get(target, propKey, receiver) :拦截对象属性的读取,比如proxy.fooproxy['foo']
  • set(target, propKey, value, receiver) :拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
  • has(target, propKey) :拦截propKey in proxy的操作,返回一个布尔值。
  • deleteProperty(target, propKey) :拦截delete proxy[propKey]的操作,返回一个布尔值。
  • ownKeys(target) :拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey) :拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc) :拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target) :拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target) :拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target) :拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto) :拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args) :拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • construct(target, args) :拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

作用:

  • 拦截和监视外部对对象的访问;
  • 降低函数或类的复杂度;
  • 在复杂操作前对操作进行校验或对所需资源进行管理。
/*
* get(target, propKey, receiver): 用于拦截某个属性的读取操作。
* 参数: 1.target: 目标对象; 2.propKey: 属性名; 3.receiver: 可选, proxy实例本身(严格地说,
* 是操作行为所针对的对象)。
* 返回值: 可以返回任何值。
*/
var target = { };
var handler = { };
var proxy = new Proxy(target, handler);
proxy.a = 'b'; // handler是一个空对象,没有任何拦截效果,访问 proxy就等同于访问 target。
target.a // 'b'
-----------------------------------------------------------
var obj = new Proxy({}, {
    get: function (target, propKey, receiver) {
        console.log(`getting ${propKey}!`);
        return Reflect.get(target, propKey, receiver);
    },
    set: function (target, propKey, value, receiver) {
        console.log(`setting ${propKey}!`);
        return Reflect.set(target, propKey, value, receiver);
    }
});
obj.count = 1; // 打印 setting count!
++obj.count; // 依次打印 getting count! setting count!
-----------------------------------------------------------
const target = {
    name: 'jacky',
    sex: 'man',
}
const handler = {
    get(target, key) {
        console.log('获取名字/性别'); // 获取名字/性别
        return Reflect.get(target, key);
    },
}
const proxy = new Proxy(target, handler);
console.log(proxy.name); // jacky [在获取name属性是先进入get方法,在get方法里面打印了获
                         // 取名字/性别,然后通过Reflect.get(target,key)的返回值拿到属
                         // 性值,相当于target[key]]。
-----------------------------------------------------------
var proxy = new Proxy({}, {
    get: function(target, propKey) {
      return 35;
    }
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
-----------------------------------------------------------
const target = Object.defineProperties({}, {
    foo: {
        value: 123,
        writable: false,
        configurable: false
    },
});
const handler = {
    get(target, propKey) {
        return 'abc';
    }
};
const proxy = new Proxy(target, handler);
proxy.foo; // TypeError: Invariant check failed [如果一个属性不可配置(configurable)且不
           // 可写(writable),则Proxy不能修改该属性,否则通过Proxy对象访问该属性会报错)。
-----------------------------------------------------------
const proxy = new Proxy({}, {
    get: function(target, key, receiver) {
        console.log('get '+key);
        return receiver;
    }
});
const d = Object.create(proxy);
d.a === d; // true (d对象本身没有a属性,所以读取d.a的时候,会去d的原型proxy对象找。这时,
           // receiver就指向d,代表原始的读操作所在的那个对象)。
d.foo; // 打印 get foo (get方法可以继承,拦截操作定义在Prototype对象上面,所以如果读取d对
       // 象继承的属性时,拦截会生效)。


/*
* set(target, propKey, value, receiver): 用来拦截某个属性的赋值操作。
* 参数: 1.target: 目标对象; 2.propKey: 属性名; 3.value: 属性值; 4.receiver: 可选, Proxy
* 实例本身。
* 返回值: 布尔值 (返回true代表属性设置成功,在严格模式下,如果set()方法返回false,那么会抛出一
* 个TypeError)。
*/
const target = { 
    name: 'jacky',
    sex: 'man',
}
const handler = {
    get(target, key) {
        console.log('获取名字/姓别');
        return Reflect.get(target, key);
    },
    set(target, key, value) {
        console.log('设置');
        return Reflect.set(target, key, `强行设置为${value}`);
    },
}
const proxy = new Proxy(target, handler);
proxy.name = 'monkey'; // 打印 设置
console.log(proxy.name); // 打印 获取名字/姓别 强行设置为monkey
-----------------------------------------------------------
const handler = {
    set: function(obj, prop, value, receiver) {
        obj[prop] = receiver;
        return true;
    }
};
const proxy = new Proxy({}, handler);
const myObj = {};
Object.setPrototypeOf(myObj, proxy);

myObj.foo = 'bar';
myObj.foo === myObj // true
-----------------------------------------------------------
let validator = {
    set: function(obj, prop, value) {
        if (prop === 'age') {
            if (!Number.isInteger(value)) {
                throw new TypeError('The age is not an integer');
            }
            if (value > 200) {
                throw new RangeError('The age seems invalid');
            }
        }
        // 对于满足条件的 age属性以及其他属性,直接保存
        obj[prop] = value;
        return true;
    }
};

let person = new Proxy({}, validator);
person.age = 100;
person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错

/*
* has(target, propKey): 拦截 propKey in proxy (即 HasProperty)的操作,即判断对象是否具有
* 某个属性时,这个方法会生效返回一个布尔值。(典型的操作就是 in 运算符)
* 参数: 1.target: 目标对象; 2.propKey: 需查询的属性名。
* 返回值: 布尔值。
* 注意: 1.如果某个属性不可配置(或者目标对象不可扩展),则 has()方法就不得“隐藏”(即返回false)
* 目标对象的该属性; 2.has()方法拦截的是 HasProperty操作,而不是 HasOwnProperty操作,即 
* has()方法不判断一个属性是对象自身的属性,还是继承的属性。
*/
var target = { _prop: 'foo', prop: 'foo' };
var handler = {
    has(target, key) {
        if (key[0] === '_') {
            return false;
        }
        return key in target;
    }
};
var proxy = new Proxy(target, handler);
'_prop' in proxy // false (隐藏 _prop属性,不被 in运算符发现)
-----------------------------------------------------------
var obj = { a: 10 };
Object.preventExtensions(obj);

var p = new Proxy(obj, {
    has: function(target, prop) {
        return false;
    }
});
'a' in p // TypeError is thrown (obj对象禁止扩展,结果使用 has拦截就会报错)

/*
* deleteProperty(target, propKey): 拦截 delete proxy[propKey]的操作,返回一个布尔值。
* 参数: 1.target: 目标对象; 2.propKey: 待删除的属性名。
* 返回值: 布尔值。
* 注意: 1.如果这个方法抛出错误或者返回 false,当前属性就无法被 delete命令删除; 2.目标对象自
* 身的不可配置(configurable)的属性,不能被 deleteProperty方法删除,否则报错。
*/
var target = { _prop: 'foo' };
var handler = {
    deleteProperty (target, key) {
        invariant(key, 'delete');
        delete target[key];
        return true;
    }
};
function invariant (key, action) {
    if (key[0] === '_') {
        throw new Error(`Invalid attempt to ${action} private "${key}" property`);
    }
}

var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property
-----------------------------------------------------------
var p = new Proxy({}, {
    deleteProperty: function(target, prop) {
        console.log('called: ' + prop);
        return true;
    }
});

delete p.a; // "called: a"

/*
* ownKeys(target): 用来拦截对象自身属性的读取操作。具体来说,拦截以下操作: 
* Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()、Object.keys()、
* for...in循环。
* 参数: 1.target: 目标对象。
* 返回值: 一个可枚举对象。
* 注意: 1.ownKeys()方法返回的数组成员,只能是字符串或 Symbol值。如果有其他类型的值,或者返回的
* 根本不是数组,就会报错; 2.如果目标对象自身包含不可配置的属性,则该属性必须被 ownKeys()方法返
* 回,否则报错; 3.如果目标对象是不可扩展的(non-extensible),这时 ownKeys()方法返回的数组之
* 中,必须包含原对象的所有属性,且不能包含多余的属性,否则报错; 4.使用 Object.keys()方法时,有三
* 类属性会被 ownKeys()方法自动过滤,不会返回: 目标对象上不存在的属性、属性名为 Symbol值、不可
* 遍历(enumerable)的属性。
*/
let target = {
    a: 1,
    b: 2,
    c: 3
};
let handler = {
    ownKeys(target) {
        return ['a'];
    }
};
let proxy = new Proxy(target, handler);
console.log(Object.keys(proxy)) // ['a']
-----------------------------------------------------------
let target = {
    _bar: 'foo',
    _prop: 'bar',
    prop: 'baz'
};
let handler = {
    ownKeys (target) {
        console.log(Reflect.ownKeys(target)); // ['_bar', '_prop', 'prop']
        return Reflect.ownKeys(target).filter(key => key[0] !== '_');
    }
};
let proxy = new Proxy(target, handler);
for (let key of Object.keys(proxy)) {
    console.log(target[key]); // baz
}
-----------------------------------------------------------
let target = {
    a: 1,
    b: 2,
    c: 3,
    [Symbol.for('secret')]: '4',
};
Object.defineProperty(target, 'key', {
    value: 'static',
    writable: true,
    enumerable: false,
    configurable: true,
});
let handler = {
    ownKeys(target) {
        return ['a', 'd', Symbol.for('secret'), 'key'];
    }
};
let proxy = new Proxy(target, handler);
console.log(Object.keys(proxy)); // ['a']
-----------------------------------------------------------
const obj = { hello: 'world' };
const proxy = new Proxy(obj, {
    ownKeys: function () {
        return ['a', 'b'];
    }
});

for (let key in proxy) {
    console.log(key); // 没有任何输出 [ownkeys()指定只返回 a和 b属性,由于 obj没有这两个
                      // 属性,因此 for...in循环不会有任何输出]。
}
-----------------------------------------------------------
var p = new Proxy({}, {
    ownKeys: function(target) {
        return ['a', 'b', 'c'];
    }
});
console.log(Object.getOwnPropertyNames(p)); // ['a', 'b', 'c']
-----------------------------------------------------------
var obj = {};
var p = new Proxy(obj, {
    ownKeys: function(target) {
        return [123, true, undefined, null, {}, []];
    }
});
Object.getOwnPropertyNames(p)
// Uncaught TypeError: 123 is not a valid property name
-----------------------------------------------------------
var obj = {};
Object.defineProperty(obj, 'a', {
    configurable: false,
    enumerable: true,
    value: 10 }
);
var p = new Proxy(obj, {
    ownKeys: function(target) {
        return ['b'];
    }
});
Object.getOwnPropertyNames(p); 
// Uncaught TypeError: 'ownKeys' on proxy: trap result did not include 'a'
-----------------------------------------------------------
var obj = {
    a: 1
};
Object.preventExtensions(obj);
var p = new Proxy(obj, {
    ownKeys: function(target) {
        return ['a', 'b'];
    }
});
Object.getOwnPropertyNames(p)
// Uncaught TypeError: 'ownKeys' on proxy: trap returned extra keys but proxy target
// is non-extensible

/*
* getOwnPropertyDescriptor(target, propKey): 拦截 Object.getOwnPropertyDescriptor(),返
* 回一个属性描述对象或者 undefined。
* 参数: 1.target: 目标对象; 2.propKey: 属性名。
* 返回值: 一个属性描述对象或者 undefined。
*/
var target = { _foo: 'bar', baz: 'tar' };
var handler = {
    getOwnPropertyDescriptor (target, key) {
        if (key[0] === '_') {
            return;
        }
        return Object.getOwnPropertyDescriptor(target, key);
    }
};
var proxy = new Proxy(target, handler);
console.log(Object.getOwnPropertyDescriptor(proxy, 'wat')); // undefined
console.log(Object.getOwnPropertyDescriptor(proxy, '_foo')); // undefined
console.log(Object.getOwnPropertyDescriptor(proxy, 'baz'));
// { value: 'tar', writable: true, enumerable: true, configurable: true }

/*
* defineProperty(target, propKey, propDesc): 拦截 Object.defineProperty(proxy,
* propKey, propDesc)、Object.defineProperties(proxy, propDescs)操作,返回一个布尔值。
* 参数: 1.target: 目标对象; 2.propKey: 属性名; 3.propDesc: 属性描述符。
* 返回值: 布尔值。(表示定义该属性的操作成功与否)
* 注意: 1.如果目标对象不可扩展(non-extensible),则 defineProperty()不能增加目标对象上不存在
* 的属性,否则会报错; 2.如果目标对象的某个属性不可写(writable)或不可配置(configurable),则 
* defineProperty()方法不得改变这两个设置。
*/
var target = {};
var handler = {
    defineProperty (target, key, descriptor) {
        return false;
    }
};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar'; // 不会生效 [defineProperty()方法内部没有任何操作,只返回 false,导致添加
                  // 新属性总是无效。注意,这里的 false只是用来提示操作失败,本身并不能阻止添
                  // 加新属性。
console.log(proxy); // Proxy {}
-----------------------------------------------------------
var target = {};
var handler = {
    defineProperty (target, key, descriptor) {
        return Reflect.defineProperty(target, key, descriptor);
    }
};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar';
console.log(proxy); // Proxy {foo: 'bar'}
-----------------------------------------------------------
var p = new Proxy({}, {
    defineProperty(target, prop, descriptor) {
        console.log(descriptor); // {value: 'proxy'}
        return Reflect.defineProperty(target, prop, descriptor);
    }
});
Object.defineProperty(p, 'name', {
    value: 'proxy',
    type: 'custom'
});
console.log(p); // Proxy {name: 'proxy'}

/*
* preventExtensions(target): 拦截 Object.preventExtensions(proxy),该方法必须返回一个布
* 尔值,否则会被自动转为布尔值。
* 参数: 1.target: 目标对象。
* 返回值: 布尔值。
* 注意: 1.只有目标对象不可扩展时(即 Object.isExtensible(proxy) 为 false), proxy
* .preventExtensions 才能返回 true,否则会报错。
*/
var proxy = new Proxy({}, {
    preventExtensions: function(target) {
        return true;
    }
});
Object.preventExtensions(proxy); // proxy.preventExtensions()方法返回 true,但这时 
                                 // Object.isExtensible(proxy)会返回 true,因此报错。
// Uncaught TypeError: 'preventExtensions' on proxy: trap returned truish but the 
// proxy target is extensible
-----------------------------------------------------------
var proxy = new Proxy({}, {
    preventExtensions: function(target) {
        Object.preventExtensions(target);
        return true;
    }
});
console.log(Object.preventExtensions(proxy)); // Proxy {}
console.log(proxy); // Proxy {}

/*
* getPrototypeOf(target): 主要用来拦截获取对象原型。具体来说,拦截下面这些操作: 
* Object.prototype.__proto__、Object.prototype.isPrototypeOf()、Object
* .getPrototypeOf()、Reflect.getPrototypeOf()、instanceof。
* 返回值: 对象。
* 注意: 1.getPrototypeOf()方法的返回值必须是对象或者null,否则报错; 2.如果目标对象不可扩
* 展(non-extensible), getPrototypeOf()方法必须返回目标对象的原型对象。
*/
var proto = {};
var p = new Proxy({}, {
  getPrototypeOf(target) {
    return proto;
  }
});
console.log(Object.getPrototypeOf(p) === proto); 
// true [getPrototypeOf()方法拦截 Object.getPrototypeOf(),返回 proto对象]。
-----------------------------------------------------------
var obj = {};
var p = new Proxy(obj, {
    getPrototypeOf(target) {
        return 'foo';
    }
});
Object.getPrototypeOf(p); // TypeError: 'foo' is not an object or null

/*
* isExtensible(target): 拦截 Object.isExtensible(proxy)操作,返回一个布尔值。
* 参数: 1.target: 目标对象。
* 返回值: 布尔值。
* 注意: 1.该方法只能返回布尔值,否则返回值会被自动转为布尔值; 2.返回值必须与目标对象的
* isExtensible属性保持一致,否则就会抛出错误。
*/
var p = new Proxy({}, {
    isExtensible: function(target) {
        console.log("called"); // called
        return true;
    }
});

console.log(Object.isExtensible(p)); // true
p.a = 10;
console.log(p); // Proxy {a: 10}
-----------------------------------------------------------
var p = new Proxy({}, {
    isExtensible: function(target) {
        return false;
    }
});
  
Object.isExtensible(p)
// Uncaught TypeError: 'isExtensible' on proxy: trap result does not reflect
// extensibility of proxy target (which is 'true')

/*
* setPrototypeOf(target, proto): 拦截 Object.setPrototypeOf(proxy, proto), 返回一个布
* 尔值。
* 参数: 1.target: 目标对象; 2.proto: 对象新原型或为 null。
* 注意: 1.该方法只能返回布尔值,否则会被自动转为布尔值; 2.如果目标对象不可扩展(non
* -extensible), setPrototypeOf()方法不得改变目标对象的原型。
*/
var target = function () {};
var handler = {
    setPrototypeOf (target, proto) {
        throw new Error('Changing the prototype is forbidden');
    }
};
var proto = {};
var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
// Error: Changing the prototype is forbidden
-----------------------------------------------------------
var target = function () {};
var handler = {
    setPrototypeOf (target, proto) {
        return Reflect.setPrototypeOf(target, proto);
    }
};
var proto = { x: 10 };
var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
console.log(proxy.x); // 10

/*
* apply(target, object, args): 拦截 Proxy实例作为函数调用的操作,比如
* proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
* 参数: 1.target: 目标对象; 2.object: 目标对象的上下文对象(this); 3.args: 目标对象的参数
* 数组。
* 返回值: 任意值。
* 注意: 1.调用 Reflect.apply方法,也会被拦截。
*/
var target = function () { return 'I am the target'; };
var handler = {
    apply: function () {
        return 'I am the proxy';
    }
};
var p = new Proxy(target, handler);
console.log(p()); // "I am the proxy" [变量 p是 Proxy的实例,当它作为函数调用时(p()),就
                  // 会被 apply方法拦截,返回一个字符串]。
-----------------------------------------------------------
function sum (left, right) {
    return left + right;
};
var twice = {
    apply (target, ctx, args) {
        return Reflect.apply(...arguments) * 2;
    }
};
var proxy = new Proxy(sum, twice);
console.log(proxy(1, 2)); // 6
console.log(proxy.call(null, 5, 6)); // 22
console.log(proxy.apply(null, [7, 8])); // 30
console.log(Reflect.apply(proxy, null, [9, 10]));  // 38

/*
* construct(target, args, newTarget): 拦截 Proxy实例作为构造函数调用的操作,比如 new
* proxy(...args)。
* 参数: 1.target: 目标对象; 2.args: 构造函数的参数列表; 3.newTarget: 创建实例对象时,new
* 命令作用的构造函数(下面例子的 p)。
* 返回值: 对象。
* 注意: 1.construct()方法返回的必须是一个对象,否则会报错; 2.construct()拦截的是构造函数,所
* 以它的目标对象必须是函数,否则就会报错; 3.construct()方法中的 this指向的是 handler,而不是
* 实例对象。
*/
const p = new Proxy(function () {}, {
    construct: function(target, args) {
        console.log('called: ' + args.join(', '));
        return { value: args[0] * 10 };
    }
});

console.log((new p(1)).value); // 10
-----------------------------------------------------------
const p = new Proxy(function() {}, {
    construct: function(target, argumentsList) {
        return 1; // construct()方法返回的必须是一个对象,否则会报错。
    }
});
  
new p() // 报错
// Uncaught TypeError: 'construct' on proxy: trap returned non-object ('1')
-----------------------------------------------------------
const p = new Proxy({}, {
    construct: function(target, argumentsList) {
        return {};
    }
});

new p(); // 报错 [拦截的目标对象不是一个函数,而是一个对象(new Proxy()的第一个参数),导致报错]。
// Uncaught TypeError: p is not a constructor
-----------------------------------------------------------
const handler = {
    construct: function(target, args) {
        console.log(this === handler); // true (this指向的是 handler,而不是实例对象)。
        return new target(...args);
    }
}

let p = new Proxy(function () {}, handler);
console.log(new p()); // {}

2.Reflect

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。

Reflect对象的设计目的:

  • Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。
  • 修改某些Object方法的返回结果,让其变得更合理。
  • Object操作都变成函数行为。
  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。

Reflect对象总共有 13 个静态方法:

  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.has(target, name)
  • Reflect.deleteProperty(target, name)
  • Reflect.ownKeys(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.preventExtensions(target)
  • Reflect.getPrototypeOf(target)
  • Reflect.isExtensible(target)
  • Reflect.setPrototypeOf(target, prototype)
  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args) 上面这些方法的作用,大部分与Object对象的同名方法的作用都是相同的,而且它与Proxy对象的方法是一一对应的。
/*
* Reflect.get(target, name, receiver): 查找并返回 target对象的 name属性,如果没有该属性,
* 则返回 undefined。
* 参数: 1.target: 目标对象; 2.name: 属性名; 3.receiver: 如果target对象中指定了getter,
* receiver则为 getter调用时的 this值。
* 返回值: 属性的值。
* 注意: 1.如果 target不是对象, Reflect.get方法会报错。
*/
var myObject = {
    foo: 1,
    bar: 2,
    get baz() {
        console.log(this) // {foo: 1, bar: 2} [Reflect.get(myObject, 'baz')时触发]。
        return this.foo + this.bar;
    },
}
console.log(Reflect.get(myObject, 'foo')); // 1
console.log(Reflect.get(myObject, 'bar')); // 2
console.log(Reflect.get(myObject, 'baz')); // 3
-----------------------------------------------------------
var myObject = {
    foo: 1,
    bar: 2,
    get baz() {
        return this.foo + this.bar;
    },
};
var myReceiverObject = {
    foo: 4,
    bar: 4,
};
console.log(Reflect.get(myObject, 'baz', myReceiverObject)); // 8
// 如果 name属性部署了读取函数(getter), 则读取函数的 this绑定 receiver。
-----------------------------------------------------------
console.log(Reflect.get(["zero", "one"], 1)); // "one"
-----------------------------------------------------------
var x = { p: 1 };
var obj = new Proxy(x, {
    get(t, k, r) { return k + "bar"; }
});
console.log(Reflect.get(obj, "foo")); // "foobar"
-----------------------------------------------------------
Reflect.get(1, 'foo') // 报错
Reflect.get(false, 'foo') // 报错

/*
* Reflect.set(target, name, value, receiver): 设置 target对象的 name属性等于 value。
* 参数: 1.target: 目标对象; 2.name: 设置的属性名; 3.value: 设置的值; 4.receiver: 如果
* 遇到 setter, receiver则为 setter调用时的 this值。
* 返回值: 布尔值。(表明是否成功设置属性)
* 注意: 如果 target不是对象,Reflect.set会报错。
*/
var myObject = {
    foo: 1,
    set bar(value) {
        return this.foo = value;
    },
}

console.log(myObject.foo); // 1
Reflect.set(myObject, 'foo', 2);
console.log(myObject.foo); // 2
Reflect.set(myObject, 'bar', 3)
console.log(myObject.foo); // 3
console.log(myObject.bar); // undefined
-----------------------------------------------------------
var myObject = {
    foo: 4,
    set bar(value) {
        return this.foo = value;
    },
};
var myReceiverObject = {
    foo: 0,
};
Reflect.set(myObject, 'bar', 1, myReceiverObject); // 如果name属性设置了赋值函数,则赋值
console.log(myObject.foo); // 4                    // 函数的 this绑定receiver。
console.log(myReceiverObject.foo); // 1
-----------------------------------------------------------
let p = {
    a: 'a'
};
  
let handler = {
    set(target, key, value, receiver) {
        console.log('set');
        Reflect.set(target, key, value, receiver)
    },
    defineProperty(target, key, attribute) {
        console.log('defineProperty');
        Reflect.defineProperty(target, key, attribute);
    }
};
let obj = new Proxy(p, handler);
obj.a = 'A';
// set
// defineProperty
// [Proxy.set拦截里面使用了Reflect.set,而且传入了receiver,导致触发Proxy.defineProperty拦
// 截。这是因为 Proxy.set的receiver参数总是指向当前的 Proxy实例(即上例的obj),而 Reflect
// .set一旦传入receiver,就会将属性赋值到receiver上面(即obj),导致触发defineProperty拦截。
// 如果Reflect.set没有传入receiver,那么就不会触发 defineProperty拦截]。

// 注意: 如果 Proxy对象和 Reflect对象联合使用,前者拦截赋值操作,后者完成赋值的默认行为,而且
// 传入了receiver,那么Reflect.set会触发 Proxy.defineProperty拦截。
-----------------------------------------------------------
Reflect.set(1, 'foo', {}) // 报错
Reflect.set(false, 'foo', {}) // 报错

/*
* Reflect.has(target, name): 作用与 in 操作符 相同 (对应 name in target里面的 in运算符)。
* 参数: 1.target: 目标对象; 2.name: 属性名。
* 返回值: 布尔值。(表明 target是否存在name属性)
* 注意: 1.如果Reflect.has()方法的第一个参数不是对象,会报错。
*/
var myObject = {
    foo: 1,
};
// 旧写法
console.log('foo' in myObject); // true
// 新写法
console.log(Reflect.has(myObject, 'foo')); // true
-----------------------------------------------------------
console.log(Reflect.has({ x: 0 }, 'y')); // false

/*
* Reflect.deleteProperty(target, name): 等同于 delete obj[name],用于删除对象的属性。
* 参数: 1.target: 目标对象; 2.name: 需要删除的属性。
* 返回值: 布尔值。(表明 target中 name属性是否被成功删除)
* 注意: 1.如果 Reflect.deleteProperty()方法的第一个参数不是对象,会报错。
*/
const myObj = { foo: 'bar' };
// 旧写法
console.log(delete myObj.foo); // true
// 新写法
console.log(Reflect.deleteProperty(myObj, 'foo')); // true

/*
* Reflect.ownKeys(target): 用于返回对象的所有属性,基本等同于 Object.getOwnPropertyNames
* 与 Object.getOwnPropertySymbols之和。
* 参数: 1.target: 目标对象。
* 返回值: 目标对象的自身属性键组成的数组。
* 注意: 1.如果 Reflect.ownKeys()方法的第一个参数不是对象,会报错。
*/
var myObject = {
    foo: 1,
    bar: 2,
    [Symbol.for('baz')]: 3,
    [Symbol.for('bing')]: 4,
};
// 旧写法
console.log(Object.getOwnPropertyNames(myObject)); // ['foo', 'bar']
console.log(Object.getOwnPropertySymbols(myObject)); // [Symbol(baz), Symbol(bing)]
// 新写法
console.log(Reflect.ownKeys(myObject)); // ['foo', 'bar', Symbol(baz), Symbol(bing)]

/*
* Reflect.getOwnPropertyDescriptor(target, name): 基本等同于 Object
* .getOwnPropertyDescriptor,用于得到指定属性的描述对象,将来会替代掉后者。
* 参数: 1.target: 目标对象; 2.name: 属性名。
* 返回值: 如果属性存在于给定的目标对象中,则返回属性描述符;否则,返回 undefined。
* 注意: 1.Reflect.getOwnPropertyDescriptor和 Object.getOwnPropertyDescriptor的一个区别
* 是,如果第一个参数不是对象,Object.getOwnPropertyDescriptor(1, 'foo')不报错,返回
* undefined,而 Reflect.getOwnPropertyDescriptor(1, 'foo')会抛出错误,表示参数非法。
*/
var myObject = {};
Object.defineProperty(myObject, 'hidden', {
    value: true,
    enumerable: false,
});
// 旧写法
var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden');
// 新写法
var theDescriptor2 = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');
console.log(theDescriptor); 
// {value: true, writable: false, enumerable: false, configurable: false}
console.log(theDescriptor2); 
// {value: true, writable: false, enumerable: false, configurable: false}

/*
* Reflect.defineProperty(target, name, desc): 基本等同于 Object.defineProperty,用来为
* 对象定义属性。未来,后者会被逐渐废除,请从现在开始就使用 Reflect.defineProperty代替它。
* 参数: 1.target: 目标对象; 2.name: 要定义或修改的属性的名称; 3.desc: 要定义或修改的属性
* 的描述。
* 返回值: 布尔值。(表明属性是否被成功定义)
* 注意: 1.如果 Reflect.defineProperty的第一个参数不是对象,就会抛出错误。
*/
function MyDate() {
    /*…*/
}
// 旧写法
Object.defineProperty(MyDate, 'now', {
    value: () => Date.now()
});
// 新写法
Reflect.defineProperty(MyDate, 'now', {
    value: () => Date.now()
});
console.log(MyDate.now); // () => Date.now()
-----------------------------------------------------------
Reflect.defineProperty(1, 'foo'); // 报错 (第一个参数不是对象)
-----------------------------------------------------------
const p = new Proxy({}, {
    defineProperty(target, prop, descriptor) {
        console.log(descriptor); 
        return Reflect.defineProperty(target, prop, descriptor);
    }
});

p.foo = 'bar'; // {value: "bar", writable: true, enumerable: true, configurable: true}
console.log(p.foo); // "bar"
// Proxy.defineProperty对属性赋值设置了拦截,然后使用 Reflect.defineProperty完成了赋值。

/*
* Reflect.preventExtensions(target): Reflect.preventExtensions对应 Object
* .preventExtensions方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功。
* 参数: 1.target: 目标对象。
* 返回值: 布尔值。(表明目标对象是否成功被设置为不可扩展)
* 注意: 1.如果参数不是对象,Object.preventExtensions在 ES5环境报错,在 ES6环境返回传入的参
* 数,而 Reflect.preventExtensions会报错。
*/
var myObject = {};
// 旧写法
console.log(Object.preventExtensions(myObject)); // {}
// 新写法
console.log(Reflect.preventExtensions(myObject)); // true
-----------------------------------------------------------
// ES5 环境
console.log(Object.preventExtensions(1)); // 报错
// ES6 环境
console.log(Object.preventExtensions(1)); // 1
// 新写法
console.log(Reflect.preventExtensions(1)); // 报错

/*
* Reflect.getPrototypeOf(target): Reflect.getPrototypeOf方法用于读取对象的__proto__属
* 性,对应 Object.getPrototypeOf(target)。
* 参数: 1.target: 目标对象。
* 返回值: 给定对象的原型。如果给定对象没有继承的属性,则返回 null。
* 注意: Reflect.getPrototypeOf和Object.getPrototypeOf的一个区别是,如果参数不是对象,
* Object.getPrototypeOf 会将这个参数转为对象,然后再运行,而Reflect.getPrototypeOf会报错。
*/
function FancyThing(){};
const myObj = new FancyThing();
console.log(FancyThing.prototype); // {constructor: ƒ}
// 旧写法
console.log(Object.getPrototypeOf(myObj) === FancyThing.prototype); // true
// 新写法
console.log(Reflect.getPrototypeOf(myObj) === FancyThing.prototype); // true
-----------------------------------------------------------
console.log(Object.getPrototypeOf(1)); 
// Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}
console.log(Reflect.getPrototypeOf(1)); 
// 报错
-----------------------------------------------------------
console.log(Reflect.getPrototypeOf({})); // Object.prototype
console.log(Object.prototype); 
// {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ,
// hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(Reflect.getPrototypeOf(Object.prototype)); // null
console.log(Reflect.getPrototypeOf(Object.create(null))); // null

/*
* Reflect.isExtensible(target): Reflect.isExtensible方法对应 Object.isExtensible,返回
* 一个布尔值,表示当前对象是否可扩展。
* 参数: 1.target: 目标对象。
* 返回值: 布尔值。(表明该对象是否可扩展)
* 注意: 1.如果参数不是对象,Object.isExtensible会返回false,因为非对象本来就是不可扩展的,而
* Reflect.isExtensible会报错。
*/
const myObject = {};
// 旧写法
console.log(Object.isExtensible(myObject)); // true
// 新写法
console.log(Reflect.isExtensible(myObject)); // true
-----------------------------------------------------------
console.log(Object.isExtensible(1)); // false
console.log(Reflect.isExtensible(1)); // 报错

/*
* Reflect.setPrototypeOf(target, newProto): Reflect.setPrototypeOf方法用于设置目标对
* 象的原型(prototype),对应Object.setPrototypeOf(obj, newProto)方法。它返回一个布尔值,表
* 示是否设置成功。
* 参数: 1.target: 目标对象; 2.newProto: 对象的新原型(一个对象或 null)。
* 返回值: 布尔值。(表明原型是否已经设置成功)
* 注意: 1.如果无法设置目标对象的原型(比如,目标对象禁止扩展),Reflect.setPrototypeOf方法返
* 回 false; 2.如果第一个参数不是对象,Object.setPrototypeOf会返回第一个参数本身,而 Reflect
* .setPrototypeOf会报错; 3.如果第一个参数是 undefined或 null,Object.setPrototypeOf和
* Reflect.setPrototypeOf都会报错。
*/
const myObj = {};
// 旧写法
console.log(Object.setPrototypeOf(myObj, Array.prototype)); // Array {}
// 新写法
console.log(Reflect.setPrototypeOf(myObj, Array.prototype)); // true
console.log(myObj.length); // 0
-----------------------------------------------------------
console.log(Reflect.setPrototypeOf({}, null)); // true
console.log(Reflect.setPrototypeOf(Object.freeze({}), null));
// false (目标对象禁止扩展,Reflect.setPrototypeOf方法返回 false)
-----------------------------------------------------------
console.log(Object.setPrototypeOf(1, {})); // 1 (第一个参数不是对象)
console.log(Reflect.setPrototypeOf(1, {}));
// TypeError: Reflect.setPrototypeOf called on non-object
-----------------------------------------------------------
console.log(Object.setPrototypeOf(null, {})); // 第一个参数是 null
// TypeError: Object.setPrototypeOf called on null or undefined
console.log(Reflect.setPrototypeOf(null, {}));
// TypeError: Reflect.setPrototypeOf called on non-object

/*
* Reflect.apply(target, thisArg, args): 等同于 Function.prototype.apply.call(target, 
* thisArg, args),用于绑定 this对象后执行给定函数。
* 参数: 1.target: 目标函数; 2.thisArg: target函数调用时绑定的 this对象; 3.args: target函
* 数调用时传入的实参列表,该参数应该是一个类数组的对象。
* 返回值: 调用完带着指定参数和 this值的给定的函数后返回的结果。
* 注意: 1.一般来说,如果要绑定一个函数的 this对象,可以这样写 fn.apply(obj, args),但是如果
* 函数定义了自己的 apply方法,就只能写成 Function.prototype.apply.call(fn, obj, args),采
* 用 Reflect对象可以简化这种操作。
*/
const ages = [11, 33, 12, 54, 18, 96];
// 旧写法
const youngest = Math.min.apply(Math, ages);
const oldest = Math.max.apply(Math, ages);
const type = Object.prototype.toString.call(youngest);
// 新写法
const youngest2 = Reflect.apply(Math.min, Math, ages);
const oldest2 = Reflect.apply(Math.max, Math, ages);
const type2 = Reflect.apply(Object.prototype.toString, youngest, []);
console.log(youngest); // 11
console.log(oldest); // 96
console.log(type); // [object Number]
console.log(youngest2); // 11
console.log(oldest2); // 96
console.log(type2); // [object Number]

/*
* Reflect.construct(target, args): 等同于 new target(...args),这提供了一种不使用 new,来
* 调用构造函数的方法。
* 参数: 1.target: 目标构造函数; 2.args: 类数组,目标构造函数调用时的参数。
* 返回值: 对象。[以target(如果 newTarget存在,则为 newTarget)函数为构造函数,argumentList
* 为其初始化参数的对象实例)]。
* 注意: 1.如果 Reflect.construct()方法的第一个参数不是函数,会报错。
*/
function Greeting(name) {
    this.name = name;
}
// new 的写法
const instance = new Greeting('张三');
// Reflect.construct 的写法
const instance2 = Reflect.construct(Greeting, ['张三']);
console.log(instance); // Greeting {name: '张三'}
console.log(instance2); // Greeting {name: '张三'}

3.module

模块化:是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。

模块化的优点:1. 防止命名冲突;2. 代码复用;3. 高维护性。

模块化方案:

在 es6之前,最主要的模块化规范有:

  • Commonjs:适用于服务器端。实现此规范的产品有 NodeJS、Browserify。

  • AMD:适用于浏览器端。实现此规范的产品有 requireJS。

  • CMD:适用于浏览器端。实现此规范的产品有 seaJS。 ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

  • export 命令 一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。如果希望外部能够读取模块内部的某个变量,就必须使用 export 关键字输出该变量。

// profile.js
export var firstName = 'Michael';
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)

/*
* 注意: 1.通常情况下,export输出的变量就是本来的名字,但是可以使用 as关键字重命名; 2.export
* 命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系; 3.function和class的输出,也必
* 须遵守这样的写法; 4.export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取
* 到模块内部实时的值; 5.export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于
* 块级作用域内,就会报错,下一节的 import命令也是如此。这是因为处于条件代码块之中,就没法做静态
* 优化了,违背了 ES6模块的设计初衷。
*
*/
export function multiply(x, y) {
    return x * y;
};
-----------------------------------------------------------
function v1() { ... }
function v2() { ... }

export {
    v1 as streamV1,
    v2 as streamV2,
    v2 as streamLatestVersion // 重命名了函数v1和v2的对外接口,v2用不同的名字输出两次。
};
-----------------------------------------------------------
// 报错
export 1;

// 报错
var m = 1; 
export m; // 这两种方式都报错,没有提供对外的接口,都是输出1,不是接口。
-----------------------------------------------------------
// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;        // 这三种方式都规定了对外接口 m,接口 m与模块内部变量建立了一一对应关系,
export {n as m};  // 其他脚本可以通过这个接口,取到值 1。
-----------------------------------------------------------
// 报错
function f() {}
export f;

// 正确
export function f() {};

// 正确
function f() {}
export {f};
-----------------------------------------------------------
export var foo = 'bar'; // 输出变量 foo,值为 bar,500毫秒之后变成 baz
setTimeout(() => foo = 'baz', 500);
-----------------------------------------------------------
function foo() {
    export default 'bar' // SyntaxError (export命令处于块级作用域)
}
foo()
  • import 命令 使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
/*
* 注意: 1.如果想为输入的变量重新取一个名字,import命令要使用 as关键字,将输入的变量重命名; 2.
* import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面
* ,改写接口; 3.import后面的 from指定模块文件的位置,可以是相对路径,也可以是绝对路径。如果不
* 带有路径,只是一个模块名,那么必须有配置文件,告诉 JavaScript引擎该模块的位置; 4.import命令
* 具有提升效果,会提升到整个模块的头部,首先执行; 5.由于 import是静态执行,所以不能使用表达式和
* 变量,这些只有在运行时才能得到结果的语法结构; 6.import语句会执行所加载的模块,如果多次重复
* 执行同一句import语句,那么只会执行一次,而不会执行多次;
*/
// main.js
import { firstName, lastName, year } from './profile.js'; // 大括号里面(即导入)的变量名
                                                          // ,必须与被导入模块(profile
function setName(element) {                               // .js)对外接口的名称相同。
    element.textContent = firstName + ' ' + lastName;
}
-----------------------------------------------------------
import { lastName as surname } from './profile.js'; // lastName重命名为 surname
-----------------------------------------------------------
import {a} from './xxx.js'

a = {}; // Syntax Error : 'a' is read-only; (a是一个只读接口,对其重新赋值就会报错)
-----------------------------------------------------------
import {a} from './xxx.js'

a.foo = 'hello'; // 合法操作 (如果 a是一个对象,改写 a的属性是允许的,但不建议,很难查错)
-----------------------------------------------------------
import { myMethod } from 'util';
-----------------------------------------------------------
foo();

import { foo } from 'my_module'; // import的执行早于foo的调用。这种行为的本质是,import
                                 // 命令是编译阶段执行的,在代码运行之前。
-----------------------------------------------------------
// 报错
import { 'f' + 'oo' } from 'my_module';

// 报错
let module = 'my_module';
import { foo } from module;

// 报错
if (x === 1) {
  import { foo } from 'module1';
} else {
  import { foo } from 'module2';
}
// 它们用到了表达式、变量和 if结构。在静态分析阶段,这些语法都是没法得到值的。
-----------------------------------------------------------
import 'lodash'; // 执行lodash模块,但是不输入任何值
-----------------------------------------------------------
import 'lodash';
import 'lodash'; // 加载了两次lodash,但是只会执行一次
  • 模块的整体加载 除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
/*
* 注意: 1.模块整体加载所在的那个对象,应该是可以静态分析的,所以不允许运行时改变。
*/
// circle.js
export function area(radius) {
    return Math.PI * radius * radius;
}
export function circumference(radius) {
    return 2 * Math.PI * radius;
}

// main.js
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));

import * as circle from './circle'; // 整体加载的写法
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
-----------------------------------------------------------
import * as circle from './circle';

// 下面两行都是不允许的
circle.foo = 'hello';
circle.area = function () {};
  • export default 命令 export default命令,为模块指定默认输出。
/*
* 注意: 1.export default命令用在非匿名函数前,也是可以的; 2.export default命令用于指定模块
* 的默认输出。显然,一个模块只能有一个默认输出,因此 export default命令只能使用一次。所以,
* import命令后面才不用加大括号,因为只可能唯一对应 export default命令; 3.本质上, export
* default就是输出一个叫做 default的变量或方法,然后系统允许你为它取任意名字; 4.正是因为 export
* default命令其实只是输出一个叫做 default的变量,所以它后面不能跟变量声明语句; 5.因为 export
* default命令的本质是将后面的值,赋给default变量,所以可以直接将一个值写在 export default之
* 后; 6.import语句中,可以同时输入默认方法和其他接口。
*/
// export-default.js
export default function () { // 默认输出一个函数
    console.log('foo');
}

// import-default.js
import customName from './export-default'; // 可以为该匿名函数指定任意名字
customName(); // 'foo'
-----------------------------------------------------------
// export-default.js
export default function foo() {
    console.log('foo');
}
  
// 或者写成
function foo() {
    console.log('foo');
}
export default foo; 
// foo函数的函数名 foo,在模块外部是无效的。加载的时候,视同匿名函数加载。
-----------------------------------------------------------
// modules.js
function add(x, y) {
    return x * y;
}
export {add as default};
// 等同于
// export default add;

// app.js
import { default as foo } from 'modules';
// 等同于
// import foo from 'modules';
-----------------------------------------------------------
// 正确
export var a = 1;

// 正确
var a = 1;
export default a; // 将变量 a的值赋给变量 default

// 错误
export default var a = 1;
-----------------------------------------------------------
// 正确
export default 42;

// 报错
export 42;
-----------------------------------------------------------
export default function (obj) {
    // ···
}
export function each(obj, iterator, context) {
    // ···
}
export { each as forEach }; // 暴露出 forEach接口,默认指向 each接口,即 forEach和 each
                            // 指向同一个方法。
import _, { each, forEach } from 'lodash';

export default也可以用来输出类。

// MyClass.js
export default class { ... }

// main.js
import MyClass from 'MyClass';
let o = new MyClass();
  • export 与 import 的复合写法 如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。
/*
* 注意: 1.模块的接口改名和整体输出,也可以采用这种写法;
*
*/
export { foo, bar } from 'my_module'; // 写成一行以后,foo和 bar实际上并没有被导入当前模
                                      // 块,只是相当于对外转发了这两个接口,导致当前模块
                                      // 不能直接使用 foo和 bar。
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
-----------------------------------------------------------
// 接口改名
export { foo as myFoo } from 'my_module';

// 整体输出
export * from 'my_module';
-----------------------------------------------------------
export { default } from 'foo'; // 默认接口的写法
-----------------------------------------------------------
export { es6 as default } from './someModule'; // 具名接口改为默认接口的写法

// 等同于
import { es6 } from './someModule';
export default es6;
-----------------------------------------------------------
export { default as es6 } from './someModule'; // 默认接口也可以改名为具名接口
-----------------------------------------------------------
export * as ns from "mod"; // es2020补上了这个写法

// 等同于
import * as ns from "mod";
export {ns};
  • 跨模块常量 const声明的常量只在当前代码块有效。如果想设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法。
// 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

如果要使用的常量非常多,可以建一个专门的constants目录,将各种常量写在不同的文件里面,保存在该目录下。

// constants/db.js
export const db = {
    url: 'http://my.couchdbserver.local:5984',
    admin_username: 'admin',
    admin_password: 'admin password'
};
// constants/user.js
export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];

// constants/index.js    (然后,将这些文件输出的常量,合并在 index.js里面)
export {db} from './db';
export {users} from './users';

// script.js    (使用的时候,直接加载 index.js就可以了)
import {db, users} from './constants/index';
  • import() 前面介绍过,import命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行(import命令叫做“连接” binding 其实更合适)。所以,下面的代码会报错。
// 报错
if (x === 2) {                          // 引擎处理 import语句是在编译时,这时不会去分析
    import MyModual from './myModual';  // 或执行 if语句,所以 import语句放在 if代码块之
}                                       // 中毫无意义,因此会报句法错误,而不是执行时错误。

这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能。

const path = './' + fileName;
const myModual = require(path); // 动态加载,require到底加载哪一个模块,只有运行时才知道。

ES2020提案 引入import()函数,支持动态加载模块,返回一个 Promise 对象。

const main = document.querySelector('main');

import(`./section-modules/${someVariable}.js`)
    .then(module => {
        module.loadPageInto(main);
    })
    .catch(err => {
        main.textContent = err.message;
    });

import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。另外,import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同。import()类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载。

import() 适用场景:1.按需加载;2.条件加载;3.动态的模块路径。

button.addEventListener('click', event => {
    import('./dialogBox.js') // 只有用户点击了按钮,才会加载这个模块。
        .then(dialogBox => {
            dialogBox.open();
        })
        .catch(error => {
            /* Error handling */
        })
    });
-----------------------------------------------------------
if (condition) { // 如果满足条件,就加载模块 A,否则加载模块 B
    import('moduleA').then(...);
} else {
    import('moduleB').then(...);
}
-----------------------------------------------------------
import(f()) // 根据函数 f的返回结果,加载不同的模块
.then(...);
-----------------------------------------------------------
/*
* 注意: 1.import()加载模块成功以后,这个模块会作为一个对象,当作 then方法的参数。因此,可以使
* 用对象解构赋值的语法,获取输出接口; 2.可以同时加载多个模块; 3.import()也可以用在 async 
* 函数之中。
*/
import('./myModule.js')
    .then(({export1, export2}) => {
        // ...·
    });

Promise.all([
    import('./module1.js'),
    import('./module2.js'),
    import('./module3.js'),
])
.then(([module1, module2, module3]) => {
    ···
});

async function main() {
    const myModule = await import('./myModule.js');
    const {export1, export2} = await import('./myModule.js');
    const [module1, module2, module3] =
        await Promise.all([
            import('./module1.js'),
            import('./module2.js'),
            import('./module3.js'),
        ]);
}
main();