背景
校招前端面试必问问题之一:vue 双向绑定原理。
- 前端小白:
wt?我怎么知道?不是会用就可以了嘛?我管它怎么实现。 - 看过一些些面经:
vue双向绑定是通过数据劫持实现的,通过劫持对象的getter和setter实现。 - 准备充分:通过
Object.defineProperty来劫持对象属性的setter和getter操作,当触发getter时收集依赖,当触发setter时执行一些操作。
今天我们的主角,就是 defineProperty,以及它的兄弟 proxy。
什么是 defineProperty ?
从名字上看,可以拆分为 define 和 property,分别是 定义 和 属性 的意思。所以 defineProperty 是用来定义一个属性的。它接受的参数依次为 obj、prop、descriptor。
Object.defineProperty(obj, prop, descriptor)
其中第一和第二个参数比较简单,obj 为属性所在的对象,prop 为属性名,常见写法如下
const o = {}
Object.defineProperty(o, 'key', {
value: 'value'
})
console.log(o) // { key: 'value' }
descriptor: 翻译过来为 属性描述符,顾名思义,就是用来描述这个属性的详细信息,具体信息如下:
-
configurable:当且仅当configurable为true时,该属性描述符才能被改变,同时该属性也能从对应的对象上删除,默认为false
const o = {}
Object.defineProperty(o, 'name', {
configurable: false,
value: 'name',
});
Object.defineProperty(o, 'age', {
configurable: true,
value: 23,
});
console.log(o); // { name: 'name', age: 23 }
delete o.name
delete o.age
console.log(o); // { name: 'name' } 其中 name 属性无法被删除
-
enumerable:当且仅当enumerable为true时,该属性才能出现在对象的枚举属性中,默认为false
const o = {}
Object.defineProperty(o, 'name', {
enumerable: false,
value: 'name',
});
Object.defineProperty(o, 'age', {
enumerable: true,
value: 23,
});
console.log(Object.keys(o)); // ["age"] 其中 name 不可被枚举
-
value:属性的初始值,默认为undefined
const o = {}
Object.defineProperty(o, 'name', {
value: 'name',
});
Object.defineProperty(o, 'age', {});
console.log(o); // { name: 'name', age: undefined }
-
writable:该属性能否被赋值运算符改变,默认为false
const o = {}
Object.defineProperty(o, 'name', {
writable: false,
value: 'name',
});
Object.defineProperty(o, 'age', {
writable: true,
value: 23
});
console.log(o); // { name: 'name', age: 23 }
o.name = 'Jim';
o.age = 30;
console.log(o); // { name: 'name', age: 30 }
-
get:存取描述符之一,给属性提供一个getter方法,当访问属性时会被触发。
const o = {}
Object.defineProperty(o, 'name', {
get: () => {
console.log('get o.name')
return 'hello'
}
});
console.log(o.name) // 'get o.name' 'hello'
-
set:存取描述符之一,给属性提供一个setter方法,当给属性赋值时被触发。
const o = {}
Object.defineProperty(o, 'name', {
get: function() {
console.log('getter');
return this._name
},
set: function(newVal) {
console.log('setter', newVal);
this._name = newVal
}
});
o.name = 10; // 'setter 10'
console.log(o.name); // 'getter' 10
什么是 proxy
proxy 译为 代理,可以拦截属性的一些行为来做一些特殊处理,下面为一个简单的例子
const _target = {
name: 'Jim',
}
const handler = {
get: (obj, prop) => obj[prop] || 'no value'
}
const target = new Proxy(_target, handler);
console.log(target.name, target.age); // 'Jim' 'no value'
proxy 可以拦截十几种行为,下面进行了简单罗列,具体使用方式请 点击查看
handler.get:访问属性时触发handler.set:属性被赋值时触发handler.has:拦截in操作,如'name' in targethandler.apply:拦截函数调用,如target(args)handler.construct:拦截new操作,如new Target()handler.deleteProperty:拦截delete操作,如delete obj.namehandler.defineProperty:拦截defineProperty操作
defineProterty 和 proxy 的对比
-
defineProterty是es5的标准,proxy是es6的标准; - 利用
defineProterty实现双向数据绑定(vue2.x采用的核心) - 利用
proxy实现双向数据绑定(vue3.x会采用)