vm.$delete用法見官網。html
在ES6以前, JS沒有提供方法來偵測到一個屬性被刪除了, 所以若是咱們經過delete刪除一個屬性, Vue是偵測不到的, 所以不會觸發數據響應式。vue
見下面的demo。react
<!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 }} 年紀: {{ user.age }}
<button @click="addUserAgeField">刪除一個年紀字段</button>
</div>
<script>
const app = new Vue({
el: "#app",
data: {
user: {
name: "test",
age: 10
}
},
mounted() {},
methods: {
addUserAgeField() {
// delete this.user.age; // 這樣是不起做用, 不會觸發數據響應式更新
this.$delete(this.user, 'age') // 應該使用
}
}
});
</script>
</body>
</html>
複製代碼
源碼位置vue/src/core/instance/state.js的stateMixin方法vuex
export function stateMixin (Vue: Class<Component>) {
...
Vue.prototype.$set = set
Vue.prototype.$delete = del
...
}
複製代碼
而後查看del函數位置, vue/src/core/observer/index.js。npm
/**
* Delete a property and trigger change if necessary.
* target: 將被刪除屬性的目標對象, 能夠是對象/數組
* key: 刪除屬性
*/
export function del (target: Array<any> | Object, key: any) {
// 非生產環境下, 不容許刪除一個原始數據類型, 或者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是數組, 而且key是一個合法索引,經過數組的splcie方法刪除值, 而且還能觸發數據的響應(數組攔截器截取到變化到元素, 通知依賴更新數據)
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
// 獲取ob
const ob = (target: any).__ob__
// target._isVue: 不容許刪除Vue實例對象上的屬性
// (ob && ob.vmCount): 不容許刪除根數據對象的屬性,觸發不了響應
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
}
// 若是屬性壓根不在對象上, 什麼都不作處理
if (!hasOwn(target, key)) {
return
}
// 走到這一步說明, target是對象, 而且key在target上, 直接使用delete刪除
delete target[key]
// 若是ob不存在, 說明target自己不是響應式數據,
if (!ob) {
return
}
// 存在ob, 經過ob裏面存儲的Dep實例的notify方法通知依賴更新
ob.dep.notify()
}
複製代碼
工具函數api
// 判斷是否v是未定義
export function isUndef (v: any): boolean %checks {
return v === undefined || v === null
}
// 判斷v是不是原始數據類型(基本數據類型)
export function isPrimitive (value: any): boolean %checks {
return (
typeof value === 'string' ||
typeof value === 'number' ||
// $flow-disable-line
typeof value === 'symbol' ||
typeof value === 'boolean'
)
}
// 判斷對象上是否有屬性
const hasOwnProperty = Object.prototype.hasOwnProperty
export function hasOwn (obj: Object | Array<*>, key: string): boolean {
return hasOwnProperty.call(obj, key)
}
複製代碼
關於__ob__屬性, 在不少源碼地方咱們都會看到相似這樣獲取ob(Observer實例)數組
const ob = (target: any).__ob__
複製代碼
牢記只要數據被observe過就會打上這個私有屬性, 是在Observer類的構造器裏面發生的瀏覽器
export class Observer {
constructor (value: any) {
this.value = value
// 依賴是存在Observe上的dep屬性, 再次通知依賴更新時候咱們通常使用__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)
}
}
...
}
複製代碼
你們都知道這個方法是用來安裝插件的, 是全局api。 具體使用見官網。緩存
源碼位置: vue/src/core/index.jsbash
源碼位置: vue/src/core/global-api/index.js
export function initGlobalAPI (Vue: GlobalAPI) {
...
// 初始化use()
initUse(Vue)
...
}
複製代碼
源碼位置: vue/src/core/global-api/use.js
export function initUse (Vue: GlobalAPI) {
// 這裏的Vue是構造器函數.
// 經過如下源碼:
// vue-dev/src/core/global-api/index.js initGlobalAPI()中
// vue-dev/src/core/index.js 這裏執行了initGlobalAPI() => 初始化一些全局api
// Vue.use(): 安裝Vue.js的插件
// 若是插件是一個對象,必須提供 install 方法
// 若是插件是一個函數,它會被做爲 install 方法
// install 方法調用時,會將 Vue 做爲參數傳入
Vue.use = function (plugin: Function | Object) {
// installedPlugins存儲install後的插件
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
// 同一個插件只會安裝一次
return this
}
// additional parameters
// 除了插件外的其餘參數 Vue.use(MyPlugin, { someOption: true })
const args = toArray(arguments, 1)
// 往args存儲Vue構造器, 供插件的install方法使用
args.unshift(this)
// 分狀況執行插件的install方法, 把this(Vue), 參數拋回給install方法
// 因此咱們常說, install這個方法的第一個參數是 Vue 構造器,第二個參數是一個可選的選項對象:
if (typeof plugin.install === 'function') {
// plugin是一個對象
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
// plugin是一個函數
plugin.apply(null, args)
}
// install以後會存儲該插件避免重複安裝
installedPlugins.push(plugin)
return this
}
}
複製代碼
咱們都知道開發一個Vue.js 的插件應該暴露一個 install 方法。這個方法的第一個參數是 Vue 構造器,第二個參數是一個可選的選項對象:
那麼咱們首先就是看Vuex的install方法是怎麼實現的
源碼位置: vuex-dev/src/store.js
let Vue // bind on install
// install: 裝載vuex到vue, Vue.use(Vuex)也是執行install方法
// 關於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
}
// 首次安裝插件, 會把局部的Vue緩存到全局的window.Vue. 主要爲了不重複調用Vue.use()
Vue = _Vue
applyMixin(Vue)
}
複製代碼
源碼位置: vuex/src/mixin.js
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
// 若是是2.x.x以上版本,注入一個全局mixin, 執行vueInit方法
Vue.mixin({ beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
// 重寫Vue原型上的_init方法, 注入vueinit方法 _init方法見 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
// 從新執行_init
_init.call(this, options)
}
}
/**
* Vuex init hook, injected into each instances init hooks list.
*/
// 注入store到Vue構造器
function vuexInit () {
// 這裏的this. 指的是Vue構造器
/**
* new Vue({
* ...,
* store,
* route
* })
*/
// options: 就是new Vue(options)
// 源碼見 vue-dev/src/core/instance/init.js initMixin方法
const options = this.$options
// store injection
// store是咱們使用new Vuex.Store(options)的實例
// 注入store到Vue構造函數上的$store屬性上, 因此咱們在Vue組件裏面使用this.$store來使用
if (options.store) {
// options.store爲真說明是根節點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是若是執行的?
export class Store {
constructor (options = {}) {
// 瀏覽器環境下安裝vuex
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
...
}
}
複製代碼