從最簡單的案例,來學習Vue.js源碼。vue
<body> <div id='app'> <input type="text" v-model="message">---{{message}} </div> </body> <script src='./vue.js'></script> <script> var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } });
</script>
(一)爲什麼能夠直接使用 Vue?node
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.Vue = factory()); }(this, (function () { 'use strict';
function Vue$3(options){
this._init(options);
}
return Vue$3;
})));
此寫法,即兼容了 cmd ,也兼容了ES6,也將Vue對象掛載到window對象。react
(二)Vue雙向綁定的實現原理es6
Vue 採用數據劫持,經過Object.defineProperty的getter和setter,結合觀察者模式來實現數據綁定。數組
與此相關的對象有四個:服務器
Observer(數據監聽器):對數據對象的全部屬性進行監聽,若是 Data Change ===通知==> Watcher;閉包
Watcher(訂閱者): 鏈接 Observer、Compile 的橋樑。app
Dep(消息訂閱器):收集 Watcher,數據變更觸發 notify 函數,再調用 Watcher.update() ide
Compile:(Directive 指令解析器):對元素節點進行掃描和解析,替換、綁定相應回調函數。函數
其實這裏我有一個疑惑: 爲何Dep 要收集 Watcher?
看源碼的時候我重點關注上面 Observer、Watcher、Dep.
進入Vue構造函數,實例化對象,進入 Vue.prototype._init() 函數。
(1) Vue.prototype._init() 函數
1 Vue.prototype._init = function (options) { 2 var vm = this; 3 // a uid 4 vm._uid = uid$1++; 5 vm._isVue = true; //表明一個Vue實例,不是組件,不用被監聽 6 if (options && options._isComponent) { 7 initInternalComponent(vm, options);//注入的參數中是組件處理 8 } else { 9 //實例內部初始化,返回 vm.constructor.options ,若是含有 super,內部也會調用 mergeOptions() 10 //extend(Vue.options,XXX)以及 initGlobalAPI() 中擴展了options 全部實例基礎參數 11 var tempOptions = resolveConstructorOptions(vm.constructor); 12 vm.$options = mergeOptions(tempOptions, options || {}, vm); 13 } 14 //完成後,vm 擁有了 _renderProxy 對象屬性 15 initProxy(vm); 16 //爲何要保存自身引用呢? 17 vm._self = vm; 18 //一步一步往 vm 添加屬性,方法 19 initLifecycle(vm); // 20 initEvents(vm);//父子組件通訊工做 21 initRender(vm); 22 callHook(vm, 'beforeCreate'); 23 initInjections(vm); // resolve injections before data/props 24 initState(vm);//完成 watch observe 等初始化 25 initProvide(vm); // resolve provide after data/props 26 callHook(vm, 'created'); 27 28 //Vue 傳入的參數中有el屬性,進行掛載,啓動 29 if (vm.$options.el) { 30 vm.$mount(vm.$options.el); 31 } 32 }; 33 }
2-13行代碼,經過整合傳入的參數,賦值vm. $options屬性中,以便方便的取出。
若是咱們使用了 Vuex或者VueRouter,也會將其方法屬性掛載在 $options屬性中。
(2)initProxy()
爲vm新增了一個 _renderProxy 代碼屬性。
1 initProxy = function initProxy(vm) { 2 if (hasProxy) { 3 //是否支持 es6 的代理 4 var options = vm.$options; 5 var handlers = options.render && options.render._withStripped 6 ? getHandler 7 : hasHandler; 8 vm._renderProxy = new Proxy(vm, handlers); 9 } else { 10 vm._renderProxy = vm; 11 } 12 }; 13 var hasHandler = { 14 has: function has(target, key) { 15 var has = key in target; 16 //判斷key是否跟內置全局變量衝突 17 var isAllowed = allowedGlobals(key) || key.charAt(0) === '_'; 18 if (!has && !isAllowed) { 19 warnNonPresent(target, key); 20 } 21 return has || !isAllowed 22 } 23 }; 24 25 var getHandler = { 26 get: function get(target, key) { 27 if (typeof key === 'string' && !(key in target)) { 28 warnNonPresent(target, key); 29 } 30 return target[key] 31 } 32 };
vm. _renderProxy 會在 Vue.prototype._render() 中以下使用。
vnode = render.call(vm._renderProxy, vm.$createElement);
(三) Observer 的初始化
(1) initData()函數
initData完成了對model元素Data數據格式化、元素代理初始化、監聽初始化。
1 function initData(vm) { 2 var data = vm.$options.data; 3 /*獲取自定義 data 數據,這裏爲何使用閉包,而不是直接獲取 options.data*/ 4 data = vm._data = typeof data === 'function' ? 5 getData(data, vm) : data || {}; 6 7 var keys = Object.keys(data); 8 var props = vm.$options.props; 9 var methods = vm.$options.methods; 10 var i = keys.length; 11 while (i--) { 12 //循環爲每一個 data屬性加入代理 13 var key = keys[i]; 14 if (props && hasOwn(props, key)) { 15 } else if (!isReserved(key)) { 16 //若是key以 $ _ 開頭,不做處理,是Vue的關鍵字 17 //複製建立新的屬性。直接掛在在vm 下,且自定義了 get/set 方法, 18 //其真實的值在 vm._data 下 19 proxy(vm, "_data", key); 20 } 21 } 22 //處理完成後,vm 以及 vm._data 都含有用戶定義的model數據 23 //監聽 24 observe(data, true /* asRootData */); 25 }
(2)observe(data,true)
這個函數會返回一個Observer() 實例對象, 若是model還未添加監聽屬性,則添加。
1 function observe(value, asRootData) { 2 if (!isObject(value)) { 3 return 4 } 5 var ob; 6 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { 7 //有 __ob__ 屬性,即已經被監聽 8 ob = value.__ob__; 9 } else if ( 10 //是否應該被監聽 是不是服務器渲染 必須爲數組或對象 是否可擴展 是否實例纔有的屬性 11 observerState.shouldConvert && !isServerRendering() && 12 (Array.isArray(value) || isPlainObject(value)) && 13 Object.isExtensible(value) && !value._isVue) {//生成一個觀察者 14 ob = new Observer(value); 15 } 16 //根數據計數 屬性 ++ 17 if (asRootData && ob) { 18 ob.vmCount++; 19 } 20 //ob 包含了 dep,value,__ob__屬性引用自身,自定義 get\set 方法, 21 //計數vmCount,原型方法haiyou observeArray,監聽對象的walk方法 22 return ob 23 }
observe()函數的關鍵點在於 ob=new Observer(value) 。
(3)Observer 對象
Observer初始化時,會將當前Observer自身引用掛載在 model 中,
同時循環爲每一個model屬性重寫get/set 方法,這樣就實現了數據劫持。
1 /** 2 * 建立Dep對象實例 3 * 將自身this添加到value的ob屬性上 4 */ 5 var Observer = function Observer(value) { 6 this.value = value; 7 this.dep = new Dep(); //Dep 構造函數: uid--id,subs[] --依賴收集 8 this.vmCount = 0; 9 def(value, '__ob__', this);//爲model屬性添加 __ob__屬性 10 if (Array.isArray(value)) { 11 //遞歸調用 Observer(value) 最後仍然走 walk() 12 var augment = hasProto ? protoAugment : copyAugment; 13 augment(value, arrayMethods, arrayKeys);//原型擴展 14 this.observeArray(value); 15 } else { 16 this.walk(value); 17 } 18 }; 19 20 //Walk 爲每一個屬性對象添加get/set 21 Observer.prototype.walk = function walk(obj) { 22 var keys = Object.keys(obj); 23 for (var i = 0; i < keys.length; i++) { 24 defineReactive$$1(obj, keys[i], obj[keys[i]]); 25 } 26 };
(4)defineReactive$$1()
這個函數主要是對Object某個屬性值設置了數據劫持,也就是經過重寫對象屬性中的 get/set 方法,
一旦改變,就會馬上觸發相關函數。
1 /** 2 * 對象屬性被劫持 經過調用Object.defineProperty 給data的每一個屬性添加 getter setter方法, 3 * 當data某個屬性被訪問時,調用getter方法,判斷當 Dep.target 不爲空時調用 dep.denpend 和 childObj.dep.denpend方法 4 * 當改變data的屬性時,調用setter方法,這時調用 dep.notify方法進行通知 5 * 6 */ 7 function defineReactive$$1(obj, key, val, customSetter, shallow) { 8 var dep = new Dep();//依賴管理 9 10 var property = Object.getOwnPropertyDescriptor(obj, key);//返回鍵描述信息 11 if (property && property.configurable === false) { 12 //不能夠修改直接返回 13 return 14 } 15 16 var getter = property && property.get; 17 var setter = property && property.set; 18 19 var childOb = !shallow && observe(val); 20 Object.defineProperty(obj, key, { 21 enumerable: true, 22 configurable: true, 23 get: function reactiveGetter() { 24 var value = getter ? getter.call(obj) : val; 25 if (Dep.target) { 26 dep.depend(); 27 if (childOb) { 28 childOb.dep.depend(); 29 if (Array.isArray(value)) { 30 dependArray(value); 31 } 32 } 33 } 34 return value 35 }, 36 set: function reactiveSetter(newVal) { 37 var value = getter ? getter.call(obj) : val; //獲取當前值,是前一個值 38 if (newVal === value || (newVal !== newVal && value !== value)) { 39 //值沒有發生變化,再也不作任何處理 40 return 41 } 42 /* eslint-enable no-self-compare */ 43 if ("development" !== 'production' && customSetter) { 44 customSetter(); 45 } 46 if (setter) { 47 setter.call(obj, newVal);//調用默認setter方法或將新值賦給當前值 48 } else { 49 val = newVal; 50 } 51 childOb = !shallow && observe(newVal); 52 dep.notify();//賦值後通知依賴變化 53 } 54 }); 55 }
可是我有一點疑惑的是:model值改變,會按照 hasHandler ==> proxySetter ==> reactiveSetter 這樣一種順序,
直至最後 觸發 Dep.notify() 爲何?