1. vue-reactive

Observer 劫持 data 中的数据,get 的时候,向 Dep 中订阅 Watcher,set 的时候,让 Dep 调用notify 事件发布消息触发 Watcher 中的 update 方法 ,更新绑定了对应 data 数据的 dom。Compiler 主要负责解析 vue 特有的一些指令,比如: v-model、@click,Watcher 就是在 Compiler 的构造函数中实例化的。

Observer 主要目的是劫持 data 中的数据。

Dep 负责依赖的搜集。

Compile 是编译模板中的信息,比如指令。

Watcher 是 data 数据与 dom 元素连接的桥梁。

1.1. vue2.0

Object.defineProperty

export class Vue {
  constructor(options = {}) {
    this.$options = options
    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
    this.$data = options.data
    this.$methods = options.methods

    this.proxy(this.$data)

    // observer 拦截 this.$data
    new Observer(this.$data)

    new Compiler(this)
  }

  // 代理一下,this.$data.xxx -> this.xxx
  proxy(data) {
    Object.keys(data).forEach(key => {
      // this
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          return data[key]
        },
        set(newValue) {
          // NaN !== NaN
          if (data[key] === newValue || __isNaN(data[key], newValue)) return
          data[key] = newValue
        }
      })
    })
  }
}

function __isNaN(a, b) {
  return Number.isNaN(a) && Number.isNaN(b)
}

class Dep {
  constructor() {
    this.deps = new Set()
  }
  add(dep) {
    if (dep && dep.update) this.deps.add(dep)
  }
  notify() {
    this.deps.forEach(dep => dep.update())
  } 
}

// html -> <h1>{{ count }}</h1> -> compiler 发现有 {{ count }}
// -> new Watcher(vm, 'count', () => renderToView(count)) -> count getter 被触发
// -> dep.add(watcher实例) -> this.count++ -> count setter -> dep.notify
// -> () => renderToView(count) -> 页面就变了

class Watcher {
  // vm - Vue 实例
  constructor(vm, key, cb) {
    this.vm = vm
    this.key = key
    this.cb = cb // 在我们今天的列子里面,就是绘制数据到页面

    // window.watcher = this
    Dep.target = this
    this.__old = vm[key] // 存下了初始值,触发 getter
    Dep.target = null
  }
  update() {
    let newValue = this.vm[this.key]
    if (this.__old === newValue || __isNaN(newValue, this.__old)) return
    this.cb(newValue)
  }
}

class Observer {
  constructor(data) {
    this.walk(data)
  }
  walk(data) {
    if (!data || typeof data !== 'object') return
    Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]))
  }
  defineReactive(obj, key, value) {
    let that = this
    this.walk(value)
    let dep = new Dep()
    Object.defineProperty(obj, key, {
      configurable: true,
      enumerable: true,
      get() {
        // Watcher 实例
        Dep.target && dep.add(Dep.target)
        return value
      },
      set(newValue) {
        if (value === newValue || __isNaN(value, newValue)) return
        value = newValue
        that.walk(newValue)
        dep.notify()
      }
    })
  }
}

class Compiler {
  constructor(vm) {
    this.el = vm.$el
    this.vm = vm
    this.methods = vm.$methods

    this.compile(vm.$el)
  }
  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)
      }

      if (node.childNodes && node.childNodes.length) this.compile(node)
      // ...
    })
  }
  // <input v-model="msg"/>
  compileElement(node) {
    if (node.attributes.length) {
      Array.from(node.attributes).forEach(attr => {
        let attrName = attr.name
        if (this.isDirective(attrName)) {
          // v-on:click  v-model
          attrName = attrName.indexOf(':') > -1 ? attrName.substr(5) : attrName.substr(2)
          let key = attr.value
          this.update(node, key, attrName, this.vm[key])
        }
        // ...
      })
    }
  }
  update(node, key, attrName, value) {
    if (attrName === 'text') {
      node.textContent = value
      new Watcher(this.vm, key, val => node.textContent = val)
    }
    else if (attrName === 'model') {
      node.value = value
      new Watcher(this.vm, key, val => node.value = val)
      node.addEventListener('input', () => {
        this.vm[key] = node.value
      })
    }
    else if (attrName === 'click') {
      node.addEventListener(attrName, this.methods[key].bind(this.vm))
    }
    // ....
  }
  // 'this is {{ count }}'
  compileText(node) {
    let reg = /\{\{(.+?)\}\}/
    let value = node.textContent
    if (reg.test(value)) {
      let key = RegExp.$1.trim()
      node.textContent = value.replace(reg, this.vm[key])

      new Watcher(this.vm, key, val => {
        node.textContent = val
      })
    }
  }
  isDirective(str) {
    return str.startsWith('v-')
  }
  isElementNode(node) {
    return node.nodeType === 1
  }
  isTextNode(node) {
    return node.nodeType === 3
  }
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script type='module'>
    import { Vue } from './index.js'
    let vm = new Vue({
      el: '#app',
      data: {
        msg: 'Hello Vue2.x',
        count: 666
      },
      methods: {
        increase() {
          this.count++
        }
      }
    })
  </script>
</head>
<body>
  <div id="app">
    <h3>{{ msg }}</h3>
    <h3>{{ count }}</h3>
    <h1>v-text</h1>
    <div v-text="msg"></div>
    <h1>v-model</h1>
    <input type="text" v-model="msg" >
    <input type="text" v-model="count">
    <button v-on:click="increase">按钮</button>
  </div>
</body>
</html>

1.1.1. Object.defineProperty(obj, key, handler)

  • 对象遍历方法:Reflect.ownKeys(obj) / Object.keys()

  • 递归模板:首先是终止条件

const render = (key, val) => {
  console.log(`SET key=${key} val=${val}`)
}

const reactive = (obj) => {
  // 递归终止条件
  if (Object.prototype.toString.call(obj) !== '[object Object]') {
    return
  }
  Reflect.ownKeys(obj).forEach(key => {
    let val = obj[key]
    // 下探到下一层
    reactive(val)
    // 当前层逻辑处理
    Object.defineProperty(obj, key, {
      get() {
        return val
      },
      set(newVal) {
        if (val !== newVal) {
          val = newVal
          render(key, val)
        }
      }
    })
  })
}


const data = {
  a: 1,
  b: 2,
  c: {
    c1: {
      d: 99
    },
    c2: 3
  }
}

reactive(data1)

data.a = 5
data.c.c2 = 6
data.c.c1.d = 7

1.1.2. 数组的处理

  • 改写数组原型方法:arr.proto === Array.prototype

  • 数组原型方法不能使用箭头函数

  • 数组的判断方法:Object.prototype.toString.call(arr) === '[object Array]'

const render = (method, ...val) => {
  console.log(`method:${method}-val:${val}`)
}

const reactive = (arr) => {
  // 原来的数组原型方法
  const arrPrototype = Array.prototype

  // 创建新的数组原型方法
  const newArrPrototype = Object.create(arrPrototype)

  const methods = ['push', 'pop', 'unshift', 'shift', 'splice', 'sort', 'reverse']

  // 改写新的数组原型方法
  methods.forEach(method => {
    newArrPrototype[method] = function() {
      arrPrototype[method].call(this, ...arguments)
      render(method, ...arguments)
    }
  })
  if (Object.prototype.toString.call(arr) !== '[object Array]') return
  // 用新的数组原型方法替换实例旧的原型方法 arr.__proto__ === Array.prototype
  arr.__proto__ = newArrPrototype
}

data = [1, 2, 3, 4]

reactive(data)

data.push(5)
data.splice(0, 2)
data.reverse()
data.sort((a, b) => a - b)

1.2. vue3.0

proxy + composition-api

vue3.0 的数据双向绑定,从 reactive 说起,触发 get 的时候,订阅依赖,触发 set 和 delete 的时候,发布依赖,依赖的收集本质还是发布订阅模式。

const reactive = (data) => {
  return new Proxy(data, {
    get(target, key, receiver) {
      const ret = Reflect.get(target, key, receiver)
      console.log('get', key)
      // 此处订阅依赖,调用 track(target, key)
      return ret
    },
    set(target, key, val, receiver) {
      Reflect.set(target, key, val, receiver)
      console.log('set', key, val)
      // 此处发布依赖,调用 trigger(target, key)
      return true
    },
    deleteProperty(target, key, receiver) {
      const ret = Reflect.deleteProperty(target, key, receiver)
      console.log('delete', key)
      // 此处也是发布依赖,调用 trigger(target, key),相当于 dep.notify()
      return ret 
    }
  })
}

1.3. new Proxy(obj, handler)

  • handler 个方法对应的参数:target、key、value、receiver

  • Reflect.get(target, key, receiver)

  • Reflect.set(target, key, val, receiver)

  • Reflect.deleteProperty(target, key)

const observableStore = new Map()

const makeObservable = (target) => {
  let handleName = Symbol('handle')
  observableStore.set(handleName, [])
  target.observe = function(handler) {
    observableStore.get(handleName).push(handler)
  }

  const proxyHandler = {
    get(target, key, receiver) {
      observableStore.get(handleName).forEach(handler => handler('GET', key, target[key]))
      // 懒监听,去获取的时候才监听对象里面的对象,而不是直接递归循环监听
      if (typeof target[key] === 'object' && target[key] !== null) {
        return new Proxy(target[key], proxyHandler)
      }

      return Reflect.get(target, key, receiver)
    },
    set(target, key, val, receiver) {
      observableStore.get(handleName).forEach(handler => handler('SET', key, val))
      Reflect.set(target, key, val, receiver)
    },
    deleteProperty(target, key) {
      observableStore.get(handleName).forEach(handler => handler('DELETE', key, target[key]))
      Reflect.deleteProperty(target, key)
    }
  }

  return new Proxy(target, proxyHandler)
}

let user = {
  a: {
    b: 1
  }
}

user = makeObservable(user)

user.observe((action, key, value) => {
  console.log(`${action} key=${key} value=${value}`)
})

user.name = 'Tommy'
console.log(user.name)
delete user.name
console.log(user.a.b)
console.log(user.a)
user.a.b = 2
Copyright © tomgou 2022 all right reserved,powered by Gitbook该文章修订时间: 2023-08-28 17:33:23

results matching ""

    No results matching ""