When instance objects are created on Vue, data, props, computed, and watch are set to reactive objects
These processes occur in initState (vm), it is defined in SRC/core/instance/state. Js
export function initState (vm: Component) {
vm._watchers = []
/ / get the options
const opts = vm.$options
/ / processing props
if (opts.props) initProps(vm, opts.props)
/ / processing methods
if (opts.methods) initMethods(vm, opts.methods)
/ / the data processing
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)}/ / processing computed
if (opts.computed) initComputed(vm, opts.computed)
/ / processing watch
if(opts.watch && opts.watch ! == nativeWatch) { initWatch(vm, opts.watch) } }Copy the code
The initState function processes props, methods, data, computed, and watch in sequence
Let’s talk about how DATA responds, and write a separate article about the others
Data response principle
During the execution of initState, if opts.data exists, initData is called to initialize the data and turn it into a responsive object
initData
InitData functions defined in SRC/core/instance/state. Js
function initData (vm: Component) {
let data = vm.$options.data
// if data is a function, execute the function first, get the return value and assign it to vm._data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
// Ensure that there are no properties of the same name in methods, props, and data
while (i--) {
const key = keys[i]
if(process.env.NODE_ENV ! = ='production') {
// If there is a key error in the methods
if (methods && hasOwn(methods, key)) {
warn(`Method "${key}" has already been defined as a data property.`, vm)
}
}
// If the props contains a key error
if(props && hasOwn(props, key)) { process.env.NODE_ENV ! = ='production' && warn(/ *... * /)}else if(! isReserved(key)) {// enable developers to access properties in data directly through this.xxx
proxy(vm, `_data`, key)
}
}
// Add a response to data
observe(data, true /* asRootData */)}Copy the code
InitData does three things in total:
- The first thing we do is verify
methods
,props
,data
There is no property of the same name in - through
proxy
将data
In thekey
The agent tovm
Up, so you can get throughthis.xxx
Access modedata
Attribute in; - through
observe
The observer () function creates an observer instance and givesdata
All properties add a response
proxy
const sharedPropertyDefinition = {
enumerable: true.configurable: true.get: noop,
set: noop
}
// Set the proxy to proxy key to target
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
Copy the code
observe
Defined in the/SRC/core/observer/index in js
export function observe (value: any, asRootData: ? boolean) :Observer | void {
// Non-objects and VNode instances do not respond
if(! isObject(value) || valueinstanceof VNode) {
return
}
let ob: Observer | void
// If the value object has an __ob__ attribute, the response has been added, and the __ob__ attribute is returned
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 ) {// Create an observer instance
ob = new Observer(value)
}
// asRootData is true if the object that is currently adding the response is data
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
Copy the code
The observe function takes two arguments
value
The object to which to add the responseasRootData
If the object to which the response is added isdata
.asRootData
是true
If shouldObserve is true and value is a non-VNode object, then create an Observer instance from the Observer, and return the existing Observer instance if the object has already been observed
Observer
Defined in the/SRC/core/observer/index in js
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
// Create a Dep instance
this.dep = new Dep()
this.vmCount = 0
// Mount an instance of itself to value.__ob__
def(value, '__ob__'.this)
// Array processing logic
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
// If value is an object
this.walk(value)
}
}
// Iterate over all properties and add a response to the property
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/** * Iterates through the array. If the array elements are objects, create an observer instance for that object and add a response */
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
Copy the code
The Observer mounts this to value.__ob__ during instantiation; That is, responsive objects have a __ob__ attribute that points to the Observer instance. An instance of dep is also mounted on this.dep;
The next step is to determine whether it is an array. If it is an array, it will follow the array processing logic (later). In the case of an object, walk is called through each property of the object, setting getters and setters for each property through the defineReactive method
defineReactive
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function, shallow? : boolean) {
// Instantiate dep with one key for each DEp
const dep = new Dep()
Return obj[key]; return obj[key]
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if((! getter || setter) &&arguments.length === 2) {
val = obj[key]
}
// This recursive call handles the case where val (obj[key]) is an object, ensuring that all keys in the object are observed
letchildOb = ! shallow && observe(val)Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter () {
// ...
const value = getter ? getter.call(obj) : val
// ...
return value
},
set: function reactiveSetter (newVal) {/ *... * /}})}Copy the code
DefineReactive function
- Instantiate a
Dep
Object, akey
Corresponds to aDep
The instance - Then get
obj
Attribute descriptor to determine whether it is configurable; - Recursive calls to child objects
observe
Method, so as to ensure that no matterobj
All of its child properties can also become reactive objects that can be accessed or modifiedobj
One of the more deeply nested properties can also triggergetter
和setter
. - using
Object.defineProperty
Let’s go toobj
The properties of thekey
addgetter
和setter
Array response Principle
There is the following logic in the Observer
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
// If value is an object
this.walk(value)
}
Copy the code
During the instantiation of the Observer, it determines whether value is an array. If it is an array, it first determines whether the current environment has an __proto__ attribute. If so, protoAugment is executed
// can we use __proto__?
export const hasProto = '__proto__' in {}
Copy the code
Look at arrayMethods before looking at protoAugment and copyAugment
ArrayMethods defined in SRC/core/observer/array. Js
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]
def(arrayMethods, method, function mutator (. args) {
const result = original.apply(this, args)
const ob = this.__ob__
// Method to add elements
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2) // The splice method has new elements after the second argument
break
}
// Set the response to the new element
if (inserted) ob.observeArray(inserted)
// Trigger dependency notifications manually
ob.dep.notify()
return result
})
})
Copy the code
Vue overrides some of the methods of the array, and then mounts the overridden methods onto arrayMethods. Rewrite the idea as follows:
- When an array method is called, the native method is executed first, getting the return value of the native function
reslut
; - In the case of a new method, a response is added to the new element, provided that the new element is an object
- Triggers the current array
dep.notify
noticeWatcher
update - Returns the return value of the native function
reslut
After you know the content of arrayMethods, look at protoAugment and copyAugment, these two functions are relatively simple, do not show the code, directly say what to do
protoAugment
willarrayMethods
It’s assigned to an array__proto__
attributecopyAugment
traversearrayKeys
(arrayMethods
Property name collection), willarrayMethods
All properties are mounted to the array
What that means is that when the object being listened on is an array, Vue overrides some of the methods of the array, and then mounts those overridden methods onto the array. When these methods are called, the original method is executed first, the return value is obtained, and if it is a new property, the response is added to the new property; Finally, manually trigger the update.
The point to note here is that deP is the array deP, and an instance of deP is also created during the instantiation of the Observer
The flowchart for adding a response is as follows
Depend on the collection
Before looking at getters, let’s look at Dep and Watcher
Dep
import type Watcher from './watcher'
import { remove } from '.. /util/index'
import config from '.. /config'
let uid = 0
export default class Dep {
statictarget: ? Watcher; id: number; subs:Array<Watcher>;
constructor () {
this.id = uid++
// Initialize the subs array to store watchers that depend on this property
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
const subs = this.subs.slice()
if(process.env.NODE_ENV ! = ='production' && !config.async) {
subs.sort((a, b) = > a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Dep.target = null
const targetStack = []
// Change the value of dep. target so that the value of dep. target is the Watcher being updated.
// and adds the Watcher currently being updated to the targetStack
export function pushTarget (target: ? Watcher) {
targetStack.push(target)
Dep.target = target
}
// Delete the last element in the targetStack and change the value of dep. target (out of the stack)
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]}Copy the code
Dep is a Class that defines properties and methods
Dep.target
: This is a global uniqueWatcher
There can only be one global at a timeWatcher
By calculatingthis.subs
Initialization:subs
Array to store items that depend on this propertyWatcher
addSub
: will depend on this attributeWatcher
Added to thesubs
To do dependency collectionremoveSub
: deletesubs
In theWatcher
notify
: traversalsubs
And execute the elementupdate
Method, to informWatcher
update
Watcher
The Watcher instantiation process is more complex and will be explained in detail in the dependency collection process
Rely on the collection process
$mount calls mountComponent to create a exporcher, pass updateComponent, which is the second argument of class Watcher
// ...
updateComponent = () = > {
vm._update(vm._render(), hydrating)
}
// ...
new Watcher(vm, updateComponent, noop, {
before () {
if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* isRenderWatcher will be true */)
Copy the code
Look at Watcher’s definition
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object, isRenderWatcher? : boolean) {
this.vm = vm
// For render Watcher, isRenderWatcher is true
if (isRenderWatcher) {
// Only render Watcher will mount itself to vm._watcher
vm._watcher = this
}
vm._watchers.push(this)
if (options) {
// Initialize the Watcher properties, which are false for rendering Watcher
this.deep = !! options.deepthis.user = !! options.userthis.lazy = !! options.lazythis.sync = !! options.sync// Rendering Watcher's before function performs the beforeUpdate lifecycle
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid
this.active = true
this.dirty = this.lazy
this.deps = []
this.newDeps = []
this.depIds = new Set(a)this.newDepIds = new Set(a)this.expression = process.env.NODE_ENV ! = ='production'
? expOrFn.toString()
: ' '
// Render Watcher's expOrFn is updateComponent, which is a function
if (typeof expOrFn === 'function') {
// Assign updateComponent to the getter property
this.getter = expOrFn
} else {}
The render Watcher's lazy property is false, so the get method is executed during initialization
this.value = this.lazy
? undefined
: this.get()
}
get () {}
addDep (dep: Dep) {}
cleanupDeps () {}
update () {}
run () {}
evaluate () {}
depend () {}
teardown () {}
}
Copy the code
When initializing the render Watcher, there are several points to note
vm._watcher = this
Mount itself tovm._watcher
on- Initialize four properties
this.deps = []
this.newDeps = []
this.depIds = new Set(a)this.newDepIds = new Set(a)Copy the code
- will
updateComponent
Assigned togetter
attribute - perform
this.get()
methods
Look at the get method
get () {
// Assign dep. target to the current Watcher
pushTarget(this)
let value
const vm = this.vm
try {
// Execute the getter method, which executes the updateComponent method
value = this.getter.call(vm, vm)
} catch (e) {
} finally {
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
Copy the code
Call pushTarget to point dep. target at the current render Watcher. Call this.getter to execute the _render method. During execution of the _render method, the attribute value of the responsive property is fetched, which triggers the getter method of the property to do dependency collection
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function, shallow? : boolean) {
const dep = new Dep()
/ / Object. GetOwnPropertyDescriptor return objects on a has its own corresponding attribute descriptor
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if((! getter || setter) &&arguments.length === 2) {
val = obj[key]
}
letchildOb = ! shallow && observe(val)Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter () {
// Get the current value
const value = getter ? getter.call(obj) : val
// Add the possibility of judgment here
// 1. When you modify the value of an attribute, you may get the value of another attribute, so this can prevent the collection of dependencies again
// 2. Prevent dependency collection when lifecycle functions are executed
if (Dep.target) {
dep.depend()
if (childOb) {
// The purpose of this is
// 1. The view can be updated when certain methods of the array are executed
// Since calling some methods on the array actually triggers the update manually, but the Watcher stored in the array dep is updated, the Watcher needs to be stored in the array deP instance during the dependency collection process
// 2. When you add properties to an object, you can update the view (for the same reason as in # 1).
childOb.dep.depend()
if (Array.isArray(value)) {
// If it is an array and there are objects in the array elements, add the Watcher to the object's dep.subs
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {/ *... * /}})}Copy the code
The getter first determines whether the dep. target exists, in which case dep. target points to the current rendering Watcher, and then dep.depend(), which executes Watcher’s addDep method
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)}}}Copy the code
Dep. AddSub (this) will execute this.subs.push(sub), adding the current Watcher to the subs of the deP that holds the data. The purpose is to prepare which subs can be notified in the event of subsequent data changes
Back to the getter method, if childOb is an Observer instance, add the current Watcher to the subs of Childob.dep; If the value is an array, run a dependArray dependArray, which adds Watcher to the dep.subs of the object if there is an object in the array.
After the dependency collection is complete, go back to Watcher’s GET method and execute popTarget() to recover the dep.target value, then execute the cleanupDeps cleanup process. Compare newDeps to deps. If deps does but newDeps does not, the new VNode is no longer dependent on the current properties, and the current watcher is removed from the subs of the dep
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)}}let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
Copy the code
After dependency collection is complete, VNode is created and enter the patch process rendering node.
Distributed update
The purpose of dependency collection is to distribute updates to related dependencies when data is modified
When the value of a property is modified, the setter method for the property is executed
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function, shallow? : boolean) {
const dep = new Dep()
/ / Object. GetOwnPropertyDescriptor return objects on a has its own corresponding attribute descriptor
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if((! getter || setter) &&arguments.length === 2) {
val = obj[key]
}
letchildOb = ! shallow && observe(val)Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter () {},
set: function reactiveSetter (newVal) {
// Get the old attribute value
const value = getter ? getter.call(obj) : val
// If the values of the new and old attributes are equal, return directly
if(newVal === value || (newVal ! == newVal && value ! == value)) {return
}
// In the development environment, execute the customSetter if it is passed in
// There is only one case in which an error is reported for props
if(process.env.NODE_ENV ! = ='production' && customSetter) {
customSetter()
}
if(getter && ! setter)return
// Set the new value
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// Add a response to the new value if it is an objectchildOb = ! shallow && observe(newVal)// Notify all subscribers of the update
dep.notify()
}
})
}
Copy the code
The setter method first checks whether the old and new property values are equal, and if they are not, sets the new value and executes dep.notify() to notify all subscribers of the update
Dep. notify is a method of the dep class:
notify () {
const subs = this.subs.slice()
if(process.env.NODE_ENV ! = ='production' && !config.async) {
subs.sort((a, b) = > a.id - b.id)
}
// Trigger the update method for each Watcher in subs
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Copy the code
During dependency collection, all watchers that depend on this attribute are added to subs. Dep. notify traverses subs and triggers the update method of each Watcher
update () {
// Render Watcher's lazy is false, as shown in the section calculating properties
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
// Render Watcher's sync is also false, as shown in the Watch section
this.run()
} else {
// Render Watcher to execute queueWatcher
queueWatcher(this)}}Copy the code
The code is simple, in the case of rendering Watcher, executing queueWatcher methods
QueueWatcher methods defined in SRC/core/observer/scheduler. Js
const queue: Array<Watcher> = [] // Queue, store Watcher to execute
let has: { [key: number]: ?true } = {} // Make sure the same Watcher is added only once
let waiting = false // Ensure that nextTick(flushSchedulerQueue) is called only once at a time
let flushing = false
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if(! flushing) { queue.push(watcher) }else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1.0, watcher)
}
// queue the flush
if(! waiting) { waiting =true
nextTick(flushSchedulerQueue)
}
}
}
Copy the code
The queueWatcher method first verifies that the incoming Watcher is in the queue, ensuring that the same Watcher is added only once. Then if flushing is false, then add Watcher to the queue; Now the flushing is true and we’ll talk about it in the watch section; Next call nextTick(flushSchedulerQueue) if waiting is false and there is no nextTick waiting at the current time
Vue does not trigger the watcher callback every time the data changes. Instead, Vue adds the watcher to a queue first and executes the flushSchedulerQueue after nextTick
Then see flushSchedulerQueue implementation, it is defined in SRC/core/observer/scheduler. Js
export const MAX_UPDATE_COUNT = 100
let circular: { [key: number]: number } = {}
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
// Order the Watcher in the queue from smallest to largest by ID
queue.sort((a, b) = > a.id - b.id)
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
// Render Watcher has before methods that execute beforeUpdate hooks, before parent after child
watcher.before()
}
id = watcher.id
// Change the id in has to null
has[id] = null
// Call the watcher.run method to trigger the update
watcher.run()
if(process.env.NODE_ENV ! = ='production'&& has[id] ! =null) {
// For each watcher, circular[id] is added by 1
circular[id] = (circular[id] || 0) + 1
// When the number of executions is greater than 100, an error is reported and the loop is executed
if (circular[id] > MAX_UPDATE_COUNT) {
warn()
break}}}// ...
const updatedQueue = queue.slice()
// Empty queue, has, circular, and set to false for waiting and flushing
resetSchedulerState()
// ...
callUpdatedHooks(updatedQueue)
}
Copy the code
When performing to the queue and executes flushSchedulerQueue function, first on the queue Watcher sorting, since the childhood by id:
-
- Component updates are from parent to child. (Because the parent component is always created before the child component)
-
- The component’s
User Watcher
inRender Watcher
Before executing (because it was initialized firstwatch
Is created during initializationUser Watcher
)
- The component’s
-
- If a component is destroyed during the execution of the parent’s Watcher, its Watcher execution can be skipped, so the parent’s Watcher should be executed first
It then traverses the queue and executes the watch.before method, which executes the component’s beforeUpdate lifecycle function. Change the ID in has to null so that the Watcher with the current ID can be added to the queue again during all Watcher updates (such as changing the value of a responsive property in a listener). The update is then triggered by a call to watcher.run().
run () {
if (this.active) {
// Trigger Watcher's get method
const value = this.get()
if( value ! = =this.value ||
isObject(value) ||
this.deep
) { / *... * /}}}Copy the code
For rendering Watcher, the watcher.run() method executes the Watcher class’s get method to trigger the component update. Since rendering Watcher’s GET method returns undefined, the remaining if logic does not execute. This logic is related to computing properties, watches. FlushSchedulerQueue: flushSchedulerQueue: flushSchedulerQueue; flushSchedulerQueue: flushSchedulerQueue; flushSchedulerQueue: flushSchedulerQueue; flushSchedulerQueue: flushSchedulerQueue; FlushSchedulerQueue function executes resetSchedulerState to empty the queue, has, circular, and will be waiting, flushing to false. Then call the callUpdatedHooks method.
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
// Only render Watcher will bind itself to vm._watcher
if(vm._watcher === watcher && vm._isMounted && ! vm._isDestroyed) { callHook(vm,'updated')}}}Copy the code
CallUpdatedHooks specifies the updated lifecycle of a component. Note that the order of execution is child first and parent second. If vm._watcher === WATcher, the component has executed the Mounted life cycle, and the component is not unmounted. This concludes the distribution of updates.
conclusion
The responsive principle of Vue is to intercept data access and modification through object.defineProperty; When executing a component render function, if a responsive property is used, the getter for that property is triggered and the component’s Render Watcher is added to the property’s dep.subs. This process is called dependency collection.
When a responsive property is modified, the setter is triggered to notify all Watcher updates in dep.subs, thus rerendering the component.
Schematic of the response on the official website
Array response
For arrays, Vue overrides seven methods; When the array data is fetched, childob.dep.depend () is called in the getter, adding the current Watcher to this.dep in the array Observer instance. When the overridden method is called, all Watcher updates in the dep.subs of the array Observer instance are manually notified
Vue.prototype.$set
Add a property to the responsive object and ensure that the new property is also responsive and triggers the view update. It must be used to add new properties to reactive objects
View updates are also triggered when object or array data is modified via this.$set, for example
this.$set(this.arr, 1.'Modify array elements')
this.$set(this.obj, 'test'.'Modify object properties')
Copy the code
Let’s look at the code for set
export function set (target: Array<any> | Object, key: any, val: any) :any {
// ...
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
// In the case of an array, call splice to trigger the update
target.splice(key, 1, val)
return val
}
// If the target already has a key, it is a responsive attribute and no further action is required
if (key intarget && ! (keyin Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
// ...
// Target is not an Observer instance
if(! ob) { target[key] = valreturn val
}
// Add the new key to ob.value and add the response
defineReactive(ob.value, key, val)
// Trigger dependency notifications manually
ob.dep.notify()
return val
}
Copy the code
The vue.prototype. $set method also manually triggers notifications to update the view and adds responses to new properties.
If target is an array, then the splice method of the array is called, because Vue overwrites the splice method of the array, and all updates are triggered manually in the overwritten splice method; If the new element is an object, the response is added to that object;
If the target is a responsive object, the __ob__ attribute (with the value of an Observer instance) is obtained, and the new attribute is added to the target with defineReactive and the response is added. Then trigger the update manually.
Why is data in a component a function
When a component is reused multiple times, multiple instances are created. Each instance will retrieve data from options during initialization. If data is an object, each component instance will point to the same data, causing a conflict. So if it’s a function that returns an object, that solves the problem.