Vue响应式原理

作为写业务的码农,几乎不必知道原理。但是当你去找工作的时候,可是需要造原子弹的,什么都得知道一些才行。所以找工作之前可以先复习下,只要是关于vue的,必定会问响应式原理。

核心:

//es5Object.defineProperty(obj,key,{    get() {        // 获取obj[key]的时候触发    },    set(val) {       // obj[key] = 'xxx'时触发    }})

其实,只需要在修改data值的时候,需要触发一个回调方法,来更新与此值有关的数据,就完了。但是你面试的时候,那些大佬可不会觉得是这样的,需要把整个流程说明白才行。

简单的Vue响应式代码如下:

Vue.js:

class Vue {  constructor(opts) {    this.opts = opts    if (opts.data) this.initData(opts.data);    if (opts.watch) this.initWatch(opts.watch);    if (opts.computed) this.initComputed(opts.computed);    if (opts.el) this.$mount(opts.el)  }  initData(data) {    // 让data上的数据被get的时候能够搜集watcher,data变为观察者    new Observable(data);    this.data = data    // 数据可在this上直接读取    Object.keys(data).forEach(key => {      this.proxy(key)    })  }  initWatch(watch) {    Object.keys(watch).forEach(key => {      //       new Watcher(this, key, watch[key])    })  }  initComputed(data) {    Object.keys(data).forEach(key => {      new Watcher(this, key)      this.proxy(key, {        get: data[key],      })    })  }  $mount(el) {    el = document.querySelector(el)    this.template = el.innerHTML    this.el = el    const fn = _ => {      const nwTemp = this.parseHTML(this.template)      this.el.innerHTML = nwTemp      if(this.opts.mounted) {        this.opts.mounted.call(this)      }    }    new Watcher(this, fn)  }  parseHTML(template) {    return template.replace(/\{\{(.*?)\}\}/g, (str, str1) => {      return this[str1.trim()]    })  }  // 将数据直接挂到this上,使用getterSetter代理,获取vm.data上的值  proxy(key, getterSetter) {    const vm = this    getterSetter = getterSetter || {      set(value) {        vm.data[key] = value      },      get() {        return vm.data[key]      }    }    Object.defineProperty(vm, key, getterSetter)  }}// 给data作为观察者用的class Observable {  constructor(obj) {    Object.keys(obj).forEach(key => {      this.defineReact(obj, key, obj[key])    });  }  defineReact(obj, key, value) {      const dep = new Dep()    Object.defineProperty(obj, key, {      get() {        // 获取data里面信息的时候,能够搜集依赖,这些依赖都是watcher实例        if (Dep.target) {          dep.append(Dep.target)        }        return value      },      set(val) {        value = val        // 修改data里面数据的时候,去通知已搜集的依赖更新        dep.notify()      }    })  }}// 搜集与触发wacherclass Dep {  constructor() {    this.subs = []  }  append(watcher) {    // 避免重复添加watcher    // watcher 在update的时候,会重新获取值,此时不必再添加    if(this.subs.includes(watcher)) return    this.subs.push(watcher)  }  notify() {    this.subs.forEach(watcher => {      watcher.update()    })  }}Dep.target = nullclass Watcher {  // keyOrFn 为字符串或者function,opts.watch为字符串,computed,$mount中为function  constructor(vm, keyOrFn, cb) {    this.cb = cb    if (typeof keyOrFn === 'string') {      this.getter = function () {        return vm[keyOrFn] // 例:initWatch时,watch:{a(){}} ,a为data里的数据,此处获取vm.a会触发a的收集      }    } else {            this.getter = keyOrFn // 如果为fn(computed中)值为此函数的返回值    }    this.value = this.get()  }  get() {    Dep.target = this // 此watcher记录下来    const value = this.getter(this.vm)     // 运行getter,如果是watch,获取一次data里的值,完成收集。如果是computed,运行其方法,其方法中含有data的值时,会触发收集    // 当更新时,也会触发getter     Dep.target = null     return value  }  update() {    // 更新    const oldValue = this.value    // 重新获取值    const value = this.get()    if (this.cb) {      //如果是watch,会触发watch的函数      this.cb.call(this.vm, value, oldValue)    }  }}

index.html

  <div id="app">      <h2>Vue响应式原理</h2>      <br />      msg: {{ msg }}      <br />      <p>num: {{ num }}</p>      <p>num+1计算属性值:{{ add1 }}</p>      <button>add</button>    </div>    <script src="./Vue.js"></script>    <script>      const app = new Vue({        el: "#app",        data: {          msg: "这是msg",          num: 1        },        watch: {          msg(newVal, oldVal) {            console.log(newVal, oldVal);          }        },        computed: {          add1() {            return this.num + 1;          }        },        mounted() {          const btn = document.querySelector("button");          btn.onclick = _ => {            this.num += 1;          };        }      });    </script>