1、如何实现双向绑定?
以用户提交表单为例,其原理是我们对input进行value的属性绑定(v-bind:value="…"),将Model中的变量绑定到View上(Model -> View),以及当用户对input进行操作时,进行事件监听(v-on: input =" … "),从而实现双向数据绑定。v-model实际上是语法糖,结合了上述两个操作。
2、底层原理是什么?
参考:Vue的MVVM是如何实现的
如何追踪数据变化?
Vue将遍历传入Vue实例data选项中的js对象的所有property,并使用Object.defineProperty
把这些 property 全部转为getter/setter
。这些getter/setter
可使property在被访问和修改时通知变更。每个组件实例都对应一个watcher实例,它会在组件渲染的过程中把“接触”过的数据property记为依赖,之后当依赖项的setter触发时,会通知watcher,从而使它关联的组件重新渲染。
vue实现数据双向绑定主要是采用数据劫持,配合发布-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发响应监听回调。
vue数据双向绑定整合Observer Compile 和 Watcher三者,
(1) 通过Observer来监听自己Model的数据变化: Obeject.defineProperty()来监听属性变动,将需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter,同时创建一个消息订阅器Dep用来收集订阅者,数据变动之后触发notify,再调用订阅者的update方法
(2) 通过Complie来解析编译模板指令: 对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。(每次找到一个数据替换,都要重新渲染一遍,可能会造成页面的回流和重绘,那么我们最好的办法就是把以上的元素放在内存中,在内存中操作完成之后,再替换掉.)
(3) **通过Watcher搭起Observer 和Compile 之间的通信桥梁:**能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图,达到 数据变化 -> 视图更新,视图交互变化 -> 数据model变更。什么时候添加绑定watcher? 当订阅数据变化时,来绑定更新函数,从而让watcher更新视图
(1)观察者模式和发布订阅者模式的区别
**观察者模式:**在观察者模式里,被观察者subject只需要维护一套观察者observer的集合,这些observer实现相同的接口,subject只需要知道,通知observer时,需要调用哪个方法就好了。观察者模式是由具体目标调度的,其订阅者与发布者之间存在依赖。
发布订阅者模式: :发布订阅模式里,不仅仅只有发布者和订阅者两个角色,还存在一个调度中心,当事件触发时,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。发布/订阅模式是统一由调度中心调的,其订阅者与发布者之间不存在依赖。
(2)Object.defineProperty可以对数组对象数据进行劫持吗?数组对象如何实现数据的响应更新?
参考:深入理解 Object.defineProperty 及实现数据双向绑定
Object.defineProperty(obj, prop, descriptor);
descriptor
由两部分组成:数据描述符(configurable,enumerable,value 及 writable 配置项)和访问器描述符(configurable,enumerable,get以及set),即使用访问器描述符中 getter或 setter方法的话,不允许使用 writable 和 value 这两个配置项。
ans:
- 当我们使用 Object.defineProperty 对数组赋值有一个新对象的时候,会执行set方法,但是当我们改变数组中的某一项值的时候,或者使用数组中的push等其他的方法,或者改变数组的长度,都不会执行set方法
- 通过重写 Array.property.push方法,并且生成一个新的数组赋值给数据,这样数据双向绑定就触发了
重新编写数组的方法:
const arrPush = {};
// 如下是 数组的常用方法
const arrayMethods = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
// 对数组的方法进行重写
arrayMethods.forEach((method) => {
const original = Array.prototype[method];
arrPush[method] = function() {
console.log(this);
return original.apply(this, arguments);
}
});
const testPush = [];
// 对 testPush 的原型 指向 arrPush,因此testPush也有重写后的方法
testPush.__proto__ = arrPush;
testPush.push(1); // 打印 [], this指向了 testPush
testPush.push(2); // 打印 [1], this指向了 testPush
(3)Object.defineProperty和Proxy的区别
Object.definePropery是对对象的属性的劫持,而Proxy是对对象的劫持,因此对于新增的属性不用重新定义getter,setter特性,Proxy也可以实现劫持,同时对于复杂对象也不必进行深度遍历。Vue3中将使用Proxy来实现数据劫持.
参考:Object.defineProperty与Proxy理解整理
let p = new Proxy(target, handler)
- Object.defineProperty()的主要问题
- 不能监听数组的变化
- 必须遍历对象的每个属性
- 必须深层遍历嵌套的对象
- Proxy
- 针对对象:针对整个对象,而不是对象的某个属性。相比于Object.defineProperty(),省了一个 Object.keys() 的遍历
- 支持数组:不需要对数组的方法进行重载
- 嵌套支持:和 Object.defineProperty() 是一样的,也需要通过逐层遍历来解决。Proxy 的写法是在 get 里面递归调用 Proxy 并返回
- Proxy的优劣势
- 优势:Proxy 的第二个参数可以有 13 种拦截方法,比 Object.defineProperty() 要更加丰富
- 劣势:Proxy 的兼容性不如 Object.defineProperty(), 不能使用 polyfill 来处理兼容性