此篇文章主要是從應用及源碼層面講解vue
部分常用api,閱讀起來可能略有難度,新手可以看《從文檔開始,重學vue(上)》
示例代碼均在vue-cli3中完成
Vue.extend()
可以使用 extend
創建一個子類
,該方法通常用于構建全局組件,如彈框組件等,下面我們就用它來制作個全局alert
組件吧
- 首先我們需要一個
alert.vue
組件,組件很簡單就接受一個參數,然后有兩個控制顯示隱藏的方法 - 需要把
alert
掛載到body
注意extend
的使用方式 - 使用
使用之前別忘了在main.js
中use
一下
import Alert from "./components/Alert/create";Vue.use(Alert)
用起來也非常方便,如下:
mounted(){ this.$alert('公眾號,碼不停息')}
上面我們使用extend
直接給他傳了個組件進去,其實我們也可以給extend
的配置對象,如下:
Vue.extend({ template: "{{msg}}", data() { return { msg: "碼不停息" }; }});
下面我們通過源碼來看看在vue
內部extend
都做了哪些事情,關鍵性代碼已經加上注釋主要做的事情就是把通過
extend
掛載的組件初始化,并完善里面的options
最后返回組件
Vue.nextTick()
如果想理解清楚nextTick
,需要我們了解vue
的異步隊列
及 javascript
(確切的說是瀏覽器)的事件循環機制
- vue異步更新隊列
- 事件循環機制
可能你還沒有注意到,Vue 在更新 DOM 時是異步執行的。只要偵聽到數據變化,Vue 將開啟一個隊列,并緩沖在同一事件循環中發生的所有數據變更。如果同一個 watcher 被多次觸發,只會被推入到隊列中一次。這種在緩沖時去除重復數據對于避免不必要的計算和 DOM 操作是非常重要的。然后,在下一個的事件循環“tick”中,Vue 刷新隊列并執行實際 (已去重的) 工作。Vue 在內部對異步隊列嘗試使用原生的 Promise.then、MutationObserver 和 setImmediate,如果執行環境不支持,則會采用 setTimeout(fn, 0) 代替(Vue官網)
可以簡單的總結為Vue實現響應式并不是數據發生變化之后 DOM 立即變化,而是按一定的策略進行 DOM 的更新,他的策略就是同一事件循環中的所有數據變化完成之后,再統一進行視圖更新,如果在這個過程中想要操作dom就比較棘手了,而Vue.nextTick
就是來解決這樣的問題,如下:
{{ time }}
export default { data() { return { time: "" }; }, methods: { getDom() { return document.getElementById("time").innerHTML; } }, mounted() { this.time = new Date().toLocaleTimeString(); console.log("獲取time", this.getDom()); //獲取不到 this.$nextTick(() => { console.log("獲取time", this.getDom()); //可以獲取到 }); }};

可以看到,當我們給time
賦值后直接通過原生dom
獲取值是獲取不到的,而用this.$nextTick(callback)
就可以獲取到了,那nextTick
在內部做了什么呢?我們找到相應的源碼可以看出,當我們在代碼中執行
$nextTick
方法時,內部是調用了nextTick
,那我們再來看看nextTick
方法里面都有什么?原來
nextTick
方法把我們傳的函數都push到了一個callback
數組里,那這個數組是什么時候執行呢?為了方便看,我把相關代碼都復制出來如下:代碼較長,可以放大觀看,關鍵代碼已給出注釋,主要邏輯如下:
用戶調用$nextTick(callback) -> 把用戶傳入函數push到callback數組 -> 檢測當前平臺環境決定使用哪種方式處理異步 -> 執行flushCallbacks函數 -> flushCallbacks中循環執行callback
因為微任務會在當前宏任務執行完畢后立即執行,這樣就能保證在執行$nextTick()
的時候,當前宏任務(包括頁面渲染)已經執行完畢
Vue.set()
用Vue.set()
設置的值是響應式,當我們需要對 復雜數據類型 新增屬性和值,同時需要新增的值是 響應式 的時候就需要使用該api 如下所示:
- 直接給對象賦一個新的屬性和新值
公眾號: {{ userInfo.name }}
作者:{{ userInfo.author || "暫無數據" }}
export default { data() { return { userInfo: { name: "碼不停息" } }; }, methods: {}, mounted() { this.userInfo.author = "劉小灰"; // 注意 userInfo開始沒有author屬性 }};
渲染結果如下, 數據沒有出來,新增的author不是響應式
- 我們再用
Vue.set()
給對象賦一個新的屬性和新值
公眾號: {{ userInfo.name }}
作者:{{ userInfo.author || "暫無數據" }}
export default { data() { return { userInfo: { name: "碼不停息" } }; }, methods: {}, mounted() { this.$set(this.userInfo, "author", "劉小灰"); //使用set賦值 }};
再看看結果,新增的author是響應式
除了基本使用,我們來思考下Vue
為什么要設置這個api,為什么直接通過.
語法添加的屬性就不是響應式的呢? 使用this.$set()
是有如何做到響應式的呢?
我們來源碼中找答案
簡單了解下響應式
響應式的具體表現是當我們把data中的屬性和頁面綁定后,改變data中的數據后,頁面會自動更新,那Vue
是如何實現響應式的呢?
不難得出在vue2.x
中是使用Object.defineProperty
對數據進行劫持來實現響應式,當我們初始化的時候,會把每一個數據都進行依賴收集,內部主要是通過遍歷及遞歸來實現,如下(關鍵代碼已給出注釋):下面我們來看看核心方法
defineReactive
都干了什么事情(關鍵代碼已給出注釋)
大體流程
初始化 -> 執行
Observer
-> 如果是數組特殊處理,否則遍歷子 -> 執行walk
->defineReactive
進行響應式處理,如果數據中對象嵌套,遞歸之,否則進行依賴收集,確保全部數據都經過Object.defineProperty
的洗禮 -> 響應式處理完畢
這時問題來了, 如果你在代碼中給某個對象通過.
語法新加個屬性,這個時候初始化過程早已結束,新加的屬性并沒有經過Object.defineProperty
的洗禮,自然不會變成響應式數據,這個時候我們就需要使用Vue.set()
方法,讓數據變成響應式,那set
中是如何做的呢?其實就是重新調了下Object.defineProperty
的set
方法進行依賴收集即可
關于Vue
是如何對數組進行特殊處理的,可以看Object.defineProperty是如何實現對數組的監聽
Vue.use()
安裝 Vue
插件使用,use的源碼比較短我們直接看源碼:如下(關鍵代碼已給出注釋)所以在我們平常使用時,我們有兩種使用方式
方式一:Vue.use({ install(vue){ }})方式二:Vue.use((vue)=>{})
無論是哪種方式,Vue
都會把vue實例
在回調中返回回來供我們使用
最后
最后我想說說為什么我們要學習源碼,我覺得源碼能不能學透并不重要(當然,如果你可以把源碼徹底看懂也再好不過),對我們大部分人來說,學習源碼最重要的目的是 查漏補缺 ,看大佬們是怎么寫代碼,是怎么組織代碼,而這種能力不是我們多做幾個項目,多發幾個ajax請求能夠得到的,就拿自己來說,看了源碼后我發現自己對函數式編程,對發布訂閱模式掌握的還不是不好,然后自己花些時間再加強下這方面的理解,然后把自己理解到的知識再在看源碼中得以升華,我感覺這是最酷的!
最后的最后
交個朋友吧,關注微信公眾號,拉你進群,和一群志同道合的人學習源碼