一、vue2.x 响应式系统的实现原理
MVVM 模式最核心的特性就是数据双向绑定,Vue 构建了一套响应式系统,可以实现侦测对象的变化,从而在数据发生变化时自动渲染视图。
vue 2.x 响应式实现原理 利用 Object.defineProperty() 方法作为对象添加 get() 和 set() 方法来侦测对象的变化,当获取对象属性值时会调用 get() 方法,当修改对象属性值时会调用 set(),于是可以 在 get() 和 set() 方法中添加代码,实现数据与视图的双向绑定。
针对简单地侦测对象的属性变化
js/** * 模拟视图更新 */ const updateView = () => { console.log('视图更新') } /** * 对 Object.defineProperty() 方法进行封装 */ const defineReactive = (obj, key, value) => { Object.defineProperty(obj, key, { get() { return value }, set(newValue) { if (newValue !== value) { // 在 set() 方法中触发视图更新 updateView(); value = newValue } } }) } /** * 对一个对象中所有属性的变化进行侦测 */ const observer = (target) => { // 如果不是对象数据类型,直接返回 if (typeof target !== 'object') { return target } // 循环遍历对象的所有属性,并将它们转换为 getter 和 setter 形式 for (const key in target) { defineReactive(target, key, target[key]) } } // 调用 let user = { name: '张三' }; // 对 user 对象的所有属性变化进行侦测 observer(user) user.name = '李四'
存在一个问题,假如属性的值也是对象,更改这个对象属性的值,就监听不到了。
当对象的属性也是对象类型,单纯针对这个值对象的属性进行改变
js/** * 模拟视图更新 */ const updateView = () => { console.log('视图更新') } /** * 对 Object.defineProperty() 方法进行封装 */ const defineReactive = (obj, key, value) => { // 通过递归调用解决多层对象嵌套的属性侦测问题 observer(value) Object.defineProperty(obj, key, { get() { return value }, set(newValue) { if (newValue !== value) { // 在 set() 方法中触发视图更新 updateView(); value = newValue } } }) } /** * 对一个对象中所有属性的变化进行侦测 */ const observer = (target) => { // 如果不是对象数据类型,直接返回 if (typeof target !== 'object') { return target } // 循环遍历对象的所有属性,并将它们转换为 getter 和 setter 形式 for (const key in target) { defineReactive(target, key, target[key]) } } // 调用 let user = { name: '张三', company: { companyName: '亚信科技' } }; // 对 user 对象的所有属性变化进行侦测 observer(user) user.company.companyName = '阿里巴巴'
通过在 defineReactive 方法中 递归调用 observer 方法
当对象的属性也是对象类型,赋值新的对象,再改变新对象的属性值
js/** * 模拟视图更新 */ const updateView = () => { console.log('视图更新') } /** * 对 Object.defineProperty() 方法进行封装 */ const defineReactive = (obj, key, value) => { // 通过递归调用解决多层对象嵌套的属性侦测问题 observer(value) Object.defineProperty(obj, key, { get() { return value }, set(newValue) { if (newValue !== value) { // 如果 newValue 是对象类型,则继续侦测该对象的所有属性变化 observer(newValue) // 在 set() 方法中触发视图更新 updateView(); value = newValue } } }) } /** * 对一个对象中所有属性的变化进行侦测 */ const observer = (target) => { // 如果不是对象数据类型,直接返回 if (typeof target !== 'object') { return target } // 循环遍历对象的所有属性,并将它们转换为 getter 和 setter 形式 for (const key in target) { defineReactive(target, key, target[key]) } } // 调用 let user = { name: '张三', company: { companyName: '亚信科技' } }; // 对 user 对象的所有属性变化进行侦测 observer(user) user.company = { companyName: '阿里巴巴' } user.company.companyName = '京东'
数组侦测
现象
js// 调用 let user = { name: '张三', company: { companyName: '亚信科技' }, emails: ['163@qq.com'] }; // 对 user 对象的所有属性变化进行侦测 observer(user) user.emails.push('zhangsan@qq.com')
emails 属性是数组类型,当通过 push() 方法改变数组内容时,并不会触发对象的 set() 方法的调用。
解决方案
替换数组的原型对象
jsconst arrayPrototype = Array.prototype // 使用数组的原型对象创建一个新对象 const proto = Object.create(arrayPrototype) /** * 模拟视图更新 */ const updateView = () => { console.log('视图更新') } // 改变数组自身内容的方法只有如下7个,对它们进行拦截 ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => { Object.defineProperty(proto, method, { get() { // 在 get() 方法中触发视图更新 updateView(); return arrayPrototype[method] }, }) }) /** * 对 Object.defineProperty() 方法进行封装 */ const defineReactive = (obj, key, value) => { // 通过递归调用解决多层对象嵌套的属性侦测问题 observer(value) Object.defineProperty(obj, key, { get() { return value }, set(newValue) { if (newValue !== value) { // 如果 newValue 是对象类型,则继续侦测该对象的所有属性变化 observer(newValue) // 在 set() 方法中触发视图更新 updateView(); value = newValue } } }) } /** * 对一个对象中所有属性的变化进行侦测 */ const observer = (target) => { // 如果不是对象数据类型,直接返回 if (typeof target !== 'object') { return target } if (Array.isArray(target)) { // 如果 target 是数组,则将数组的原型对象设置为 proto Object.setPrototypeOf(target, proto); // 对数组中的元素进行侦测 for (let i = 0, len = target.length; i < len; i++) { observer(target[i]) } return } // 循环遍历对象的所有属性,并将它们转换为 getter 和 setter 形式 for (const key in target) { defineReactive(target, key, target[key]) } } // 调用 let user = { name: '张三', company: { companyName: '亚信科技' }, emails: ['163@qq.com'] }; // 对 user 对象的所有属性变化进行侦测 observer(user) user.emails.push('zhangsan@qq.com')