Vue源碼之關(guān)于vm.$delete()/Vue.use()內(nèi)部原理詳解
vm.$delete()
vm.$delete用法見(jiàn)官網(wǎng)。
為什么需要Vue.delete()?
在ES6之前, JS沒(méi)有提供方法來(lái)偵測(cè)到一個(gè)屬性被刪除了, 因此如果我們通過(guò)delete刪除一個(gè)屬性, Vue是偵測(cè)不到的, 因此不會(huì)觸發(fā)數(shù)據(jù)響應(yīng)式。
見(jiàn)下面的demo。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Vue Demo</title> <script src="https://cdn.jsdelivr.net/npm/vue"></script> </head> <body> <div id="app"> 名字: {{ user.name }} 年紀(jì): {{ user.age }} <button @click="addUserAgeField">刪除一個(gè)年紀(jì)字段</button> </div> <script> const app = new Vue({ el: "#app", data: { user: { name: "test", age: 10 } }, mounted() {}, methods: { addUserAgeField() { // delete this.user.age; // 這樣是不起作用, 不會(huì)觸發(fā)數(shù)據(jù)響應(yīng)式更新 this.$delete(this.user, 'age') // 應(yīng)該使用 } } }); </script> </body> </html>
源碼分析內(nèi)部實(shí)現(xiàn)
源碼位置vue/src/core/instance/state.js的stateMixin方法
export function stateMixin (Vue: Class<Component>) { ... Vue.prototype.$set = set Vue.prototype.$delete = del ... }
然后查看del函數(shù)位置, vue/src/core/observer/index.js。
/** * Delete a property and trigger change if necessary. * target: 將被刪除屬性的目標(biāo)對(duì)象, 可以是對(duì)象/數(shù)組 * key: 刪除屬性 */ export function del (target: Array<any> | Object, key: any) { // 非生產(chǎn)環(huán)境下, 不允許刪除一個(gè)原始數(shù)據(jù)類(lèi)型, 或者undefined, null if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`) } // 如果target是數(shù)組, 并且key是一個(gè)合法索引,通過(guò)數(shù)組的splcie方法刪除值, 并且還能觸發(fā)數(shù)據(jù)的響應(yīng)(數(shù)組攔截器截取到變化到元素, 通知依賴更新數(shù)據(jù)) if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1) return } // 獲取ob const ob = (target: any).__ob__ // target._isVue: 不允許刪除Vue實(shí)例對(duì)象上的屬性 // (ob && ob.vmCount): 不允許刪除根數(shù)據(jù)對(duì)象的屬性,觸發(fā)不了響應(yīng) if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' ) return } // 如果屬性壓根不在對(duì)象上, 什么都不做處理 if (!hasOwn(target, key)) { return } // 走到這一步說(shuō)明, target是對(duì)象, 并且key在target上, 直接使用delete刪除 delete target[key] // 如果ob不存在, 說(shuō)明target本身不是響應(yīng)式數(shù)據(jù), if (!ob) { return } // 存在ob, 通過(guò)ob里面存儲(chǔ)的Dep實(shí)例的notify方法通知依賴更新 ob.dep.notify() }
工具函數(shù)
// 判斷是否v是未定義 export function isUndef (v: any): boolean %checks { return v === undefined || v === null } // 判斷v是否是原始數(shù)據(jù)類(lèi)型(基本數(shù)據(jù)類(lèi)型) export function isPrimitive (value: any): boolean %checks { return ( typeof value === 'string' || typeof value === 'number' || // $flow-disable-line typeof value === 'symbol' || typeof value === 'boolean' ) } // 判斷對(duì)象上是否有屬性 const hasOwnProperty = Object.prototype.hasOwnProperty export function hasOwn (obj: Object | Array<*>, key: string): boolean { return hasOwnProperty.call(obj, key) }
關(guān)于__ob__屬性, 在很多源碼地方我們都會(huì)看到類(lèi)似這樣獲取ob(Observer實(shí)例)
const ob = (target: any).__ob__
牢記只要數(shù)據(jù)被observe過(guò)就會(huì)打上這個(gè)私有屬性, 是在Observer類(lèi)的構(gòu)造器里面發(fā)生的
export class Observer { constructor (value: any) { this.value = value // 依賴是存在Observe上的dep屬性, 再次通知依賴更新時(shí)候我們一般使用__ob__.dep.notify() this.dep = new Dep() this.vmCount = 0 // 定義__ob__ def(value, '__ob__', this) if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else { this.walk(value) } } ... }
Vue.use()
大家都知道這個(gè)方法是用來(lái)安裝插件的, 是全局api。
具體使用見(jiàn)官網(wǎng)。
通過(guò)Vue.use()源碼+Vuex部分源碼分析插件的安裝過(guò)程
Vue.use()什么時(shí)候被綁在Vue原型上
源碼位置: vue/src/core/index.js
Vue
initGlobalAPI()
源碼位置: vue/src/core/global-api/index.js
export function initGlobalAPI (Vue: GlobalAPI) { ... // 初始化use() initUse(Vue) ... }
initUse()
源碼位置: vue/src/core/global-api/use.js
export function initUse (Vue: GlobalAPI) { // 這里的Vue是構(gòu)造器函數(shù). // 通過(guò)以下源碼: // vue-dev/src/core/global-api/index.js initGlobalAPI()中 // vue-dev/src/core/index.js 這里執(zhí)行了initGlobalAPI() => 初始化一些全局api // Vue.use(): 安裝Vue.js的插件 // 如果插件是一個(gè)對(duì)象,必須提供 install 方法 // 如果插件是一個(gè)函數(shù),它會(huì)被作為 install 方法 // install 方法調(diào)用時(shí),會(huì)將 Vue 作為參數(shù)傳入 Vue.use = function (plugin: Function | Object) { // installedPlugins存儲(chǔ)install后的插件 const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) if (installedPlugins.indexOf(plugin) > -1) { // 同一個(gè)插件只會(huì)安裝一次 return this } // additional parameters // 除了插件外的其他參數(shù) Vue.use(MyPlugin, { someOption: true }) const args = toArray(arguments, 1) // 往args存儲(chǔ)Vue構(gòu)造器, 供插件的install方法使用 args.unshift(this) // 分情況執(zhí)行插件的install方法, 把this(Vue), 參數(shù)拋回給install方法 // 所以我們常說(shuō), install這個(gè)方法的第一個(gè)參數(shù)是 Vue 構(gòu)造器,第二個(gè)參數(shù)是一個(gè)可選的選項(xiàng)對(duì)象: if (typeof plugin.install === 'function') { // plugin是一個(gè)對(duì)象 plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { // plugin是一個(gè)函數(shù) plugin.apply(null, args) } // install之后會(huì)存儲(chǔ)該插件避免重復(fù)安裝 installedPlugins.push(plugin) return this } }
Vuex源碼
我們都知道開(kāi)發(fā)一個(gè)Vue.js 的插件應(yīng)該暴露一個(gè) install 方法。這個(gè)方法的第一個(gè)參數(shù)是 Vue 構(gòu)造器,第二個(gè)參數(shù)是一個(gè)可選的選項(xiàng)對(duì)象:
那么我們首先就是看Vuex的install方法是怎么實(shí)現(xiàn)的
源碼位置: vuex-dev/src/store.js
let Vue // bind on install // install: 裝載vuex到vue, Vue.use(Vuex)也是執(zhí)行install方法 // 關(guān)于Vue.use()源碼. vue-dev/src/core/global-api/use.js export function install (_Vue) { if (Vue && _Vue === Vue) { if (process.env.NODE_ENV !== 'production') { console.error( '[vuex] already installed. Vue.use(Vuex) should be called only once.' ) } return } // 首次安裝插件, 會(huì)把局部的Vue緩存到全局的window.Vue. 主要為了避免重復(fù)調(diào)用Vue.use() Vue = _Vue applyMixin(Vue) }
applyMixin()
源碼位置: vuex/src/mixin.js
export default function (Vue) { const version = Number(Vue.version.split('.')[0]) if (version >= 2) { // 如果是2.x.x以上版本,注入一個(gè)全局mixin, 執(zhí)行vueInit方法 Vue.mixin({ beforeCreate: vuexInit }) } else { // override init and inject vuex init procedure // for 1.x backwards compatibility. // 重寫(xiě)Vue原型上的_init方法, 注入vueinit方法 _init方法見(jiàn) vue-dev/src/core/instance/init.js const _init = Vue.prototype._init // 作為緩存變量 Vue.prototype._init = function (options = {}) { options.init = options.init ? [vuexInit].concat(options.init) : vuexInit // 重新執(zhí)行_init _init.call(this, options) } } /** * Vuex init hook, injected into each instances init hooks list. */ // 注入store到Vue構(gòu)造器 function vuexInit () { // 這里的this. 指的是Vue構(gòu)造器 /** * new Vue({ * ..., * store, * route * }) */ // options: 就是new Vue(options) // 源碼見(jiàn) vue-dev/src/core/instance/init.js initMixin方法 const options = this.$options // store injection // store是我們使用new Vuex.Store(options)的實(shí)例 // 注入store到Vue構(gòu)造函數(shù)上的$store屬性上, 所以我們?cè)赩ue組件里面使用this.$store來(lái)使用 if (options.store) { // options.store為真說(shuō)明是根節(jié)點(diǎn)root this.$store = typeof options.store === 'function' ? options.store() : options.store } else if (options.parent && options.parent.$store) { // 子組件直接從父組件中獲取$store,這樣就保證了所有組件都公用了全局的同一份store this.$store = options.parent.$store } } }
至于install方法Vuex是如果執(zhí)行的?
export class Store { constructor (options = {}) { // 瀏覽器環(huán)境下安裝vuex if (!Vue && typeof window !== 'undefined' && window.Vue) { install(window.Vue) } ... } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue 項(xiàng)目打包通過(guò)命令修改 vue-router 模式 修改 API 接口前綴
這篇文章主要介紹了vue 項(xiàng)目打包通過(guò)命令修改 vue-router 模式 修改 API 接口前綴的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2018-06-06Vue中filter使用及根據(jù)id刪除數(shù)組元素方式
這篇文章主要介紹了Vue中filter使用及根據(jù)id刪除數(shù)組元素方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03解決vue初始化項(xiàng)目時(shí),一直卡在Project description上的問(wèn)題
今天小編就為大家分享一篇解決vue初始化項(xiàng)目時(shí),一直卡在Project description上的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-10-10Vue2使用cube-ui?實(shí)現(xiàn)搜索過(guò)濾、高亮功能
cube-ui?是基于?Vue.js?實(shí)現(xiàn)的精致移動(dòng)端組件庫(kù),由于很長(zhǎng)一段時(shí)間沒(méi)有學(xué)習(xí)cube-ui?的功能實(shí)現(xiàn)示例代碼了,今天通過(guò)本文給大家介紹下Vue2使用cube-ui?實(shí)現(xiàn)搜索過(guò)濾、高亮功能,感興趣的朋友跟隨小編一起看看吧2023-01-01代號(hào)為Naruto的Vue?2.7正式發(fā)布功能詳解
這篇文章主要為大家介紹了代號(hào)為Naruto的Vue?2.7正式發(fā)布功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07Vue組合式API--setup中定義響應(yīng)式數(shù)據(jù)的示例詳解
在Vue2.x中,編寫(xiě)組件的方式是使用Options API,它的特點(diǎn)是在對(duì)應(yīng)的屬性中編寫(xiě)對(duì)應(yīng)的功能模塊,這篇文章主要介紹了Vue組合式API--setup中定義響應(yīng)式數(shù)據(jù)詳解,需要的朋友可以參考下2022-10-10Vue結(jié)合Openlayers使用Overlay添加Popup彈窗實(shí)現(xiàn)
本文主要介紹了Vue結(jié)合Openlayers使用Overlay添加Popup彈窗實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05vue項(xiàng)目中如何調(diào)用多個(gè)不同的ip接口
這篇文章主要介紹了vue項(xiàng)目中如何調(diào)用多個(gè)不同的ip接口,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08詳解基于Vue的支持?jǐn)?shù)據(jù)雙向綁定的select組件
這篇文章主要介紹了詳解基于Vue的支持?jǐn)?shù)據(jù)雙向綁定的select組件,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09