vue響應式原理,去掉優化,只看核心

Vue響應式原理

做爲寫業務的碼農,幾乎沒必要知道原理。可是當你去找工做的時候,但是須要造原子彈的,什麼都得知道一些才行。因此找工做以前能夠先複習下,只要是關於vue的,一定會問響應式原理。html

核心:vue

//es5
Object.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()
      }
    })
  }

}

// 蒐集與觸發wacher
class 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 = null


class 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
    // 從新獲取值
    this.value = this.get()
    if (this.cb) {
      //若是是watch,會觸發watch的函數
      this.cb.call(this.vm, this.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>

相關文章
相關標籤/搜索