vue修改数据视图更新原理学习

 更新时间:2023年11月29日 08:42:12   作者:HUMILITY  
这篇文章主要为大家介绍了vue修改数据视图更新原理学习,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

MVVM模式

在vue使用中,我们只要修改了数据,所见视图便会进行相应更新。

为什么?原理是什么?

在这期间做三件事(发布订阅者模式)

  • 数据劫持
  • 依赖收集
  • 派发更新

Object.defineProperty()详解

详见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Refer...该方法有三个入参

obj 要定义属性的对象。

prop 一个字符串或 Symbol,指定了要定义或修改的属性键。

descriptor 要定义或修改的属性的描述符。

通过该方法进行赋值(无需关注隐藏属性)

// 定义一个对象
var obj = {}
// 通过Object.defineProperty对obj进行赋值
Object.defineProperty(obj, 'name', {
 value: '张三'
})
console.log(obj) // {name: '张三'}
console.log(obj.name) // 张三

引入get

// 定义一个对象
var obj = {}
// 通过Object.defineProperty对obj进行赋值
Object.defineProperty(obj, 'name', {
 get() {
     console.log('正在访问name') // 当访问时,会执行该代码
 }
})
Object.defineProperty(obj, 'age', {
 value: 18
})
console.log(obj.name) // undefined 因为没有被赋值、所以认为不存在
console.log(obj.age) // 18
console.log(obj) // {age: 18}

引入set

// 定义一个对象
var obj = {}
// 通过Object.defineProperty对obj进行赋值
Object.defineProperty(obj, 'name', {
 get() {
     console.log('正在访问name') // 当访问时,会执行该代码
 },
 set() {
     console.log('尝试改变name') // 当对name进行更改时,执行该行代码
 }
})
Object.defineProperty(obj, 'age', {
 value: 18
})
console.log(obj.name) // undefined
obj.name = '李四'
console.log(obj.name) // undefined 虽然改变了name,
                      // 但并没有进行具体的赋值,此时会触发set内代码

如何赋值?

// 定义一个对象
var obj = {}
// 通过Object.defineProperty对obj进行赋值
Object.defineProperty(obj, 'name', {
 // getter return出的值为name值,
 get() {
     console.log('正在访问name') // 当访问时,会执行该代码
     return 'TEST' // 便于理解我们写死
 },
 set(newValue) {
     console.log('尝试改变name', newValue) // 当对name进行更改时,执行该行代码
 }
})

console.log(obj.name) // 输出TEST,
obj.name = '李四' // setter内输出 '李四'
console.log(obj.name) // 输出TEST、因为在getter中赋值为TEST,setter未成功赋值

引入defineReactive()

//为什么引入该函数?
//因为get和set不好用,并且不希望引入变量进行周转,例子:
// 定义一个对象
var obj = {}
// 定义一个变量
var temp
// 通过Object.defineProperty对obj进行赋值
Object.defineProperty(obj, 'name', {
 // getter return出的值为name值,
 get() {
     console.log('正在访问name') // 当访问时,会执行该代码
     return temp // 返回temp,此时已被赋值
 },
 set(newValue) {
     temp = newValue // 赋值给全局变量
     console.log('尝试改变name', newValue) // 当对name进行更改时,执行该行代码
 }
})
console.log(obj.name) // undefined,因为temp未赋值,且setter未触发
obj.name = '李四' // 赋值进行setter触发,将 '李四'赋值给temp
console.log(obj.name) // 李四

defineReactive提供一个闭包环境,利用闭包特性进行周转

var obj = {}
function defineReactive(data, key, val) {
 console.log(data, key, val)
 Object.defineProperty(data, key, {
     get() {
         return val
     },
     set(newValue) {
         // 如果你要设置的值和原值相等,直接返回
         if(newValue === val) return 
         val = newValue
     }
 })
}
// 进行赋值操作
defineReactive(obj, 'name', '张三')
console.log(obj.name) // 张三
obj.name = '李四'
console.log(obj.name) // 李四

引入递归侦测对象(observer)处理对象

// 不进行递归操作的话,如果一个对象有好几层,那么我们是检测不到深层变量的
// eg:
var obj =  {
             a: {
                 b: {
                     c: 'i am c'
                 }
             },
             b: 'i am b'
         }

实现递归

定义observer

把一个正常的object转换为每个层级都是响应式的

//defineReactive
function defineReactive(data, key, val) {
 console.log(data, key, val)
 if (arguments.length == 2) {
     val = data[key]
 }
 // 子元素要进行递归
 let childOb = observe(val)
 Object.defineProperty(data, key, {
     get() {
         return val
     },
     set(newValue) {
         // 如果你要设置的值和原值相等,直接返回
         if(newValue === val) return 
         val = newValue
         // 设置了新值,也要被observe
         childOb = observe(newValue)
     }
 })
}
// Observer
class Observer {
 constructor(value) {
     // 给实例添加__ob__属性,数值是这次new的实例
     def(value, '__ob__', this, false)
     this.walk(value)
 }
 // 遍历
 walk(value) {
     for (let k in value) {
         defineReactive(value, k)
     }
 }
}
// 不可遍历的方法
function def(obj, key, value, enumerable) {
 Object.defineProperty(obj, key, {
     value,
     enumerable,
     writable: true,
     configurable: true
 })
}
// 创建observe函数用于辅助
function observe(value) {
 // 只为对象服务
 if(typeof value !== 'object') return
 // 定义ob
 if(typeof value.__ob__ !== 'undefined'){
     ob = value.__ob__
 } else {
     ob = new Observer(value)
 }
}
// 循环监测
observe(obj) // 此时obj每个属性都被挂载了__ob__标识,且为响应式
obj.a.b.c.e = 10
console.log(obj.a.b.c.e) // 10

// 简单来说就是通过__ob__这个标识来确认你这个obj是不是响应式的,如果不是就递归遍历
// observe(obj) ==》 是否有__ob__ ==》 没有就new Observer(obj) ==》逐个响应式defineReactive
  • 处理数组

vue对数组的方法进行了改写(pushpopshiftunshiftsplicesortreverse
Array.prototype为原型创建了一个arrayMethods的对象,用es6Object.setPrototypeOf(),强制指向arrayMethods

// 拿到Array.prototype
const arrayPrototype = Array.prototype
// 以Array.prototype为原型创建arrayMethods对象
const arrayMethods = Object.create(arrayPrototype)
const methodsNeedChange = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
]
// 遍历
methodsNeedChange.foreach(methodName => {
    // 备份原来的方法, 因为数组的功能不能被剥夺
    const original = arrayPrototype[methodName]
    // 定义新的方法
    def(arrayMethods, methodName, functio(this){
        // 恢复原来的功能
        const result = original.apply(this, arguments)
        // 把类数组对象变为数组
        const args = [...arguments]
        // 把数组身上的__ob__取出来,__ob__已经被添加了,因为数组不是最高层,在遍历obj对象第一层的时候,已经添加了__ob__属性
        const ob = this.__ob__
        //push, unshift, splice可以插入新项,因此也要把插入的新项也变为observe
        let inserted = []
        switch(methodName){
            case 'push':
            case 'unshift':
                inserted = arguments;
                break;
            case 'splice':
                // splice格式是splice(下标、数量、插入的新项)
                inserted = args.slice(2);
                break;
        }
        // 判断有没有要插入的新项, 如果有让新项也变为响应的    
        if (inserted) {
            ob.observeArray(inserted)
        }
        return result
    }, false)
})
// 在observer 中处理数组
// Observer
class Observer {
    constructor(value) {
        // 给实例添加__ob__属性,数值是这次new的实例
        def(value, '__ob__', this, false)
        // 如果是数组,需要把数组的原型,指向arrayMethods
        if(Array.isArray(value)) {
            Object.setPrototypeOf(value, arrayMethods)
            // 让数组可以响应式、既observe
            this.observeArray(value)
        } else {
            this.walk(value)
        }
    }
    // 遍历
    walk(value) {
        for (let k in value) {
            defineReactive(value, k)
        }
    }
    // 数组的特殊遍历
    observeArray(arr) {
        for(let i = 0, l = arr.length; i < l; i++) {
            // 逐项进行observe
            observe(arr[i])
        }
    }
}
  • 依赖收集

    • 需要用到数据的地方,称为依赖
    • vue1.X, 细粒度依赖、用到数据的DOM依赖
    • vue2.X, 中等粒度依赖、用到数据的组件是依赖
    • 在getter收集依赖、在setter触发依赖
    • dep类

      • 把依赖收集的代码封装,专门用来管理依赖,每个Observer的实例,成员中都有一个Dep的实例
      • dep使用发布订阅模式、当数据发生变化时,会循环依赖列表,把所有的watcher都通知一遍
    • watcher类

      • 中介、数据发生变化时通过Watcher进行中转,通知组件
      • 依赖就是watcher、只有watcher触发的getter才会收集依赖、哪个watcher触发了getter、就把哪个watcher收集到dep中
    • whatcher把自己设置到一个全局指定的位置,然后读取数据,由于读取了数据,会触发这个数据的getter。
    • 在getter中就能得到当前正在读取数据的watcher,并把这个watcher收集到dep中
  • dep类
class Dep{
    constructor(){
        // dep类的构造器
        // 用数组存储自己订阅者, 存储的是watcher的实例
        this.subs = []
    }
    // 添加订阅
    addSub(){
        this.subs.push(sub)
    }
    // 添加依赖
    depend() {
        // Dep.target是我们自己指定的全局的位置,主要是全局唯一,没歧义
        if(Dep.target){
            this.addSub(Dep.target)
        }
    }
    // 通知更新
    notify() {
        // 浅克隆一份
        const subs = this.subs.slice()
        // 遍历
        for ()let i = 0, l = subs.length; i &lt; 1; i++){
            subs[i].update()
        }
    }
}
  • watcher类
class Watcher{
    constructor(target, expression, callback){
        // watcher类的构造器
        this.target = target
        // parsePath是一个高阶函数、用于拿到obj最底层的值
        // getter被赋值的为函数
        this.getter = parsePath(expression)
        this.callback = callback
        this.value = this.get()  
    }
    update() {
        this.run()
    }
    get() {
        //  进入依赖收集阶段,让全局的Dep.target设置为watcher本身,那么就是进入依赖收集阶段
        Dep.target = this

        const obj = this.traget
        // 取出最底层的值
        try {
            this.getter(obj)
        } finally {
            Dep.target = null
        }
        
        return value
    }
    run() {
        this.getAndInvoke(this.callback)
    }
    getAndInvoke(cb) {
        const value = this.get()
        if (value !== this.value || typeof value == 'object') {
            const oldValue = this.value
            this.value = value
            cb.call(this.target, value, oldValue)
        }
    }
}
// parsePath函数
function parsePath(str) {
    var segments = str.split('.')
    
    // 返回一个函数
    return (obj) =&gt; {
        for (let i = 0; i &lt; segments.length; i++) {
            obj = obj[segments[i]]
        }
        return obj
    }
} 
var fb = parsePath('a.b.c.d.e')
var v =fb({a:{b:{c:{d:{e:12}}}}})
console.log(v) // 12
//

dep和watcher实例化的位置

dep: defineReactive的闭包中(创建响应式中、闭包内可以得到)、observer构造器内

//defineReactive
function defineReactive(data, key, val) {
const dep = new Dep()
console.log(data, key, val)
if (arguments.length == 2) {
    val = data[key]
}
// 子元素要进行递归
let childOb = observe(val)
Object.defineProperty(data, key, {
    get() {
        // 如果处于依赖收集阶段
        if (Dep.target) {
            dep.depend()
            if (childOb) {
                childOb.dep.depend()
            }
        }
        return val
    },
    set(newValue) {
        // 如果你要设置的值和原值相等,直接返回
        if(newValue === val) return 
        val = newValue
        // 设置了新值,也要被observe
        childOb = observe(newValue)
        // 发布订阅模式,通知dep
    }
})
}
// Observer
class Observer {
constructor(value) {
    // 每一个observer实例身上,都有一个dep
    this.dep = new Dep()
    // 给实例添加__ob__属性,数值是这次new的实例
    def(value, '__ob__', this, false)
    // 如果是数组,需要把数组的原型,指向arrayMethods
    if(Array.isArray(value)) {
        Object.setPrototypeOf(value, arrayMethods)
        // 让数组可以响应式、既observe
        this.observeArray(value)
    } else {
        this.walk(value)
    }
}
// 遍历
walk(value) {
    for (let k in value) {
        defineReactive(value, k)
    }
}
// 数组的特殊遍历
observeArray(arr) {
    for(let i = 0, l = arr.length; i < l; i++) {
        // 逐项进行observe
        observe(arr[i])
    }
}
}

methodsNeedChange.foreach(methodName => {
// 备份原来的方法, 因为数组的功能不能被剥夺
const original = arrayPrototype[methodName]
// 定义新的方法
def(arrayMethods, methodName, functio(this){
    // 恢复原来的功能
    const result = original.apply(this, arguments)
    // 把类数组对象变为数组
    const args = [...arguments]
    // 把数组身上的__ob__取出来,__ob__已经被添加了,因为数组不是最高层,在遍历obj对象第一层的时候,已经添加了__ob__属性
    const ob = this.__ob__
    //push, unshift, splice可以插入新项,因此也要把插入的新项也变为observe
    let inserted = []
    switch(methodName){
        case 'push':
        case 'unshift':
            inserted = arguments;
            break;
        case 'splice':
            // splice格式是splice(下标、数量、插入的新项)
            inserted = args.slice(2);
            break;
    }
    
    // 判断有没有要插入的新项, 如果有让新项也变为响应的    
    if (inserted) {
        ob.observeArray(inserted)
    }
    
    ob.dep.notify()
    
    return result
}, false)
})

dep类和watcher类 没太懂,后续研究后详细说明下,更多关于vue修改数据视图更新的资料请关注脚本之家其它相关文章!

相关文章

  • vue 解决异步数据更新问题

    vue 解决异步数据更新问题

    今天小编就为大家分享一篇vue 解决异步数据更新问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-10-10
  • 一文详细了解Vue 3.0中的onMounted和onUnmounted钩子函数

    一文详细了解Vue 3.0中的onMounted和onUnmounted钩子函数

    Vue3.0引入了新的组件生命周期钩子函数onMounted和onUnmounted,分别用于组件挂载后和卸载前的操作,这些钩子函数为开发者提供了更多灵活性,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-10-10
  • 在 Linux/Unix 中不重启 Vim 而重新加载 .vimrc 文件的流程

    在 Linux/Unix 中不重启 Vim 而重新加载 .vimrc 文件的流程

    这篇文章主要介绍了在 Linux/Unix 中不重启 Vim 而重新加载 .vimrc 文件的流程,需要的朋友可以参考下
    2018-03-03
  • uniapp中app与webview的通讯代码示例

    uniapp中app与webview的通讯代码示例

    这篇文章主要给大家介绍了关于uniapp中app与webview通讯的相关资料,这里的通信主要是打包APP端和web-view内嵌网页的双向通信,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-03-03
  • vue组件传递对象中实现单向绑定的示例

    vue组件传递对象中实现单向绑定的示例

    下面小编就为大家分享一篇vue组件传递对象中实现单向绑定的示例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-02-02
  • 聊聊Vue.js的template编译的问题

    聊聊Vue.js的template编译的问题

    这篇文章主要介绍了聊聊Vue.js的template编译的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • vue.nextTick()与setTimeout的区别及说明

    vue.nextTick()与setTimeout的区别及说明

    这篇文章主要介绍了vue.nextTick()与setTimeout的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • vue3.2中的vuex使用详解

    vue3.2中的vuex使用详解

    这篇文章主要介绍了vue3.2中的vuex使用详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-04-04
  • Vue3+Element Plus的项目搭建过程详解

    Vue3+Element Plus的项目搭建过程详解

    这篇文章主要为大家介绍了Vue3+Element Plus的项目搭建过程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • vue如何使用 jsplumb 生成流程图

    vue如何使用 jsplumb 生成流程图

    文章介绍了如何在Vue项目中使用jsPlumb库来生成流程图,文章提供了两个简单的示例代码,展示了如何定义流程图的数据字段和如何配置连线样式,最后,文章鼓励读者继续探索更多关于vue和jsPlumb的使用技巧,感兴趣的朋友一起看看吧
    2025-02-02

最新评论