《ECMAScript 6》简记(上)

134 阅读8分钟

Babel 转码器

概念:Babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码。

  • 配置文件:.babelrc;该文件用来设置转码规则和插件,基本格式如下。
{
  "presets":[],
  "plugins":[]
}
  • 命令行转码 babel-cli自带工具babel-node,提供一个支持ES6的REPL环境。它支持Node的REPL环境的所有功能,而且可以直接运行ES6代码。
  • babel-register:babel-register 模块改写 require 命令,为它加上一个钩子。此后,每当使 用 require 加载 .js 、 .jsx 、 .es 和 .es6 后缀名的文件,就会先用Babel进行转码。
    //使用前必须必须首先加载 babel-register 转码
require("babel-register");
require("./index.js");

如果某些代码需要调用 Babel 的 API 进行转码,就要使用 babel-core 模块。 需要注意的是, babel-register 只会对 require 命令加载的文件转码,而不会对当前文件转码。另外,由于它是实时转码,所以只适合在开发环境使用。

  • babel-core

如果某些代码需要调用 Babel 的 API 进行转码,就要使用 babel-core 模块。

var es6Code = 'let x = n => n + 1';
var es5Code = require('babel-core')
.transform(es6Code, {
presets: ['latest']
}).code;
// '"use strict";\n\nvar x = function x(n) {\n return n + 1;\n};

上面代码中, transform 方法的第一个参数是一个字符串,表示需要被转换的ES6 代码,第二个参数是转换的配置对象

  • babel-polyfill

Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,比如 Iterator 、 Generator 、 Set 、 Maps 、 Proxy 、 Reflect 、 Symbol、 Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign )都不会转码。举例来说,ES6 在 Array 对象上新增了 Array.from 方法。Babel 就不会转码这个方法。如果想让这个方法运行,必须使用 babel-polyfill ,为当前环境提供一个垫片。

Babel 默认不转码的 API 非常多,详细清单可以查看 babel-plugin-transform.runtime 模块的definitions.js文件。

let和const 命令

let和const都是ES6的新的命令用于取代var,详细内容let和const

ES6共有6种申明方法,let,const,var,import,class,function

顶层对象

顶层对象ES5为window,ES6新增了顶层对象global,解决了除了浏览器以外Node环境没有顶层对象的问题

// CommonJS 的写法
var global = require('system.global')();

// ES6 模块的写法
import getGlobal from 'system.global';
const global = getGlobal();

解构赋值

数组解构赋值:

//ES5
var a=1,b=2,c=3

//ES6解构
const [a,b,c]=[1,2,3]

对象解构:

//ES5
 var brid={
 fly:true,
 run:false
 }
 
 console.log(brid.fly,brid.run)

//ES6解构
const brid={
 fly:true,
 run:false
 }
 const {fly,run}=brid
 console.log(fly,run)

字符串解构:

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
类似字符串数组还有一个属性length是可以解构的
let {length : len} = 'hello';
len // 5

注意解构的时候如果没有该对象对应的值的时候,默认undefined

圆括号问题

  1. 不能使用圆括号

(1)变量声明语句(2)函数参数 (3)赋值语句的模式 2. 可以使用圆括号的情况

字符串的扩展

1.字符的 Unicode 表示法

一个字符串有6种表示法

'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

字符串的遍历器接口

for (let codePoint of 'foo') {

console.log(codePoint)

}

includes(), startsWith(), endsWith(),repeat(),padStart(),padEnd()

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
  • repeat():重复n次字符串
  • padStart():如果某个字符串不够指定长度,会在头部补全。
  • padEnd() 如果某个字符串不够指定长度,会在尾部补全

模板字符串

模板字符串(template string)是增强版的字符串,用反引号(``)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

$('#result').append(`

There are <b>${basket.count}</b> items

in your basket, <em>${basket.onSale}</em>

are on sale!

`);

正则的扩展

RegExp 构造函数

var regex = new RegExp('xyz', 'i');

// 等价于

var regex = /xyz/i;

字符串的正则方法

字符串对象共有4个方法,可以使用正则表达

式: match() 、 replace() 、 search() 和 split() 。

ES6 将这4个方法,在语言内部全部调用 RegExp 的实例方法,从而做到所有与正

则相关的方法,全都定义在 RegExp 对象上。

  • String.prototype.match 调用 RegExp.prototype[Symbol.match]

  • String.prototype.replace 调用 RegExp.prototype[Symbol.replace]

  • String.prototype.search 调用 RegExp.prototype[Symbol.search]

  • String.prototype.split 调用 RegExp.prototype[Symbol.split]

flags属性

ES6 为正则表达式新增了 flags 属性,会返回正则表达式的修饰符。

// ES5 的 source 属性

// 返回正则表达式的正文

/abc/ig.source

// "abc"

// ES6 的 flags 属性

// 返回正则表达式的修饰符

/abc/ig.flags

// 'gi'

数值的扩展

Number.isFinite(), Number.isNaN()

ES6 在 Number 对象上,新提供了 Number.isFinite() 和 Number.isNaN() 两

个方法。Number.isFinite() 用来检查一个数值是否为有限的(finite)。

Number.isFinite(15); // true

Number.isFinite(0.8); // true

Number.isFinite(NaN); // false

Number.isFinite(Infinity); // false

Number.isFinite(-Infinity); // false

Number.isFinite('foo'); // false

Number.isFinite('15'); // false

Number.isFinite(true); // false

Number.isNaN() 用来检查一个值是否为 NaN 。

Number.isNaN(NaN) // true

Number.isNaN(15) // false

Number.isNaN('15') // false

Number.isNaN(true) // false

Number.isNaN(9/NaN) // true

Number.isNaN('true'/0) // true

Number.isNaN('true'/'true') // true

Number.EPSILON

ES6在Number对象上面,新增一个极小的常量 Number.EPSILON 。

Number.EPSILON

// 2.220446049250313e-16

Number.EPSILON.toFixed(20)

// '0.00000000000000022204'

Math.trunc()

Math.trunc 方法用于去除一个数的小数部分,返回整数部分。

Math.trunc(4.1) // 4

Math.trunc(4.9) // 4

Math.trunc(-4.1) // -4

Math.trunc(-4.9) // -4

Math.trunc(-0.1234) // -0

Math.cbrt()

Math.cbrt 方法用于计算一个数的立方根。

Math.cbrt(-1) // -1

Math.cbrt(0) // 0

Math.cbrt(1) // 1

Math.cbrt(2) // 1.2599210498948734

箭头函数

基本用法: ES6 允许使用“箭头”( => )定义函数。

var f = v => v;
//等同于
function v(){
   return v
}

箭头函数的一个用处是简化回调函数。

使用注意点

箭头函数有几个使用注意点。

(1)函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。

(3)不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。

上面四点中,第一点尤其值得注意。 this 对象的指向是可变的,但是在箭头函数中,它是固定的。

箭头函数转成 ES5 的代码如下。

// ES6

function foo() {

setTimeout(() => {

console.log('id:', this.id);

}, 100);

}

// ES5

function foo() {

var _this = this;

setTimeout(function () {

console.log('id:', _this.id);

}, 100);

}

上面代码中,转换后的ES5版本清楚地说明了,箭头函数里面根本没有自己的 this ,而是引用外层的 this 。

绑定 this

箭头函数可以绑定 this 对象,大大减少了显式绑定 this 对象的写法( call 、 apply 、 bind )。但是,箭头函数并不适用于所有场合,所以ES7提出了“函数绑定”(function bind)运算符,用来取代 call 、 apply 、 bind 调用。虽然该语法还是ES7的一个提案,但是Babel转码器已经支持。函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。

foo::bar;

// 等同于

bar.bind(foo);

foo::bar(...arguments);

// 等同于

bar.apply(foo, arguments);

const hasOwnProperty = Object.prototype.hasOwnProperty;

function hasOwn(obj, key) {

return obj::hasOwnProperty(key);

}

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。

var method = obj::obj.foo;

// 等同于

var method = ::obj.foo;

let log = ::console.log;

// 等同于

var log = console.log.bind(console);

由于双冒号运算符返回的还是原对象,因此可以采用链式写法。

// 例一

import { map, takeWhile, forEach } from "iterlib";

getPlayers()

::map(x => x.character())

::takeWhile(x => x.strength > 100)

::forEach(x => console.log(x));

// 例二

let { find, html } = jake;

document.querySelectorAll("div.myClass")

::find("p")

::html("hahaha");

尾递归

函数调用自身,称为递归。如果尾调用自身,就称为尾递归。递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

function factorial(n) {

if (n === 1) return 1;

return n * factorial(n - 1);

}

factorial(5) // 120

上面代码是一个阶乘函数,计算 n 的阶乘,最多需要保存 n 个调用记录,复杂度O(n) 。如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。

function factorial(n, total) {

if (n === 1) return total;

return factorial(n - 1, n * total);

}

factorial(5, 1) // 120

数组的扩展

扩展运算符

扩展运算符(

spread)是三个点( ... )。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

console.log(...[1, 2, 3])

// 1 2 3

扩展运算符的应用

(1)合并数组

// ES5

[1, 2].concat(more)

// ES6

[1, 2, ...more]

(2)与解构赋值结合

// ES5

a = list[0], rest = list.slice(1)

// ES6

[a, ...rest] = list

(3)函数的返回值

var dateFields = readDateFields(database);

var d = new Date(...dateFields);

(4)字符串

[...'hello']

// [ "h", "e", "l", "l", "o" ]

(5)Map 和 Set 结构,Generator 函数

扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。

let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]

//Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。
var go = function*(){
yield 1;
yield 2;
yield 3;
};
[...go()] // [1, 2, 3]

Array.from()

Array.from 方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。

下面是一个类似数组的对象, Array.from 将它转为真正的数组。

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); // ['a', 'b', 'c']

实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的 arguments 对象。 Array.from 都可以将它们转为真正的数组。

// NodeList对象

let ps = document.querySelectorAll('p');

Array.from(ps).forEach(function (p) {

console.log(p);

});

// arguments对象

function foo() {

var args = Array.from(arguments);

// ...

}

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]

Array.of()

Array.of 基本上可以用来替代 Array() 或 new Array() ,并且不存在由于参数不同而导致的重载。它的行为非常统一。

Array.of() // []

Array.of(undefined) // [undefined]

Array.of(1) // [1]

Array.of(1, 2) // [1, 2]

find()和findIndex()

[1, 5, 10, 15].find(function(value, index, arr) {

return value > 9;

}) // 10

数组实例的fill()

['a', 'b', 'c'].fill(7)

// [7, 7, 7]

new Array(3).fill(7)

// [7, 7, 7]

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

ES6 提供三个新的方法—— entries() , keys() 和 values() ——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用 for...of 循环进行遍历,唯一的区别是 keys() 是对键名的遍历、 values() 是对键值的遍历, entries() 是对键值对的遍历。

for (let index of ['a', 'b'].keys()) {

console.log(index);

}

// 0

// 1

for (let elem of ['a', 'b'].values()) {

console.log(elem);

}

// 'a'

// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {

console.log(index, elem);

}

// 0 "a"

// 1 "b"

数组实例的 includes()

Array.prototype.includes 方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes 方法类似。ES2016 引入了该方法。

[1, 2, 3].includes(2) // true

[1, 2, 3].includes(4) // false

[1, 2, NaN].includes(NaN) // true

数组的空位

ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。forEach() , filter() , every() 和 some() 都会跳过空位。map() 会跳过空位,但会保留这个值join() 和 toString() 会将空位视为 undefined ,而 undefined 和 null 会被处理成空字符串

Object.is()

ES5 比较两个值是否相等,只有两个运算符:相等运算符( == )和严格相等运算符( === )。它们都有缺点,前者会自动转换数据类型,后者的 NaN 不等于自身,以及 +0 等于 -0 。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。 Object.is 就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

Object.is('foo', 'foo')
// true

Object.is({}, {})
// false

+0 === -0 //true

NaN === NaN // false

Object.is(+0, -0) // false

Object.is(NaN, NaN) // true

Object.assign()

Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

var target = { a: 1 };

var source1 = { b: 2 };

var source2 = { c: 3 };

Object.assign(target, source1, source2);

target // {a:1, b:2, c:3}

Object.assign 方法的第一个参数是目标对象,后面的参数都是源对象。注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

属性的遍历

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

以上的5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则。

  • 首先遍历所有属性名为数值的属性,按照数字排序。
  • 其次遍历所有属性名为字符串的属性,按照生成时间排序。
  • 最后遍历所有属性名为 Symbol 值的属性,按照生成时间排序。

Set和Map数据结构

Set

基本用法

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

Set 本身是一个构造函数,用来生成 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

Map

含义和基本用法

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

const data = {};

const element = document.getElementById('myDiv');

data[element] = 'metadata';

data['[object HTMLDivElement]'] // "metadata"

Map 结构转为数组结构,比较快速的方法是使用扩展运算符( ... )

const map = new Map([

[1, 'one'],

[2, 'two'],

[3, 'three'],

]);

[...map.keys()]

// [1, 2, 3]

[...map.values()]

// ['one', 'two', 'three']

[...map.entries()]

// [[1,'one'], [2, 'two'], [3, 'three']]

[...map]

// [[1,'one'], [2, 'two'], [3, 'three']]

Map 转为对象

如果所有 Map 的键都是字符串,它可以转为对象。

function strMapToObj(strMap) {

let obj = Object.create(null);

for (let [k,v] of strMap) {

obj[k] = v;

}

return obj;

}

const myMap = new Map()

.set('yes', true)

.set('no', false);

strMapToObj(myMap)

// { yes: true, no: false }

WeakMap

WeakMap 与 Map 的区别有两点。

首先, WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。

其次, WeakMap 的键名所指向的对象,不计入垃圾回收机制。WeakMap 的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。

const e1 = document.getElementById('foo');

const e2 = document.getElementById('bar');

const arr = [

[e1, 'foo 元素'],

[e2, 'bar 元素'],

];

WeakMap 的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。

基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用WeakMap。一个典型应用场景是,在网页的 DOM 元素上添加数据,就可以使用 WeakMap 结构。当该 DOM 元素被清除,其所对应的 WeakMap 记录就会自动被移除。

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

const wm = new WeakMap();

let key = {};

let obj = {foo: 1};

wm.set(key, obj);

obj = null;

wm.get(key)

// Object {foo: 1}

WeakMap 只有四个方法可用: get() 、 set() 、 has() 、 delete() 。

Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

var obj = new Proxy({}, {

get: function (target, key, receiver) {

console.log(`getting ${key}!`);

return Reflect.get(target, key, receiver);

},

set: function (target, key, value, receiver) {

console.log(`setting ${key}!`);

return Reflect.set(target, key, value, receiver);

}

});

Proxy 对象的所有用法,都是上面这种形式,不同的只是 handler 参数的写法。其中, new Proxy() 表示生成一个 Proxy 实例, target 参数表示所要拦截的目标对象, handler 参数也是一个对象,用来定制拦截行为。

下面是另一个拦截读取属性行为的例子。

var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

上面代码中,作为构造函数, Proxy 接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有 Proxy 的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个 get 方法,用来拦截对目标对象属性的访问请求。 get 方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回 35 ,所以访问任何属性都得到 35 。

注意,要使得 Proxy 起作用,必须针对 Proxy 实例(上例是 proxy 对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。如果 handler 没有设置任何拦截,那就等同于直接通向原对象。

Proxy 实例也可以作为其他对象的原型对象。

var proxy = new Proxy({}, {

get: function(target, property) {

return 35;

}

});

let obj = Object.create(proxy);

obj.time // 35

上面代码中, proxy 对象是 obj 对象的原型, obj 对象本身并没有 time 属性,所以根据原型链,会在 proxy 对象上读取该属性,导致被拦截。

同一个拦截器函数,可以设置拦截多个操作。

下面是 Proxy 支持的拦截操作一览。对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。

(1)get(target, propKey, receiver)

拦截对象属性的读取,比如 proxy.foo 和 proxy['foo'] 。最后一个参数 receiver 是一个对象,可选,参见下面 Reflect.get 的部分。

(2)set(target, propKey, value, receiver)

拦截对象属性的设置,比如 proxy.foo = v 或 proxy['foo'] = v ,返回一个布尔值。

(3)has(target, propKey)

拦截 propKey in proxy 的操作,返回一个布尔值。

(4)deleteProperty(target, propKey)

拦截 delete proxy[propKey] 的操作,返回一个布尔值。

(5)ownKeys(target)

拦截 Object.getOwnPropertyNames(proxy) 、 Object.getOwnPropertySymbols(proxy) 、 Object.keys(proxy) ,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。

(6)getOwnPropertyDescriptor(target, propKey)

拦截 Object.getOwnPropertyDescriptor(proxy, propKey) ,返回属性的描述对象。

(7)defineProperty(target, propKey, propDesc)

拦截 Object.defineProperty(proxy, propKey,propDesc) 、 Object.defineProperties(proxy, propDescs) ,返回一个布尔值。

(8)preventExtensions(target)

拦截 Object.preventExtensions(proxy) ,返回一个布尔值。

(9)getPrototypeOf(target)

拦截 Object.getPrototypeOf(proxy) ,返回一个对象。

(10)isExtensible(target)

拦截 Object.isExtensible(proxy) ,返回一个布尔值。

(11)setPrototypeOf(target, proto)

拦截 Object.setPrototypeOf(proxy, proto) ,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。

(12)apply(target, object, args)

拦截 Proxy 实例作为函数调用的操作,比如 proxy(...args) 、 proxy.call(object,...args) 、 proxy.apply(...) 。

(13)construct(target, args)

拦截 Proxy 实例作为构造函数调用的操作,比如 new proxy(...args) 。

Proxy实例注意事项

  1. get 方法可以继承。
  2. 如果一个属性不可配置(configurable)和不可写(writable),则该属性不能被代理,通过 Proxy 对象访问该属性会报错。
  3. apply 方法拦截函数的调用、 call 和 apply 操作。
  4. has 方法用来拦截 HasProperty 操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是 in 运算符。
  5. construct 方法用于拦截 new 命令。
  6. deleteProperty 方法用于拦截 delete 操作,如果这个方法抛出错误或者返回 false ,当前属性就无法被 delete 命令删除。

Reflect

Reflect 对象与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新API。 Reflect 对象的设计目的有这样几个。

(1) 将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty ),放到 Reflect 对象上。现阶段,某些方法同时在 Object 和 Reflect 对象上部署,未来的新方法将只部署在 Reflect 对象上。也就是说,从 Reflect 对象上可以拿到语言内部的方法。

(2) 修改某些 Object 方法的返回结果,让其变得更合理。比如, Object.defineProperty(obj, name, desc) 在无法定义属性时,会抛出一个错误,而 Reflect.defineProperty(obj, name, desc) 则会返回 false。

(3) 让 Object 操作都变成函数行为。某些 Object 操作是命令式,比如 name in obj 和 delete obj[name] ,而 Reflect.has(obj,name) 和 Reflect.deleteProperty(obj, name) 让它们变成了函数行为。

(4) Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。这就让 Proxy 对象可以方便地调用对应的 Reflect 方法,完成默认行为,作为修改行为的基础。也就是说,不管 Proxy 怎么修改默认行为,你总可以在 Reflect 上获取默认行为。