美文网首页
vue响应式和依赖收集

vue响应式和依赖收集

作者: 一蓑烟雨任平生_cui | 来源:发表于2021-01-08 12:34 被阅读0次

    看了vue源码后实现的一个很简单很简单的vue😂

    目的主要是串一下new Vue()之后到组件挂载的流程,及数据更新时视图的更新流程。

    源码主要流程如下:

    1. new Vue()
    2. this._init()
      1. initLifecycle(vm)
      2. initEvents(vm)
      3. initRender(vm)
      4. callHook(vm, 'beforeCreate')
      5. initInjections(vm)
      6. initState(vm)
        1. initProps(vm)
        2. initMethods(vm)
          1. vm.XXX = this.methods[XXX]
        3. initData(vm)
          1. observe(value) // 开启响应式
        4. initComputed(vm)
        5. initWatch(vm)
          1. vm.$watch(expOrFn, cb, option)
      7. initProvide(vm)
      8. callHook(vm, 'created')
      9. vm.options.el ? vm.mount(vm.options.el) : vm.mount(el)
    3. vm.$mount(el)
      1. mountComponent(vm)
        1. callHook(vm, 'beforeMount')
        2. new Watcher()
        3. callHook(vm, 'mounted')

    具体包括对象和数组的响应式原理、发布订阅、观察者、单例模式、依赖收集、模版编译

    // index.js
    import { initState, initMethods } from './init.js'
    import initLifecycle from './initLifecycle.js'
    import mounted from './mounted.js'
    import Compiler from './compiler.js'
    
    class Vue {
      constructor(options) {
        this.vm = this
        this.$options = options
        this.init(this)
      }
    
      // 初始化操作
      init(vm) {
        initState(vm)
        initMethods(vm)
        initLifecycle(vm)
        mounted(vm)
      }
    
      $mount(el) {
        Compiler.getInstance(this, el)
      }
    }
    
    export default Vue
    
    // init.js
    import { def, observe, proxy } from './utils.js'
    
    function initData(vm) {
      let data = vm.$options.data
    
      // 保存_data 主要是因为data可能是函数
      data = vm._data = typeof data === 'function' ? data.call(vm, vm) : data || {}
    
      Object.keys(data).forEach(key => {
        proxy(vm, '_data', key)
      })
    
      observe(data)
    }
    
    function initProps() {}
    function initComputed() {}
    function initWatch() {}
    
    export function initState(vm) {
      initData(vm)
      initProps(vm)
      initComputed(vm)
      initWatch(vm)
    }
    
    export function initMethods(vm) {
      const methods = vm.$options.methods
    
      if (methods) {
        for (const key in methods) {
          if (methods.hasOwnProperty(key)) {
            def(vm, key, methods[key])
          }
        }
      }
    }
    
    // initLifecycle.js
    export default function initLifecycle(vm) {
      const created = vm.$options.created
    
      if (created) {
        created.call(vm)
      }
    }
    
    // mounted.js
    import Compiler from './compiler.js'
    
    // 挂载
    export default function mounted(vm) {
      const el = vm.$options.el
    
      // 是否提供 el 选项,如果没有则调用实例的 $mount() 方法
      if (el) {
        Compiler.getInstance(vm, el)
      }
    }
    
    // utils.js
    import Observer from './observer.js'
    
    export function def(target, key, value, enumerable = false) {
      Object.defineProperty(target, key, {
        value,
        enumerable,
        writable: true,
        configurable: true
      })
    }
    
    // 将属性代理到实例上 以便可以通过 this.XXX 方式访问
    export function proxy(target, sourceKey, key) {
      Object.defineProperty(target, key, {
        get() {
          return target[sourceKey][key]
        },
        set(newValue) {
          target[sourceKey][key] = newValue
        }
      })
    }
    
    export function observe(data) {
      if (typeof data !== 'object' || data === null) return
    
      return new Observer(data)
    }
    
    // array.js
    const arrayPrototype = Array.prototype
    const arrayMethods = Object.create(arrayPrototype)
    const methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    
    // 重写数组方法
    methodsToPatch.forEach(method => {
      // 缓存原方法
      const originalMethod = arrayPrototype[method]
    
      arrayMethods[method] = function (...args) {
        const result = originalMethod.apply(this, args)
        const ob = this.__ob__
        let inserted
    
        switch (method) {
          // 首尾追加元素
          case 'push':
          case 'unshift':
            inserted = args
            break
          case 'splice':
            inserted = args.slice(2)
            break
          default:
            break
        }
    
        if (inserted) {
          // this 为修改后的数组
          // 在一开始初始化数组时已经给它添加了属性 __ob__ 指向 Observer 的实例
          ob.observeArray(this)
        }
    
        return result
      }
    })
    
    export default arrayMethods
    
    // defineReactive.js
    import Dep from './dep.js'
    import { observe } from './utils.js'
    
    // 响应式
    export default function defineReactive(target, key, value) {
      observe(value)
    
      const dep = new Dep()
    
      Object.defineProperty(target, key, {
        get() {
          Dep.target && dep.addSub(Dep.target)
    
          return value
        },
        set(newValue) {
          if (value !== newValue) {
            value = newValue
            observe(newValue)
    
            // 通知依赖 更新视图
            dep.notify()
          }
        },
        enumerable: true,
        configurable: true
      })
    }
    
    // dep.js
    
    // 订阅发布
    export default class Dep {
      constructor() {
        this.subs = []
      }
      addSub(watcher) {
        this.subs.push(watcher)
      }
      notify() {
        this.subs.forEach(watcher => {
          watcher.update()
        })
      }
    }
    
    // watcher.js
    import Dep from './dep.js'
    
    // 观察者
    export default class Watcher {
      constructor(vm, exp, cb) {
        this.vm = vm
        this.exp = exp
        this.cb = cb
    
        Dep.target = this
    
        // 取值
        this.get()
    
        Dep.target = null // 避免重复添加Watcher
      }
    
      get() {
        this.value = this.vm[this.exp]
        this.cb(this.value)
      }
    
      update() {
        const value = this.vm[this.exp]
    
        this.cb.call(this.vm, value, this.value)
        this.value = value
      }
    }
    
    // observer.js
    import arrayMethods from './array.js'
    import defineReactive from './defineReactive.js'
    import { observe, def } from './utils.js'
    
    export default class Observer {
      constructor(data) {
        this.value = data
        // 便于在其他处操作data时,可以找到该data对应的Observer实例
        def(data, '__ob__', this) // 必须定义成不可枚举的,否则会陷入死循环。仔细品一下为啥😄
    
        if (Array.isArray(data)) {
          // 数组
          data.__proto__ = arrayMethods
    
          this.observeArray(data)
        } else {
          // 对象
          this.walk(data)
        }
      }
    
      walk(obj) {
        Object.keys(obj).forEach(key => {
          defineReactive(obj, key, obj[key])
        })
      }
    
      observeArray(array) {
        for (let i = 0; i < array.length; i++) {
          observe(array[i])
        }
      }
    }
    
    // compiler.js
    
    import Watcher from './watcher.js'
    
    // 插值正则
    const REG = /\{\{(.*)\}\}/
    
    class Compiler {
      constructor(vm, el) {
          this.$el = document.querySelector(el)
          this.$vm = vm
    
        if (this.$el) {
          // 将节点转成文档片段
          this.nodeToFragment(this.$el)
    
          // 编译
          this.compile(this.$fragment)
    
          // 将编译后的片段插入html
          this.$el.appendChild(this.$fragment)
        }
      }
    
      // 采用单例模式 避免同时存在传入 el 选项 和 调用了 $mount(el)
      static getInstance(vm, el) {
        if (!this.instance) {
          this.instance = new Compiler(vm, el)
        }
        return this.instance
      }
    
      nodeToFragment(el) {
        const fragment = document.createDocumentFragment()
        let child
    
        while ((child = el.firstChild)) {
          fragment.appendChild(child)
        }
        this.$fragment = fragment
      }
    
      compile(fragment) {
        const childNodes = fragment.childNodes
    
        Array.from(childNodes).forEach(node => {
          if (this.isElement(node)) {
            // console.log('编译元素', node.nodeName)
    
            const attrs = node.attributes
    
            Array.from(attrs).forEach(attr => {
              const attrName = attr.name
              const attrValue = attr.value
    
              if (this.isDirective(attrName)) {
                // 指令
                const dirName = attrName.slice(2) + 'Dir'
    
                console.log(dirName)
    
                this[dirName] && this[dirName](node, this.$vm, attrValue)
              }
    
              // 事件
              const eventName = this.isEvent(attrName)
    
              if (eventName) {
                this.eventHandler(node, eventName, attrValue)
              }
            })
          } else if (this.isInterpolation(node)) {
            // console.log('编译插值表达式', node.nodeValue)
            this.update(node, this.$vm, RegExp.$1, 'text')
          }
    
          if (node.childNodes?.length) {
            this.compile(node)
          }
        })
      }
    
      isElement({ nodeType }) {
        return nodeType === 1
      }
    
      // 含有插值表达式的文本节点
      isInterpolation({ nodeType, nodeValue }) {
        return nodeType === 3 && REG.test(nodeValue)
      }
    
      textUpdate(node, value) {
        node.textContent = value
      }
    
      // 更新函数
      update(node, vm, exp, dir) {
        const updateFn = this[`${dir}Update`]
    
        new Watcher(vm, exp, function (newValue) {
          updateFn && updateFn(node, newValue)
        })
      }
    
      isDirective(name) {
        return /^v-(.*)/.test(name)
      }
    
      isEvent(name) {
        return /^v-on:|@(.*)/.test(name) && RegExp.$1
      }
    
      textDir(node, vm, exp) {
        this.commonWatcher(node, vm, 'textContent', exp)
      }
    
      modelDir(node, vm, exp) {
        this.commonWatcher(node, vm, 'value', exp)
    
        node.addEventListener('input', e => {
          vm[exp] = e.target.value
        })
      }
    
      htmlDir(node, vm, exp) {
        this.commonWatcher(node, vm, 'innerHTML', exp)
      }
    
      commonWatcher(node, vm, prop, exp) {
        new Watcher(vm, exp, function (value) {
          node[prop] = value
        })
      }
    
      eventHandler(node, eventName, exp) {
        node.addEventListener(eventName, this.$vm[exp].bind(this.$vm))
      }
    }
    
    export default Compiler
    
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    
    <body>
      <div id="app">
        <h1>{{title}}</h1>
        <h2>{{age}}</h2>
        <h3>
          <span>{{age}}</span>
        </h3>
        <hr>
        <input type="text" v-model="tel">
        <h3>{{tel}}</h3>
        <hr>
        <div v-text="desc"></div>
        <br>
        <div>这是静态文本</div>
        <hr>
        <button @click="add">add</button>
        <h2>{{count}}</h2>
        <div v-html="html"></div>
      </div>
    </body>
    </html>
    
    <script type="module">
      import Vue from './index.js'
    
      new Vue({
        el: '#app',
        data() {
          return {
            title: 'hello',
            tel: 1571121,
            desc: '这是v-text文本',
            count: 2,
            age: 12,
            html: '<p>这是html片段</p>',
            info: {
              name: 'mike',
              age: 23
            },
            skill: ['eat', 'song', {
              foo: 'bar'
            }]
          }
        },
        methods: {
          add(e) {
            this.count = this.count + 1
            this.html = '<h2>修改html片段</h2>'
            this.tel = 565744
            this.desc = '3453534'
          }
        },
        created() {
          setTimeout(() => {
            this.age = 34
          }, 2000)
        }
      }).$mount('#app')   // 如果没有传el选项,则调用$mount方法实现挂载
    
      // OR
      // const vm = new Vue()
      // vm.$mount('#app')
    </script>
    

    相关文章

      网友评论

          本文标题:vue响应式和依赖收集

          本文链接:https://www.haomeiwen.com/subject/uynxaktx.html