vue源码探索之模拟实现vue响应式原理

108 阅读1分钟

本文主要讲述vue响应式的核心实现原理

vue.js

1、将传入的参数保存到属性里

2、将data里的数据转化为getter和setter并挂载到vue实例上面

  • 主要方法_proxyData

3、调用observer监听数据的变化,如果数据发生变化,发送通知

4、调用compiler解析指令和差值表达式

class Vue {
    constructor(options) {
        /**
         * 1、将传入的参数保存到属性中
         * 2、将 data 转化为 getter 和 setter 并注入到 Vue 实例中
         * 3、调用 observer 监听数据的变化
         * 4、调用 compiler 解析指令和差值表达式
         */
        this.$options = options || {};
        this.$data = options.data || {};
        this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
        
        this._proxyData(this.$data)
        new Observer(this.$data)
        new Compiler(this)
    }
    _proxyData(data) {
        Object.keys(data).forEach(key => {
            Object.defineProperty(this, key, {
                enumerable: true,
                configurable: true,
                get() {
                    return data[key]
                },
                set(newVal) {
                    if (newVal === data[key]) {
                        return;
                    }
                    data[key] = newVal;
                }
            })
        })
    }
}

observer.js

主要方法walkdefineReactive

给data所有的属性设置getter和setter转化为响应式数据,并挂载到data上面,在转化响应式的时候,需要调用Dep来收集依赖

  • data属性为对象的时候,也要给该对象设置getter和setter,需要在Object.defineProperty之前调用walk方法

  • 将data属性变更为对象的时候,也要给该对象设置getter和setter,需要在data[key]=newVal之后调用walk方法

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) {
    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
      },
      set (newValue) {
        if (newValue === val) {
          return
        }
        val = newValue
        that.walk(newValue)
        // 发送通知
        dep.notify()
      }
    })
  }
}

compiler.js

编译模板,处理文本节点和元素节点,需要通过compile方法遍历el的子节点,判断节点是文本节点还是元素节点,如果是文本节点,调用compileText方法,如果是元素节点,调用compileElement方法;如果某个子节点下也存在子节点,也需要编译,递归调用compile方法

  • compileText:用正则匹配差值表达式,正则匹配到的{{}}}内的字符串就是data中的key值,然后将vm[key]赋值到textContent

  • compilerElement:遍历元素所有的属性attrsbutes获取指令,判断是否是指令,然后判断指令是v-text还是v-model,如果是v-text,将数据赋值到textContent中,如果是v-model,将数据赋值到value

  • compileText``textUpdate/modelUpdate:需要添加观察者Watcher,用来实现数据改变的时候,更新视图

  • modelUpdate:需要给node添加input的事件监听,用来实现视图改变的时候,更新数据

class Compiler {
    constructor(vm) {
        this.el = vm.$el
        this.vm = vm
        this.compile(this.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)
            }
        })

    }
    compileElement(node) {
        // 遍历元素所有的属性节点
        let attrs = node.attributes;
        Array.from(attrs).forEach(attr => {
            let attrName = attr.name;
            // 判断是否是指令
            if (this.isDirective(attrName)) {
                let key = attr.value;
                attrName = attr.name.substr(2);
                this.update(node, key, attrName);
            }
        })
    }
    update(node, key, type) {
        let updateFn = this[`${type}Update`];
        updateFn && updateFn.call(this, node, this.vm[key], key);
    }
    textUpdate(node, value, key) {
        node.textContent = value
        new Watcher(this.vm, key, (newVal) => {
            node.textContent = newVal
        });
    }
    modelUpdate(node, value, key) {
        node.value = value
        new Watcher(this.vm, key, (newVal) => {
            node.value = newVal
        });
        node.addEventListener('input', () => {
            this.vm[key] = node.value
        })
    }
    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, (newVal) => {
                node.textContent = newVal
            })
        }
    }
    isDirective(attrName) {
        return attrName.startsWith('v-')
    }
    isTextNode(node) {
        return node.nodeType === 3
    }
    isElementNode(node) {
        return node.nodeType === 1
    }
}

Dep.js

通过addSub收集依赖、并且在数据改变的时候,调用notify发送通知,让wather更新视图

class Dep {
    constructor() {
        this.subs = []
    }
    addSub(sub) {
        if (sub && sub.update) {
            this.subs.push(sub)
        }
    }
    notify() {
        this.subs.forEach(sub => {
            sub.update()
        })
    }
}

watcher.js

需要传入vmkeycb参数,通过update方法来调用传入的callback来更新视图

  • Watcher类里面,初始化的时候向Dep类上添加一个target属性,值为当前的Watcher实例,用于在Observer里面调用Dep收集依赖的时候,判断当前依赖的指向
class Watcher {
  constructor (vm, key, cb) {
    this.vm = vm
    // data中的属性名称
    this.key = key
    // 回调函数负责更新视图
    this.cb = cb

    // 把watcher对象记录到Dep类的静态属性target
    Dep.target = this
    // 触发get方法,在get方法中会调用addSub
    this.oldValue = vm[key]
    Dep.target = null
  }
  // 当数据发生变化的时候更新视图
  update () {
    let newValue = this.vm[this.key]
    if (this.oldValue === newValue) {
      return
    }
    this.cb(newValue)
  }
}