背景
校招前端面试必问问题之一: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 target
handler.apply
:拦截函数调用,如target(args)
handler.construct
:拦截new
操作,如new Target()
handler.deleteProperty
:拦截delete
操作,如delete obj.name
handler.defineProperty
:拦截defineProperty
操作
defineProterty
和 proxy
的对比
-
defineProterty
是es5
的标准,proxy
是es6
的标准; - 利用
defineProterty
实现双向数据绑定(vue2.x
采用的核心) - 利用
proxy
实现双向数据绑定(vue3.x
会采用)