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)变量声明语句(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实例注意事项
- get 方法可以继承。
- 如果一个属性不可配置(configurable)和不可写(writable),则该属性不能被代理,通过 Proxy 对象访问该属性会报错。
- apply 方法拦截函数的调用、 call 和 apply 操作。
- has 方法用来拦截 HasProperty 操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是 in 运算符。
- construct 方法用于拦截 new 命令。
- 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 上获取默认行为。