Code前端首页关于Code前端联系我们

响应式Vue实现原理分析

terry 2年前 (2023-09-08) 阅读数 163 #Vue

响应式Vue实现原理

Object.defineProperty

语法

Object.defineProperty(obj, prop, descriptor)
 

参数

obj
 

需要定义其属性的对象。

prop
 

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

descriptor
 

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

描述符参数详细信息

  • configurable

    configurable当且仅当属性configurable的键值configurable时,才可以修改属性描述符,也可以从对应的对象中删除该属性。 默认为 false
  • enumerable

    当且仅当 enumerable 属性的键值为 true 时,

    属性才会出现在对象的枚举属性中。 默认为 false

  • value

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

  • writable

    当且仅当该属性的键writable的值为true,则该属性的值,即上面的value,可以更改为赋值运算符 默认为 false

  • get

    属性的 getter 函数,或者 undefined(如果没有 getter)。当访问属性时会调用该函数。运行时不传递任何参数,但会传递对象this(因为继承关系this不一定是这里定义该属性的对象)。该函数的返回值将用作属性值。 默认为 undefined

  • set

    属性设置功能或undefined 如果没有设置。当属性值改变时调用该函数。此方法接受一个参数(即要分配的新值),该参数将在分配时传入 this 对象。 默认为 undefined

getter 和 setter

getterES5 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'
 

隐藏功能实现如下:

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方法,用于触发依赖的执行,即只要调用notfiy方法时,之前通过dep方法收集的依赖都会被触发执行。

以下是Dep 类的预期效果。调用方法dep.depend收集并收集依赖关系。当调用方法dep.notify时,控制台会发出语句❙

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()
}
 

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门