最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue响应式实现原理解析

    正文概述 掘金(纸上的彩虹)   2021-05-20   513

    Vue响应式实现原理

    Object.defineProperty

    语法

    Object.defineProperty(obj, prop, descriptor)
    

    参数

    obj
    

    要定义属性的对象。

    prop
    

    要定义或修改的属性的名称或 Symbol

    descriptor
    

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

    descriptor参数详解

    • configurable

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

    • enumerable

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

    • value

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

    • writable

      当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。默认为 false

    • get

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

    • set

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

    getter和setter

    ES5的Object.defineProperty提供监听属性变更的功能,下面将演示如何通过covert函数修改传入对象的gettersetter实现修改对象属性时打印日志的功能。

    const obj = { foo: 123 }
    convert(obj) 
    obj.foo // 需要打印: 'getting key "foo": 123'
    obj.foo = 234 // 需要打印: 'setting key "foo" to 234'
    obj.foo // 需要打印: 'getting key "foo": 234'
    

    covert函数实现如下:

    function convert (obj) {
      // Object.keys获取对象的所有key值,通过forEach对每个属性进行修改
      Object.keys(obj).forEach(key => {
        // 保存属性初始值
        let internalValue = obj[key]
        Object.defineProperty(obj, key, {
          get () {
            console.log(`getting key "${key}": ${internalValue}`)
            return internalValue
          },
          set (newValue) {
            console.log(`setting key "${key}" to: ${newValue}`)
            internalValue = newValue
          }
        })
      })
    }
    

    依赖跟踪(订阅发布模式)

    需要实现一个依赖跟踪类Dep,类里有一个叫depend方法,该方法用于收集依赖项;另外还有一个notify方法,该方法用于触发依赖项的执行,也就是说只要在之前使用dep方法收集的依赖项,当调用notfiy方法时会被触发执行。

    下面是Dep类期望达到的效果,调用dep.depend方法收集收集依赖,当调用dep.notify方法,控制台会再次输出updated语句

    const dep = new Dep()
    
    autorun(() => {
      dep.depend()
      console.log('updated')
    })
    // 打印: "updated"
    
    dep.notify()
    // 打印: "updated"
    

    autorun函数是接收一个函数,这个函数帮助我们创建一个响应区,当代码放在这个响应区内,就可以通过dep.depend方法注册依赖项

    最终实现的Dep类代码如下:

    window.Dep = class Dep {
      constructor () {
        // 订阅任务队列,方式有相同的任务,用Set数据结构简单处理
        this.subscribers = new Set()
      }
    	// 用于注册依赖项
      depend () {
        if (activeUpdate) {
          this.subscribers.add(activeUpdate)
        }
      }
    	// 用于发布消息,触发依赖项重新执行
      notify () {
        this.subscribers.forEach(sub => sub())
      }
    }
    
    let activeUpdate = null
    
    function autorun (update) {
      //把wrappedUpdate赋值给activeUpdate,这会使得当依赖关系发生改变update函数会重新执行
      //实际上是调用wrappedUpdate,如果以后有改动,这个依赖跟踪器依然会不断的收集依赖项
      //因为update函数有可能包含条件,如果一个变量是true就收集这个依赖,如果是false就收集另外的依赖
      //所以,依赖收集系统需要动态更新这些依赖,保证依赖项一直是最新的
      const wrappedUpdate = () => {
        activeUpdate = wrappedUpdate
        update()
        activeUpdate = null
      }
      wrappedUpdate()
    }
    

    实现迷你观察者

    我们将上面的两个练习整合到一起,实现一个小型的观察者,通过在getter和setter中调用depend方法和notfiy方法,就可以实现自动更新数据的目的了,这也是Vue实现自动更新的核心原理。

    期望实现的调用效果:

    const state = {
      count: 0
    }
    //监听state
    observe(state)
    //依赖注入
    autorun(() => {
      console.log(state.count)
    })
    // 打印"count is: 0"
    //每次重新赋值的时候执行notfiy函数 重走一遍所有的依赖函数
    state.count++
    // 打印"count is: 1"
    

    最终整合代码如下:

    class Dep {
      constructor () {
        this.subscribers = new Set()
      }
    
      depend () {
        if (activeUpdate) {
          this.subscribers.add(activeUpdate)
        }
      }
    
      notify () {
        this.subscribers.forEach(sub => sub())
      }
    }
    
    function observe (obj) {
      Object.keys(obj).forEach(key => {
        let internalValue = obj[key]
    
        const dep = new Dep()
        Object.defineProperty(obj, key, {
          // 在getter收集依赖项,当触发notify时重新运行
          get () {
            dep.depend()
            return internalValue
          },
    
          // setter用于调用notify
          set (newVal) {
            const changed = internalValue !== newVal
            internalValue = newVal
            if (changed) {
              dep.notify()
            }
          }
        })
      })
      return obj
    }
    
    let activeUpdate = null
    
    function autorun (update) {
      const wrappedUpdate = () => {
        activeUpdate = wrappedUpdate
        update()
        activeUpdate = null
      }
      wrappedUpdate()
    }
    

    起源地下载网 » Vue响应式实现原理解析

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元