VUE 动态响应的实现

vue 的动态响应指的是 当一个数据改变,与之关联的数据也随之改变。

如 b = a+10 当a 变化时候 b也随之变化。web代码层面实现是靠Object.defineProperty的get和set

尤雨溪教你写vue 高级vue教程 源码分析 中文字幕翻译完毕_哔哩哔哩_bilibili

1、简单介绍Object.defineProperty

Object.defineProperty(obj,prop, descriptor)

# 对象,属性,描述符

描述符有两种 数据描述符 和存储描述符 。数据描述符描述的对象的属性 该属性是否可写是否可枚举等。存储描述符是get set 描述当存储时行为。

注意不能两种不能混用

这两种都描述符都是对象,共享下列键值

configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false

enumerable  键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false

数据描述符还具有以下可选键值:

value该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
默认为 undefined

writable  键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符 (en-US)改变。
默认为 false

存取描述符还具有以下可选键值:

get 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。默认为 undefined

set 属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
默认为 undefined

getset 方法中,this 指向某个被访问和修改属性的对象。

如果一个描述符不具有 valuewritablegetset 中的任意一个键,那么它将被认为是一个数据描述符。如果一个描述符同时拥有 valuewritablegetset 键,则会产生一个异常。

2、利用set和get,对数据读取改变做出反应

set 接受属性的新值,

const obj = { foo : 123 }
function isObject(obj){
   return typeof obj === 'object' &&  !Array.isArray(obj)
   && obj !== null
   && obj !== undefined
}
function convert(obj) {
    if (!isObject(obj)){
        throw new TypeError
    }
    Object.keys(obj).forEach((key)=>{
        inner_value = obj[key]
        Object.defineProperty(obj,key,{
            get: function(){
            console.log(`getting key "${key}": ${inner_value}`)
            return inner_value
        },
        set: function(new_value){
            console.log(`setting key "${key}" to: ${old_value}`)
            const isChanged = inner_value !== new_value
            if (isChanged){
                inner_value = new_value
            }
        }
    })

    })
   
}
convert(obj)
obj.foo 
obj.foo = 234 
obj.foo

结果
getting key "foo": 123
'setting key "foo" to: 234'
getting key "foo": 234  

3、收集依赖

依赖是指谁使用了这个数据,xx依赖于这个数据,数据改变,通知谁

创建一个Dep类,构造函数声明一个set集合用来存依赖,该类有一个depend函数set.add用来收集依赖,notiyf用来通知所有依赖更新。

atuorun()接受一个update函数参数,wrappedUpdate()里调用这个函数,activeUpdate告诉Dep对象谁执行了dep.depend, notify通知收集的依赖更新

// 如何添加依赖 用一个变量记录谁执行dep.dend() 
class Dep{
    constructor(){
        this.subcribers = new Set()
    }
    // register curren active update as a subcriber
    depend(){
        
        if(activeUpdate){
            this.subcribers.add(activeUpdate)
        }
        
    }
    // run all subcribers functions
    notify(){
        
        this.subcribers.forEach(subcriber=> subcriber())
    }
}
let activeUpdate
// autorun 执行update函数,activeUpdate变量记录是哪个函数执行dep.depdend
// 注意javascript 是单线程函数,所有activeUpdate不会出现多线程操作变量改变 
function autorun(update){
    function wrappedUpdate(){
        activeUpdate = wrappedUpdate
        update()
        activeUpdate = null
    }
    wrappedUpdate()
} 

const dep = new Dep()
autorun(()=>{
    dep.depend()
    console.log("update")
})
// 这里为什么会触发console.log wrappendUpdate保存了对update引用。update不会被销毁。闭包
dep.notify()

4、简化版observer

综合23,只不过将收集依赖放在属性get时候执行,当set的时候会通知依赖内的函数更新

 把前面的两个综合起来。监听一个对象的set 和get
// get对象属性的时候添加依赖,set 的时候通知subcribers update

function isObject(obj){
    return typeof obj === 'object' &&  !Array.isArray(obj)
    && obj !== null
    && obj !== undefined
 }
 function observer(obj) {
     if (!isObject(obj)){
         throw new TypeError
     }
     let dep = new Dep()
     Object.keys(obj).forEach((key)=>{
         inner_value = obj[key]
         Object.defineProperty(obj,key,{
             get(){
             dep.depend()
             console.log(`${key} is: ${inner_value}`)
             return inner_value
             },
            set(new_value){
                console.log(new_value)
                const isChanged = inner_value !== new_value
                if (isChanged){
                   
                    inner_value = new_value
                    dep.notify()
             }
             
         }
     })
 
     })
 }
 class Dep{
    constructor(){
        this.subcribers = new Set()
    }
    // register curren active update as a subcriber
    depend(){  
        if(activeUpdate){
            this.subcribers.add(activeUpdate)
        }   
    }
    // run all subcribers functions
    notify(){
        
        this.subcribers.forEach(subcriber=> subcriber())
    }
}
let activeUpdate
function autorun(update){
    function wrappedUpdate(){
        activeUpdate = wrappedUpdate
        update()
        activeUpdate = null
    }
    wrappedUpdate()
} 
const state = {
    count: 0
}
observer(state)
// autorunn ->wrappedUpdate->get->dep 

autorun(()=>{
    console.log(state.count)
})
state.count++

5、总结

动态响应是通过Object.defineProperty的set和get,在get中收集使用这个属性的函数,在set里通知函数更新。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值