手写一个Vue.js响应式框架

准备工作

  • 数据驱动

  • 响应式的核心原理

  • 发布订阅和观察者模式


数据驱动

  • 数据响应式、双向绑定、数据驱动

  • 数据响应式

    • 数据模型仅仅是普通的JavaScript对象,而当我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率

  • 双向绑定

    • 数据改变,视图改变;视图改变,数据也随之改变

    • 我们可以通过v-model在表单元素上创建双向数据绑定

  • 数据驱动是Vue最独特的特性之一

    • 开发工程中仅需要关注数据本身,不需要关心数据是如何渲染到视图

数据响应式的核心原理

vue2.x

let data = {
    msg: "hello"
}
 
// 模拟Vue的实例
let vm = {}
 
//数据劫持:当访问或者设置vm中的成员的时候,做一些干预操作
Object.defineProperty(vm, "msg", {
    // 可枚举(可遍历)
    enumerable: true,
    // 可配置(可以使用delete删除,可以通过defineProperty重新定义)
    configurable: true,
    // 当获取值的时候执行
    get () {
        return data.msg
    }
    // 当设置值的时候执行
    set (newValue) {
        if (newValue === data.msg) {
            return
        }
        data.msg = newValue
        // 数据更新,更新DOM的值
        document.querySelector('#app').textContent = data.msg
    }
})
  • 如果一个对象中多个属性需要转换getter/setter如何处理?

function proxyData (data) {

    // 遍历data中的所有属性

    Object.keys(data).forEach(key => {

        // 把data的属性,转换成vm中的 setter/getter

        Object.defineProperty(vm, key, {

            // 可枚举(可遍历)

            enumerable: true,

            // 可配置(可以使用delete删除,可以通过defineProperty重新定义)

            configurable: true,

            // 当获取值的时候执行

            get () {

                return data[key]

            }

            // 当设置值的时候执行

            set (newValue) {

                if (newValue ===  data[key]) {

                    return

                }

                 data[key] = newValue

                // 数据更新,更新DOM的值

                document.querySelector('#app').textContent = data.msg

            }

        })

    })

}

vue3.x

// 模拟Vue中的 data选项

let data = {

    msg: 'hello',

    count: 0

}

 

// 模拟Vue实例

let vm = new Proxy(data, {

     // 执行代理行为的函数

    // 当访问vm的成员会执行

    get (target, key) {

        return target[key]

    }

    // 当设置vm的成员时会执行

    set (target, key, newValue) {

        if (target[key] === newValue) {

            return

        }

        target[key] = newValue

        document.querySelector('#app').textContent = target[key]

    }

})

发布订阅模式和观察者模式


发布/订阅模式

  • 发布/订阅模式

    • 订阅者

    • 发布者

    • 信号中心

我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscrible)这个信号,从而知道什么时候自己可以开始执行。"这就叫做发布/订阅模式 (publish-subscrible pattern)"

  • vue的自定义事件


let vm = new Vue()

 

vm.$on('dataChange', ()=> {

    console.log("dataChange")

})

 

vm.$on('dataChange', () => {

    console.log("dataChange1")

})

 

vm.$emit("dataChange")

兄弟组件通信过程


// eventBus.js

// 事件中心

let eventHub = new Vue()

 

// componentA.vue

// 发布者

addTodo: function () {

    // 发布消息(事件)

    eventHub.$emit('add-todo',{ text: this.newTodoText})

    this.newTodoText = ''

}

// componentB.vue

// 订阅者

created: function () {

    // 订阅消息(事件)

    eventHub.$on('add-todo',this.addTodo)

观察者模式


  • 观察者(订阅者) -- Watcher

    • update(): 当事件发生时,具体要做的事情

  • 目标(发布者) -- Dep

    • subs数组:存储所有的观察者

    • addSub(): 添加观察者

    • notify(): 当事件发生,调用所有观察者的 update() 方法

  • 没有事件中心



// 发布者-目标

class Dep {

    constructor () {

        // 记录所有的订阅者

        this.subs = []

    }

    // 添加订阅者

    addSub (sub) {

        if (sub && sub.update) {

            this.subs.push(sub)

        }

    }

    // 发布通知

    notify () {

        this.subs.forEach( sub => {

            sub.update()

        })

    }

}

// 订阅者-观察者

class Watcher {

    update () {

        console.log("update")

    }

}

总结:

  • 观察者模式是由具体目标调度,比如当事件触发,Dep就会去调用观察者的方法,所有观察者模式的订阅者于发布者之间是存在依赖的

  • 发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在

模拟Vue响应式原理-分析

整体分析

  • Vue基本结构

  • 打印Vue实例观察

  • 整体结构


  • Vue

    • 把data中额成员注入到Vue实例,并且把data中的成员转成getter/setter

  • Observer

    • 能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知Dep

    •  

Vue

  • 功能

    • 负责接收初始化的参数(选项)

    • 负责把data中的属性注入到Vue实例,转换成getter/setter

    • 负责调用observer监听data中所有属性的变化

    • 负责调用compliler解析指令/差值表达式

  • 结构


  • 代码

class Vue {

  constructor (options) {

    // 1.通过属性保存选项的数据

    this.$options = options || {}

    this.$data = options.data || {}

    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el

    // 2.把data中的成员转换成getter/setter,注入到vue实例中

    this._proxyData(this.$data)

    // 3.调用observer对象,监听数据的变化

    // 4.调用compiler对象,解析指令和差值表达式

  }

  _proxyData (data) {

    // 遍历data中的所有属性

    Object.keys(data).forEach(key => {

      // 把data的属性注入到Vue实例

      Object.defineProperty(this, key, {

        enumerable: true,

        configurable: true,

        get () {

          return data[key]

        },

        set (newValue) {

          if (newValue === data[key]) {

            return 

          }

          data[key] = newValue

        }

      })

    })

  }

}

Observer

  • 功能

    • 负责把data选项中的属性转换成响应式数据

    • data中的某个属性也是对象,把该属性转换成响应式数据

    • 数据变化发生通知

  • 结构


  • 代码


class Observer {

  constructor (data) {

    this.walk(data)

  }

  walk (data) {

    // 1. 判断data是否是对象

    if(!data || typeof data !== 'object') {

      return

    }

    // 2.遍历data对象的所有属性

    Object.keys(data).forEach(key => {

      this.defineReactive(data, key, data[key])

    })

  }

  defineReactive (obj, key, val) {

    Object.defineProperty(obj, key, {

      enumerable: true,

      configurable: true,

      get () {

        return val

      },

      set (newValue) {

        if (newValue === val) {

          return

        }

        data[key] = newValue

      }

    })

  }

}

改编defineReactive方法,实现data中对象的属性也添加setter/getter方法

defineReactive (obj, key, val) {

    // 如果val是对象,把val内部的属性转换成响应式数据

    this.walk(val)

    Object.defineProperty(obj, key, {

      enumerable: true,

      configurable: true,

      get () {

        return val // 使用闭包来扩展了val的作用域

        // return obj[key] ---每次调用都会传递obj[key],会出现堆栈溢出的错误

      },

      set (newValue) {

        if (newValue === val) {

          return

        }

        data[key] = newValue

      }

    })

  }

改变defineReactive方法,实现当data的属性重新赋值新对象时,该对象的属性也会被转换为响应式的

defineReactive (obj, key, val) {

    let that = this

    // 如果val是对象,把val内部的属性转换成响应式数据

    this.walk(val)

    Object.defineProperty(obj, key, {

      enumerable: true,

      configurable: true,

      get () {

        return val // 使用闭包来扩展了val的作用域

        // return obj[key] ---每次调用都会传递obj[key],会出现堆栈溢出的错误

      },

      set (newValue) {

        if (newValue === val) {

          return

        }

        val = newValue

        that.walk(newValue)

        // 发送通知

      }

    })

  }

总结:

  • 实现data中对象的属性也添加setter/getter方法

  • 当data的属性重新赋值新对象时,该对象的属性也会被转换为响应式的


Compiler

  • 功能

    • 负责编译模板,解析指令/差值表达式

    • 负责页面的首次渲染

    • 当数据变化后重新渲染视图

  • 结构

compile的实现




// 编译模板,处理文本节点和元素节点

  compile (el) {

    let childNodes = el.childNodes

    Array.from(childNodes).forEach(node => {

      // 处理文本节点

      if(this.isTextNode(node)) {

        this.compileText(node)

      } else if (this.isElementNode(node)) {

        // 处理文本节点

        this.compileElement(node)

      }

 

      // 判断node节点,是否有子节点,如果有子节点,要递归调用compile

      if (node.childNodes && node.childNodes.length) {

        this.compile(node)

      }

    })

  }

compileText的实现

// 编译文本节点,处理差值表达式

  compileText (node) {

    // console.dir(node)

    // {{ msg }} 

    let reg = /\{\{(.+?)\}\}/

    let value = node.textContent

    if (reg.test(value)) {

      let key = RegExp.$1.trim()

      node.textContent = value.replace(reg, this.vm[key])

    }

  }

compileElement的实现


 // 编译元素节点,处理指令

  compileElement (node) {

    // console.log(node.attributes)

    // 遍历所有的属性节点

    Array.from(node.attributes).forEach(attr => {

      // 判断是否是指令

      let attrName = attr.name

      if (this.isDirective(attrName)) {

        // v-text --> text

        attrName = attrName.substr(2)

        let key = attr.value

        this.update(node, key, attrName)

      }

    })

  }

  update (node, key, attrName) {

    let updateFn = this[attrName + "Updater"]

    updateFn && updateFn(node, this.vm[key])

  }

  // 处理 v-text指令

  textUpdater (node, value) {

    node.textContent = value

  }

  // v-model

  modelUpdater (node, value) {

    node.value = value

  }

Dep(Dependency)


  • 功能

    • 收集依赖,添加观察者(watcher)

    • 通知所有观察者

  • 结构

  • 代码

class Dep {

  constructor () {

    // 存储所有的观察者

    this.subs = []

  }

  // 添加观察者

  addSub (sub) {

    if (sub && sub.update) {

      this.subs.push(sub)

    }

  }

  // 发送通知

  notify () {

    this.subs.forEach(sub => {

      sub.update()

    })

  }

}

使用(observer.js):

defineReactive (obj, key, val) {

    let that = this

    // 负责收集依赖,并发送通知

    let dep = new Dep()

    // 如果val是对象,把val内部的属性转换成响应式数据

    this.walk(val)

    Object.defineProperty(obj, key, {

      enumerable: true,

      configurable: true,

      get () {

        // 收集依赖

        Dep.target && dep.addSub(Dep.target)

        return val // 使用闭包来扩展了val的作用域

      },

      set (newValue) {

        if (newValue === val) {

          return

        }

        val = newValue

        that.walk(newValue)

        // 发送通知

        dep.notify()

      }

    })

  }


















评论者:[[ schemeInfo.user.username ]]

评论内容:[[ schemeInfo.pbody ]]

评论时间:[[ schemeInfo.ptime ]]





发表你的评论:

提交评论
上一篇:
下一篇: