乐趣区

关于前端:学习-Vue-原理响应式

近来在学习 Vue,对于它的外围概念之一——响应式始终有所困惑,偶然间发现一门课程 vue advanced workshop with Evan You。Vue 的作者尤雨溪亲自解说 Vue。上面是对该课程学习的总结。欢送大家参考和提出意见。

什么是响应式?

响应式是 Vue 的一个外围个性,用于监听视图中绑定的数据,当数据产生扭转时视图自动更新。
只有状态产生扭转,零碎依赖局部产生自动更新就能够称为响应性。在 web 的场景下,就是一直变动的状态反馈到 DOM 上的变动。
响应式实现数据驱动视图的第一步。

数据驱动视图

当初有这样一个例子:

变量 a 和变量 b,变量 b 的值永远等于 a 的 10 倍。

如果应用命令式编程,能够很简略实现。

let a = 3;
let b = a * 10;
console.log(b) // 30

然而当咱们设置 b 的值为 4 时,b 还是等于 30

let a = 3;
let b = a * 10;
console.log(b) // 30
a = 4; // 命令式,b 不会放弃关系同步
console.log(b) // 30

那么该如何实现当 a 扭转时,b 同时也扭转呢?

这里有一个神奇的函数 onChanged(),它接管一个函数并且当 a 的值扭转时,能够主动执行外面的代码,咱们将 b 的更新放在外面,问题就解决了。

onChanged (() => b = a * 10) // 申明式, b 随着 a 扭转而扭转 

咱们扩大一下,上面代码同样有一个神奇函数 onStateChange,它会在 state 扭转的时候主动运行,那咱们只有在函数中编写 dom 操作的代码,就能够实现 dom 的自动更新了。

// DOM 元素
<span class="cell b1"></span>

// 神奇函数,当 state 值扭转会主动从新运行
onStateChange(() => {document.querySelector('.cell.b1').textContent = state.a * 10
})

咱们再进一步形象,把 dom 的操作应用渲染引擎替换,然而咱们不去钻研渲染引擎的实现,只是简略的认为它会主动解析模版代码与数据关联即可,那代码就会变成上面这样。

// DOM 元素
<span class="cell b1">
    {{state.a * 10}}
</span>

// 神奇函数,当 state 值扭转会主动从新运行
onStateChange(() => { view = render(state) })

如何实现响应式

getter 和 setter

Vue 中对象会被转换成响应式,应用 ES5 的 defineProperty() 重写所有属性的 getter 和 setter 办法。

MDN 上对于 Object.defineProperty 的介绍

上面将演示如何通过 covert 函数批改传入对象的 getter 和 setter 实现批改对象属性

let realValue
Object.defineProperty(obj, 'foo', {get () {return realValue},
    set (newValue) {realValue = newValue}
})

function covert(obj) {Object.keys(obj).forEach(key => {let internalValue = obj[key] // 闭包,提供了存储机会 外部值
        Object.defineProperty(obj, key, {get () {return internalValue},
            set (newValue) {internalValue = newValue}
        })
    })
}

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

创立一个依赖跟踪类 Dep,外面有两个办法:’depend’ ‘notify’。
‘depend’: 收集这种依赖项。
‘notify’: 示意依赖产生扭转,任何之前被定义为依赖的表达式、函数、计算都会被告诉从新执行。也就是说咱们须要找到一种让他们建设关联的办法。咱们把这种计算关系叫依赖。这种计算也被认为是订阅者模式。
上面是 Dep 类冀望达到的成果,调用 dep.depend 办法收集收集依赖,当调用 dep.notify 办法,控制台会再次输入 updated 语句。

const dep = new Dep()

autorun (() => {dep.depend() // 实际上是把这个函数增加到订阅者列表中 dep 中, 之后无论你在任何中央调用
    console.log("updated")
})
// should log: "updated"

dep.notify()
// 函数再次被调用 should log: "updated" autorun

这个 autorun 函数接管一个更新函数或者表达式,当你进入这个更新函数时,所有都变得特地,当代码放在这个响应区内,就能够通过 dep.depend 办法注册依赖项。

代码实现

window.Dep = class Dep {constructor () {this.subsctibers = new Set()
    }
    depend () {if (activeUpdate) {
            // 注册这个 activeUpdate 作为订阅者
            this.subscribers.add(activeUpdate)
        }
    },
    notify () {
        // 告诉所有订阅者
        this.subscribers.forEach(sub => sub()) // 获取订阅函数并执行
    }
}
let activeUpdate // js 是单线程,任何工夫只能运行一个函数 变量不会被其余执行的函数净化

function autorun (update) {function wrappedUpdate () {
        activeUpdate = wrappedUpdate // 赋值给 wrappedUpdate 会使得当依赖关系产生扭转 update 函数会从新执行 动静更新依赖 保障依赖始终是最新的
        update()
        activeUpdate = null
    }
    updateWrapper();}

autorun(() => {dep.depend()
})

实现迷你响应性零碎

联合后面两个函数 covert() autorun() 将 covert 改名成 observe()。
observer 须要一个监听对象,监听他们得 getter 和 setter,在 getters 和 setters 外面,咱们能够设置依赖。
都整合后咱们相当于创立了一个对象,咱们拜访一个属性,它收集依赖,调用 dep.depend 当咱们通过赋值扭转属性值,调用 notify 触发扭转。

冀望实现的调用成果:

const state = {count: 0}

observe(state)

autorun(() => console.log(state.count))
// should immediately log "count is: 0"

state.count ++
// should log "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) {if (internalValue !== newVal) {
          internalValue = newVal
          dep.notify()}
      }
    })
  })
  return obj
}

let activeUpdate = null

function autorun (update) {const wrappedUpdate = () => {
    activeUpdate = wrappedUpdate
    update()
    activeUpdate = null
  }
  wrappedUpdate()}

以上都是基于集体的了解,写的学习笔记,欢送大家提出倡议。

退出移动版