校招前端面试必问问题之一:vue
双向绑定原理。javascript
wt?
我怎么知道?不是会用就能够了嘛?我管它怎么实现。vue
双向绑定是经过数据劫持实现的,经过劫持对象的 getter
和 setter
实现。Object.defineProperty
来劫持对象属性的 setter
和 getter
操做,当触发 getter
时收集依赖,当触发 setter
时执行一些操做。今天咱们的主角,就是 defineProperty
,以及它的兄弟 proxy
。前端
defineProperty
?从名字上看,能够拆分为 define
和 property
,分别是 定义
和 属性
的意思。因此 defineProperty
是用来定义一个属性的。它接受的参数依次为 obj
、prop
、descriptor
。vue
Object.defineProperty(obj, prop, descriptor) 复制代码
其中第一和第二个参数比较简单,obj
为属性所在的对象,prop
为属性名,常见写法以下java
const o = {} Object.defineProperty(o, 'key', { value: 'value' }) console.log(o) // { key: 'value' } 复制代码
descriptor
: 翻译过来为 属性描述符
,顾名思义,就是用来描述这个属性的详细信息,具体信息以下:es6
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
能够拦截十几种行为,下面进行了简单罗列,具体使用方式请 点击查看markdown
handler.get
:访问属性时触发app
handler.set
:属性被赋值时触发函数
handler.has
:拦截 in
操做,如 'name' in target
oop
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
会采用)