摘要:前言為什么要做源碼解讀我們新到一個環境,第一件事情就是熟悉環境熟悉項目,這個很考驗閱讀源碼的能力以及耐心。構造函數拉到最下面,導出的是一個構造函數。
前言
A: 為什么要做源碼解讀?
Q: 我們新到一個環境,第一件事情就是熟悉環境熟悉項目,這個很考驗閱讀源碼的能力以及耐心。vue是個很好的庫,知名度高,對js的學習具有向上性,所以搞清楚邏輯是有好處的。
A: 閱讀源碼的程度?
Q: 我們完全沒必要從頭到尾細細品味,只需要知道一些核心的實現就好了,畢竟vue也算是個產品,我們沒必要搞清楚一個產品,我們只需要知道產品的核心就夠了,多余的也是業務代碼。(相對來說)
new Vue({ // 初始化vue實例 //components: { App } // vue1.0的寫法 render: h => h(App) // 最先執行,返回一個符合component的對象,vue2.0的寫法 }) .$mount("#app") // 掛載vue實例到 ‘#app’render函數
render: h => h(App) 就是 render:function(h){ return h(App) } 即 render: function (createElement) { return createElement(App) }找到Vue引用源文件(debug順序)
import Vue from "vue"
找到
node-modules/vue
打開package.json 找到
"main": "dist/vue.runtime.common.js"
"main"是 npm模塊曝光的主要文件.
打開vue.runtime.common.js
ctrl/command + a, ctrl/command + k, ctrl/command + 1
,快捷鍵把所有方法折疊
你會發現 update執行了2次,我明明只初始化了一次vue實例,為什么update2次了呢?原因在下方表明。
構造函數拉到最下面,
module.exports = Vue;
導出的是一個Vue構造函數。
當前文件搜索 Vue 找到 構造函數
function Vue (options) { if (process.env.NODE_ENV !== "production" && !(this instanceof Vue) ) { warn("Vue is a constructor and should be called with the `new` keyword"); } this._init(options); // 調用 初始化方法 }初始化函數
var uid$3 = 0; vm._uid = uid$3++; //每個vue實例 擁有唯一id,從0開始 ++ // 合并初始化vue實例的參數(入參options和默認參數) vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ); initLifecycle();// 初始化實例生命周期相關的參數 使用Object.create(null)用來獲取一個沒有原型鏈的對象類型 initEvents(); // 初始化實例事件觸發相關的參數 initRender(); // 初始化實例渲染相關的參數 //create的準備工作做好了,觸發beforeCreate的生命周期 callHook(vm, "beforeCreate"); initState(); // 初始化實例狀態 // 狀態也初始化好了,觸發create的生命周期,所以 create和 breforCreate的區別就在 create的時候 有狀態。 callHook(vm, "created");
至此,vue實例的初始化完成,然后掛載到節點
掛載實例到節點// 將mount("#app") => query("#app") 查找到dom對象,賦值給vue.$el // 觸發beforeMount的生命周期,所以beforeMount 和 create的區別就在 beforeMount的時候 有掛載節點。 callHook(vm, "beforeMount"); // 拿到當前將要掛載的Vnode(虛擬dom對象) vm._render() vm.$vnode // (更新)渲染頁面 vm._update(vm._render(), hydrating); vm.__patch__(); createElm(); // 按照虛擬dom生成真實domcreateElm函數
// 這是一個遞歸方法,vue實例的初始化是創建一個根節點,然后再將render函數傳入的組件掛載,這就是流程圖執行2次update的原因。 // 如果是組件,則去做 組件的初始化 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } // 已經刪減,只留主要邏輯,判斷虛擬dom 的 tag屬性 if (isDef(tag)) { { // 遍歷虛擬dom的子節點,并且創建,然后遞歸當前方法。 createChildren(vnode, children, insertedVnodeQueue); if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue); } insert(parentElm, vnode.elm, refElm); } } // 如果沒有子節點的,直接創建dom,然后插入 else if (isTrue(vnode.isComment)) { vnode.elm = nodeOps.createComment(vnode.text); insert(parentElm, vnode.elm, refElm); } else { vnode.elm = nodeOps.createTextNode(vnode.text); insert(parentElm, vnode.elm, refElm); } // 觸發Mount的生命周期 callHook(vm, "mounted");
至此就是 Vue根節點初始化掛載和渲染的流程.
Vue數據更新首先 我們改造下 app.vue,像官網一樣,我們新增一個雙向綁定的文本框。
現在我們知道了,第一次的update只是掛載了根節點,那么我們新增了文本框的組件其實是在第二次init的時候初始化的。
我們可以著重看第二次的流程,搞清楚,數據的監聽與更新。
在每次vue實例初始化的時候 都會執行initState,這里面做了vue實例 數據的監聽。
function initState (vm) { vm._watchers = []; var opts = vm.$options; if (opts.props) { initProps(vm, opts.props); } if (opts.methods) { initMethods(vm, opts.methods); } if (opts.data) { // 這里判斷vm.$options.data,從而執行initData()或者 直接 監聽 vm._data={}作為根狀態 initData(vm); } else { observe(vm._data = {}, true /* asRootData */); } if (opts.computed) { initComputed(vm, opts.computed); } if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); } }
vm.$options.data 哪里來的呢?
是在 Vue._init方法中
if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options); } else { vm.$options = mergeOptions( // 這個方法中 給data賦值,也就是我們render中 的 data(){return{//我們組件的數據}} resolveConstructorOptions(vm.constructor), options || {}, vm ); }initData函數
function initData (vm) { var data = vm.$options.data; // 把從render函數返回的data函數對象賦值給data,然后data.call(this,this),也就是vm.data();拿到data返回值 data = vm._data = typeof data === "function" ? getData(data, vm) : data || {}; // observe data observe(data, true /* asRootData */); } function getData (data, vm) { pushTarget(); try { return data.call(vm, vm) } catch (e) { handleError(e, vm, "data()"); return {} } finally { popTarget(); } }
function observe (value, asRootData) { var ob; if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) { ob = value.__ob__; } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value); // 返回一個 新建的 Observer } return ob }Observer對象(監聽者)
var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); // 這里的dep 稍后會說 this.vmCount = 0; def(value, "__ob__", this); if (Array.isArray(value)) { var augment = hasProto ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys); this.observeArray(value); } else { this.walk(value); } }; // 如果是對象,則按照key來劫持 Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } }; // 如果是數組,就遍歷每個數組元素,再每個元素再判斷是否為數組,對象 Observer.prototype.observeArray = function observeArray (items) { for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); } };數據劫持
function defineReactive ( obj, key, val, customSetter, shallow ) { var dep = new Dep(); // 這里的dep 稍后會說 var property = Object.getOwnPropertyDescriptor(obj, key); if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get; if (!getter && arguments.length === 2) { val = obj[key]; } var setter = property && property.set; var childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); // 這里的dep 稍后會說 if (childOb) { childOb.dep.depend(); if (Array.isArray(value)) { dependArray(value); } } } return value }, set: function reactiveSetter (newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== "production" && customSetter) { customSetter(); } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify(); // 這里的dep 稍后會說 } }); }依賴收集
我們在使用vue的時候,data方法會返回對象,包含了所有我們想要觀察的數據屬性,同樣 vue也會幫我們監聽這些屬性的變化,但是,假如我們在data中設置了多個屬性,但是在模板中只使用了1個,又會如何呢?我們在腳本中設置value2的值(this.value3= "hello world"),那么vue監聽到變化 還會去通知模板重新渲染么?
new Vue({ template: `value1: {{value1}}`, data: { value1: "value1", value2: "value2", value3: "value3", ... } });自然是不會的,vue很聰明的使用了依賴收集
Dep(訂閱者容器) Watcher對象(訂閱者) 小結
Dep : 一個訂閱者的容器,可以增加或刪除訂閱者,可以向訂閱者發送消息;
Watcher : 訂閱者類。它在初始化時可以接受getter, callback兩個函數作為參數。getter用來計算Watcher對象的值。當Watcher被觸發時,會重新通過getter計算當前Watcher的值,如果值改變,則會執行callback.未完待續。。。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98862.html
相關文章
入口文件開始,分析Vue源碼實現
摘要:一方面是因為想要克服自己的惰性,另一方面也是想重新溫故一遍。一共分成了個基礎部分,后續還會繼續記錄。文章中如果有筆誤或者不正確的解釋,也歡迎批評指正,共同進步。最后地址部分源碼 Why? 網上現有的Vue源碼解析文章一搜一大批,但是為什么我還要去做這樣的事情呢?因為覺得紙上得來終覺淺,絕知此事要躬行。 然后平時的項目也主要是Vue,在使用Vue的過程中,也對其一些約定產生了一些疑問,可...
入口文件開始,分析Vue源碼實現
摘要:一方面是因為想要克服自己的惰性,另一方面也是想重新溫故一遍。一共分成了個基礎部分,后續還會繼續記錄。文章中如果有筆誤或者不正確的解釋,也歡迎批評指正,共同進步。最后地址部分源碼 Why? 網上現有的Vue源碼解析文章一搜一大批,但是為什么我還要去做這樣的事情呢?因為覺得紙上得來終覺淺,絕知此事要躬行。 然后平時的項目也主要是Vue,在使用Vue的過程中,也對其一些約定產生了一些疑問,可...
vue源碼解讀-目錄結構
摘要:目錄結構構建相關的文件,一般情況下我們不需要動鉤子別名配置 目錄結構 ├── scripts ------------------------------- 構建相關的文件,一般情況下我們不需要動│ ├── git-hooks ------------------------- git鉤子│ ├── alias.js -------------------------- 別名配...
【Vue 2.0】核心源碼解讀 -- 不定期更新
摘要:觀察員由模板解析指令創建的觀察員負責模板中的更新視圖操作。觀察員種類目前了解情況來看主要分三類視圖指令的計算屬性的用戶自定義的 介紹 關于 Vue.js 的原理一直以來都是一個話題。經過幾天的源碼學習和資料介紹,我將一些個人理解的經驗給寫下來,希望能夠與大家共勉。 附上GITHUB源碼地址, 如果有任何不解 可以在 文章下面提出或者寫下issue, 方便大家回答和學習, 有興趣可以St...
前方來報,八月最新資訊--關于vue2&3的最佳文章推薦
摘要:哪吒別人的看法都是狗屁,你是誰只有你自己說了才算,這是爹教我的道理。哪吒去他個鳥命我命由我,不由天是魔是仙,我自己決定哪吒白白搭上一條人命,你傻不傻敖丙不傻誰和你做朋友太乙真人人是否能夠改變命運,我不曉得。我只曉得,不認命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出處 查看github最新的Vue...
發表評論
0條評論
閱讀 1355·2019-08-30 15:44
閱讀 2099·2019-08-30 11:04
閱讀 518·2019-08-29 15:17
閱讀 2540·2019-08-26 12:12
閱讀 3133·2019-08-23 18:09
閱讀 922·2019-08-23 15:37
閱讀 1522·2019-08-23 14:43
閱讀 2920·2019-08-23 13:13