關於vm.$set()用法能夠看官網,這裏就不贅述了。html
在Vue.js裏面只有data中已經存在的屬性纔會被Observe爲響應式數據, 若是你是新增的屬性是不會成爲響應式數據, 所以Vue提供了一個api(vm.$set)來解決這個問題。vue
<!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'
}
},
mounted () {
},
methods: {
addUserAgeField () {
// this.user.age = 20 這樣是不起做用, 不會被Observer
this.$set(this.user, 'age', 20) // 應該使用
}
}
})
</script>
</body>
</html>
複製代碼
源碼位置: vue/src/core/instance/index.jsreact
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
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)
}
initMixin(Vue)
// 給原型綁定代理屬性$props, $data
// 給Vue原型綁定三個實例方法: vm.$watch,vm.$set,vm.$delete
stateMixin(Vue)
// 給Vue原型綁定事件相關的實例方法: vm.$on, vm.$once ,vm.$off , vm.$emit
eventsMixin(Vue)
// 給Vue原型綁定生命週期相關的實例方法: vm.$forceUpdate, vm.destroy, 以及私有方法_update
lifecycleMixin(Vue)
// 給Vue原型綁定生命週期相關的實例方法: vm.$nextTick, 以及私有方法_render, 以及一堆工具方法
renderMixin(Vue)
export default Vue
複製代碼
...
Vue.prototype.$set = set
Vue.prototype.$delete = del
...
複製代碼
源碼位置: vue/src/core/observer/index.jsgit
export function set (target: Array<any> | Object, key: any, val: any): any {
// 若是 set 函數的第一個參數是 undefined 或 null 或者是原始類型值,那麼在非生產環境下會打印警告信息
// 這個api原本就是給對象與數組使用的
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 相似$vm.set(vm.$data.arr, 0, 3)
// 修改數組的長度, 避免索引>數組長度致使splcie()執行有誤
target.length = Math.max(target.length, key)
// 利用數組的splice變異方法觸發響應式, 這個前面講過
target.splice(key, 1, val)
return val
}
// target爲對象, key在target或者target.prototype上。
// 同時必須不能在 Object.prototype 上
// 直接修改便可, 有興趣能夠看issue: https://github.com/vuejs/vue/issues/6845
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
// 以上都不成立, 即開始給target建立一個全新的屬性
// 獲取Observer實例
const ob = (target: any).__ob__
// Vue 實例對象擁有 _isVue 屬性, 即不容許給Vue 實例對象添加屬性
// 也不容許Vue.set/$set 函數爲根數據對象(vm.$data)添加屬性
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
// target自己就不是響應式數據, 直接賦值
if (!ob) {
target[key] = val
return val
}
// 進行響應式處理
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
複製代碼
工具函數github
// 判斷給定變量是不是未定義,當變量值爲 null時,也會認爲其是未定義
export function isUndef (v: any): boolean %checks {
return v === undefined || v === null
}
// 判斷給定變量是不是原始類型值
export function isPrimitive (value: any): boolean %checks {
return (
typeof value === 'string' ||
typeof value === 'number' ||
// $flow-disable-line
typeof value === 'symbol' ||
typeof value === 'boolean'
)
}
// 判斷給定變量的值是不是有效的數組索引
export function isValidArrayIndex (val: any): boolean {
const n = parseFloat(String(val))
return n >= 0 && Math.floor(n) === n && isFinite(val)
}
複製代碼
關於(ob && ob.vmCount)。npm
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 省略...
if (asRootData && ob) {
// vue已經被Observer了,而且是根數據對象, vmCount纔會++
ob.vmCount++
}
return ob
}
複製代碼
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
//opts.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)
}
}
複製代碼
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 省略...
// observe data
observe(data, true /* asRootData */)
}
複製代碼