背景
校招前端面試必問問題之一:vue
雙向綁定原理润努。
- 前端小白:
wt?
我怎么知道猿涨?不是會用就可以了嘛蛔外?我管它怎么實(shí)現(xiàn)。 - 看過一些些面經(jīng):
vue
雙向綁定是通過數(shù)據(jù)劫持實(shí)現(xiàn)的伟端,通過劫持對象的getter
和setter
實(shí)現(xiàn)杯道。 - 準(zhǔn)備充分:通過
Object.defineProperty
來劫持對象屬性的setter
和getter
操作,當(dāng)觸發(fā)getter
時(shí)收集依賴责蝠,當(dāng)觸發(fā)setter
時(shí)執(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
: 翻譯過來為 屬性描述符
丰辣,顧名思義撒强,就是用來描述這個屬性的詳細(xì)信息,具體信息如下:
-
configurable
:當(dāng)且僅當(dāng)configurable
為true
時(shí)笙什,該屬性描述符才能被改變飘哨,同時(shí)該屬性也能從對應(yīng)的對象上刪除,默認(rèn)為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
:當(dāng)且僅當(dāng)enumerable
為true
時(shí)琐凭,該屬性才能出現(xiàn)在對象的枚舉屬性中芽隆,默認(rè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
:屬性的初始值,默認(rèn)為undefined
const o = {}
Object.defineProperty(o, 'name', {
value: 'name',
});
Object.defineProperty(o, 'age', {});
console.log(o); // { name: 'name', age: undefined }
-
writable
:該屬性能否被賦值運(yùn)算符改變统屈,默認(rèn)為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
方法,當(dāng)訪問屬性時(shí)會被觸發(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
方法,當(dāng)給屬性賦值時(shí)被觸發(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
可以攔截十幾種行為膜宋,下面進(jìn)行了簡單羅列窿侈,具體使用方式請 點(diǎn)擊查看
handler.get
:訪問屬性時(shí)觸發(fā)handler.set
:屬性被賦值時(shí)觸發(fā)handler.has
:攔截in
操作,如'name' in target
handler.apply
:攔截函數(shù)調(diào)用秋茫,如target(args)
handler.construct
:攔截new
操作棉磨,如new Target()
handler.deleteProperty
:攔截delete
操作,如delete obj.name
handler.defineProperty
:攔截defineProperty
操作
defineProterty
和 proxy
的對比
-
defineProterty
是es5
的標(biāo)準(zhǔn)学辱,proxy
是es6
的標(biāo)準(zhǔn); - 利用
defineProterty
實(shí)現(xiàn)雙向數(shù)據(jù)綁定(vue2.x
采用的核心) - 利用
proxy
實(shí)現(xiàn)雙向數(shù)據(jù)綁定(vue3.x
會采用)