Proxy与Object.defineProperty的对比
Proxy
可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy
这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
Object.defineProperty
是使用的数据劫持:直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。数据劫持最典型的应用 -----> 双向的数据绑定(一个常用的面试题),
-
Vue 2.x
利用Object.defineProperty()
,并且把内部解耦为Observer
,Dep
, 并使用Watcher
相连 -
Vue
在3.x
版本之后改用Proxy
进行实现 -
Object.defineProperty
-
只能监听对象(
Object
),不能监听数组的变化,无法触发push
,pop
,shift
,unshift
,splice
,sort
,reverse
。 -
必须遍历对象的每个属性
-
只能劫持当前对象属性,如果想深度劫持,必须深层遍历嵌套的对象
"use strict" let obj = {}; let value = 1 Object.defineProperty(obj, 'listenA', { writable: true, //可修改 enumerable: true, // 可枚举 for...in... Object.keys() configurable: true, // 可配置,可删除 get: () => value, set: val => { console.log(`set obj.listenA .. ${val}`); value = val }, }); obj.listenA = 2 //set obj.listenA .. 2 console.log(obj.listenA) // 2 复制代码
-
-
Proxy
-
可以直接监听对象而非属性
-
可以直接监听数组的变化
// 代理整个对象 let proxyObj = new Proxy({}, { get: (target, key, receiver) => { console.log(
getting ${key}!
); return target[key]; }, set: (target, key, value, receiver) => { console.log(target, key, value, receiver); return target[key] = value; }, }); proxyObj.val = 1; // {} val 1 {} proxyObj.val; // getting val!//代理数组 let proxyArr = new Proxy([], { get: (target, key, receiver) => { console.log(
getting ${key}!
); return target[key]; }, set: (target, key, value, receiver) => { console.log(target, key, value, receiver); return (target[key] = value); }, }); proxyArr[0] = 1; // {} val 1 {} console.log(proxyArr[0]); // getting val! // 1 console.log(proxyArr); // [1]
-
Reflect
Reflect
翻译过来是反射的意思,与Proxy
对象一样,也是ES6
为了操作对象而提供的新API
。有一下几个作用
-
将
Object
对象的一些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上。现阶段,某些方法同时在Object
和Reflect
对象上部署,未来的新方法将只部署在Reflect
对象上。也就是说,从Reflect
对象上可以拿到语言内部的方法。 -
修改某些
Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。// 老写法 try { Object.defineProperty(target, property, attributes); // success } catch (e) { // failure }
// 新写法 if (Reflect.defineProperty(target, property, attributes)) { // 成功返回true // success } else { // failure } 复制代码
-
让
Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。// 老写法 'assign' in Object // true
// 新写法 Reflect.has(Object, 'assign') // true 复制代码
-
Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。Proxy(target, { set: function(target, name, value, receiver) { var success = Reflect.set(target, name, value, receiver); if (success) { console.log('property ' + name + ' on ' + target + ' set to ' + value); } return success; } });
async function
ES2017
标准引入了async
函数,使得异步操作变得更加方便,由于async函数
返回的是Promise
对象,可以作为await
命令的参数。
async function timeout(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
返回 Promise 对象
async function f() {
return 'hello world';
}
f().then(v => console.log(v))
// "hello world"
复制代码
async
函数内部抛出错误,会导致返回的Promise
对象变为reject
状态。抛出的错误对象会被catch
方法回调函数接收到
async function f() {
throw new Error('出错了');
}
f().then(
v => console.log('resolve', v),
e => console.log('reject', e)
)
//reject Error: 出错了
Promise 对象的状态变化
async
函数返回的Promise
对象,必须等到内部所有await
命令后面的Promise
对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('http://localhost:8080/').then(console.log(123))
await 命令
正常情况下,
await
命令后面是一个Promise
对象,返回该对象的结果。如果不是Promise
对象,就直接返回对应的值。
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
复制代码
任何一个
await
语句后面的Promise
对象变为reject
状态,那么整个async
函数都会中断执行。
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
如果希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个
await
放在try...catch
结构里面,这样不管这个异步操作是否成功,第二个await
都会执行。
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// hello world
async 函数的实现原理
将 Generator 函数和自动执行器,包装在一个函数里
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
严格模式
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上
"use strict"
;
严格模式主要有以下限制。
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用
with
语句 - 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量
delete prop
,会报错,只能删除属性delete global[prop]
eval
不会在它的外层作用域引入变量eval
和arguments
不能被重新赋值arguments
不会自动反映函数参数的变化- 不能使用
arguments.callee
- 不能使用
arguments.caller
- 禁止
this
指向全局对象 - 不能使用
fn.caller
和fn.arguments
获取函数调用的堆栈 - 增加了保留字(比如
protected、static
和interface
)
export && import
用
export
&&import
来进行模块的导出导入。
// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};
// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};
import 'lodash';
import 'lodash'; //加载了两次lodash,但是只会执行一次。
export { foo, bar } from 'my_module'; //当前模块不能直接使用foo和bar
// 接口改名
export { foo as myFoo } from 'my_module';
// 整体输出
export * from 'my_module';
//默认接口
export { default } from 'foo';
export { default as es6 } from './someModule';