背景
校招前端面試必問問題之一:vue 雙向綁定原理。
- 前端小白:
wt?我怎么知道?不是會用就可以了嘛?我管它怎么實現(xiàn)。 - 看過一些些面經(jīng):
vue雙向綁定是通過數(shù)據(jù)劫持實現(xiàn)的,通過劫持對象的getter和setter實現(xiàn)。 - 準備充分:通過
Object.defineProperty來劫持對象屬性的setter和getter操作,當觸發(fā)getter時收集依賴,當觸發(fā)setter時執(zhí)行一些操作。
今天我們的主角,就是 defineProperty,以及它的兄弟 proxy。
什么是 defineProperty ?
從名字上看,可以拆分為 define 和 property,分別是 定義 和 屬性 的意思。所以 defineProperty 是用來定義一個屬性的。它接受的參數(shù)依次為 obj、prop、descriptor。
Object.defineProperty(obj, prop, descriptor)
其中第一和第二個參數(shù)比較簡單,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時,該屬性才能出現(xiàn)在對象的枚舉屬性中,默認為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方法,當訪問屬性時會被觸發(fā)。
const o = {}
Object.defineProperty(o, 'name', {
get: () => {
console.log('get o.name')
return 'hello'
}
});
console.log(o.name) // 'get o.name' 'hello'
-
set:存取描述符之一,給屬性提供一個setter方法,當給屬性賦值時被觸發(fā)。
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:訪問屬性時觸發(fā)handler.set:屬性被賦值時觸發(fā)handler.has:攔截in操作,如'name' in targethandler.apply:攔截函數(shù)調用,如target(args)handler.construct:攔截new操作,如new Target()handler.deleteProperty:攔截delete操作,如delete obj.namehandler.defineProperty:攔截defineProperty操作
defineProterty 和 proxy 的對比
-
defineProterty是es5的標準,proxy是es6的標準; - 利用
defineProterty實現(xiàn)雙向數(shù)據(jù)綁定(vue2.x采用的核心) - 利用
proxy實現(xiàn)雙向數(shù)據(jù)綁定(vue3.x會采用)