關(guān)于Vue.js的響應(yīng)式原理
寫在前面
因?yàn)閷?duì)Vue.js很感興趣,而且平時(shí)工作的技術(shù)棧也是Vue.js,這幾個(gè)月花了些時(shí)間研究學(xué)習(xí)了一下Vue.js源碼,并做了總結(jié)與輸出。
文章的原地址:https://github.com/answershuto/learnVue。
在學(xué)習(xí)過程中,為Vue加上了中文的注釋https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以對(duì)其他想學(xué)習(xí)Vue源碼的小伙伴有所幫助。
可能會(huì)有理解存在偏差的地方,歡迎提issue指出,共同學(xué)習(xí),共同進(jìn)步。
關(guān)于Vue.js
Vue.js是一款MVVM框架,上手快速簡單易用,通過響應(yīng)式在修改數(shù)據(jù)的時(shí)候更新視圖。Vue.js的響應(yīng)式原理依賴于Object.defineProperty,尤大大在Vue.js文檔中就已經(jīng)提到過,這也是Vue.js不支持E8 以及更低版本瀏覽器的原因。Vue通過設(shè)定對(duì)象屬性的 setter/getter 方法來監(jiān)聽數(shù)據(jù)的變化,通過getter進(jìn)行依賴收集,而每個(gè)setter方法就是一個(gè)觀察者,在數(shù)據(jù)變更的時(shí)候通知訂閱者更新視圖。
將數(shù)據(jù)data變成可觀察(observable)的
那么Vue是如何將所有data下面的所有屬性變成可觀察的(observable)呢?
- function observer(value) {
- Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
- }
- function defineReactive (obj, key, val, cb) {
- Object.defineProperty(obj, key, {
- enumerable: true,
- configurable: true,
- get: ()=>{
- /*....依賴收集等....*/
- },
- set:newVal=> {
- cb();/*訂閱者收到消息的回調(diào)*/
- }
- })
- }
- class Vue {
- constructor(options) {
- this._data = options.data;
- observer(this._data, options.render)
- }
- }
- let app = new Vue({
- el: '#app',
- data: {
- text: 'text',
- text2: 'text2'
- },
- render(){
- console.log("render");
- }
- })
為了便于理解,首先考慮一種最簡單的情況,不考慮數(shù)組等情況,代碼如上所示。在initData中會(huì)調(diào)用observe這個(gè)函數(shù)將Vue的數(shù)據(jù)設(shè)置成observable的。當(dāng)_data數(shù)據(jù)發(fā)生改變的時(shí)候就會(huì)觸發(fā)set,對(duì)訂閱者進(jìn)行回調(diào)(在這里是render)。
那么問題來了,需要對(duì)app._date.text操作才會(huì)觸發(fā)set。為了偷懶,我們需要一種方便的方法通過app.text直接設(shè)置就能觸發(fā)set對(duì)視圖進(jìn)行重繪。那么就需要用到代理。
代理
我們可以在Vue的構(gòu)造函數(shù)constructor中為data執(zhí)行一個(gè)代理proxy。這樣我們就把data上面的屬性代理到了vm實(shí)例上。
- _proxy(options.data);/*構(gòu)造函數(shù)中*/
- /*代理*/
- function _proxy (data) {
- const that = this;
- Object.keys(data).forEach(key => {
- Object.defineProperty(that, key, {
- configurable: true,
- enumerable: true,
- get: function proxyGetter () {
- return that._data[key];
- },
- set: function proxySetter (val) {
- that._data[key] = val;
- }
- })
- });
- }
我們就可以用app.text代替app._data.text了。