directory
- The property type of the object
- Array length and index
- Vue to array method hack
Attribute types
We know that an object is an unordered collection of attributes. There are three ways to create an object that contains attributes:
- The constructor
- literal
- defineProperty
var object1 = new Object()
object1.name = 'a'
var object2 = {}
object2.name = 'b'
var object3 = {}
Object.defineProperty(object3, 'name', {
enumerable: true.configurable: true,
get() {
return 'c'
},
set() {
// do}})Copy the code
We’ll talk about property types after that.
Attribute types are divided into
- Data attributes
- Accessor properties
The ECMA specification defines attributes placed in 2 pairs of square brackets to represent internal attributes
Same thing, both
[[Configurable]]
This literally means whether a property is configurable — whether it can be modified; Can delete attributes by delete; Can you change a property to an accessor property?[[Enumerable]]
Whether throughfor-in
Loop back to this property.
The difference between
- Data attributes
[[Writable]]
Could you write[[Value]]
The value of the attribute
- Accessor properties
[[Get]]
The value function[[Set]]
The mutator
Now look at the difference between property creation
- Methods 1 and 2 are the same for assigning attributes, but the difference is how objects are created. In the use of
object.name
When we assign, we’re actually assigning to a data attribute[[Value]]
Assignment, same thing with values - Objects created through the third method are in pairs
object.name
Values are assigned via accessor properties[[Get]]
and[[Set]]
function
Use defineProperty to notice points
Suppose we want to change the value of a to 123
var object = { a: 1 }
Object.defineProperty(object, 'a', {
enumerable: true.configurable: true,
get() {
// Attribute A cannot be referenced in a function, otherwise circular references will be created
/ / error
return this.a + '23'
/ / right
return val + '23'
},
set(newVal) {
To modify properties based on their original values, we can take advantage of closures
// The set function is called when the object is initialized, and the value of an attribute (such as a) is stored in a closure
// Then we can use the value of the closure variable to change the value
val = newVal
}
})
// This is an assignment and then a modification
Copy the code
How do I listen for changes to an object
Array length and index
We know that Vue overwrites the prototype of the array to detect changes in the array because defineProperty cannot detect changes in the array length, specifically the increase in length caused by changing length.
We need to understand two concepts: array length and array index
The length property of the array is initialized to
enumberable: false
configurable: false
writable: true
Copy the code
That is, trying to delete and modify (but not assign) the Length attribute doesn’t work.
An array index is a way to access the value of an array. If you compare it to an object, an index is the property key of the array, which is two different concepts from Length.
var a = [a, b, c]
a.length = 10
// The value of indexes 3-9 will also be undefined
// But keys in index 3-9 have no values
// we can print with for-in and only print 0,1,2
for (var key in a) {
console.log(key) / / 0
}
Copy the code
When we give the array push, we assign length
Relationship between Length and numeric subscripts – There is a close relationship between the length property of a JavaScript array and its numeric subscripts. Several methods built into arrays (such as Join, slice, indexOf, and so on) take the value of Length into account. Other methods (push, splice, etc.) also change the value of length.
Each of these built-in methods changes the value of length as it manipulates an array, in two cases
- Reduce the value of
- When we shift an array, you’ll notice that it traverses the array (as shown below) and the index of the array is updated accordingly. In this case defineProperty can be detected because there are attributes (indexes).
- The added value of
- When push value, the array length will be +1 and the index will also be +1, but the index at this time is new. Although defineProperty cannot detect the new attribute, the new object attribute can be displayed in VUE
vm.$set
To add a listener - Manually assign length to a larger value, in which case the length will be updated, but the corresponding index will not be assigned, that is, the object’s attributes are not there, and defineProperty, however awesome it is, cannot handle listening for unknown attributes.
- When push value, the array length will be +1 and the index will also be +1, but the index at this time is new. Although defineProperty cannot detect the new attribute, the new object attribute can be displayed in VUE
Verifies the effect of several internal methods on the index of an array
// Define an observe method
function defineReactive(data, key, val) {
Object.defineProperty(data, key, {
enumerable: true.configurable: true.get: function defineGet() {
console.log(`get key: ${key} val: ${val}`)
return val
},
set: function defineSet(newVal) {
console.log(`set key: ${key} val: ${newVal}`)
// Remember closures we discussed above
// The new value is assigned to val and stored in memory to achieve the effect of the assignment
val = newVal
}
})
}
function observe(data) {
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key])
})
}
let test = [1.2.3]
/ / initialization
observe(test)
Copy the code
In console.log, you’ll notice that the array is iterated over during printing
The printing process can be understood as
- The test variable points to an array of 3 bytes, but does not know what the index value is
- Traverse index
Next we do the following
- In push, the new index is added and the length is changed, but the new index is not observed
- Modify the value of the new index
- The value corresponding to the new index is displayed
- Get is triggered when the index is displayed by the value of Observe
- Observe that the observe set index was not triggered when the index was deleted. Observe that the observe set index was not triggered when the index was deleted. As you can see in the figure below, the same is true
- Set is triggered by changing the value of index 1
- Unshift iterates the values with indexes 0 and 1, stores them, and reassigns them
When we assign length, we can see that we do not traverse the array to assign the index.
For defineProperty, arrays and objects are handled equally, except that get and set are overwritten during initialization to detect changes in arrays or objects. For new attributes, manual initialization is required. For arrays, push and unshift values can also be added to the index. Add observe to the index. Pop, shift deletes the updated index and also triggers get and set of defineProperty. If length is reassigned to an array, no new index is added, because it is not clear how many new indexes are added. According to the ECMA specification, the maximum index is 2^ 32-1, so it is not possible to loop through the index assignment.
The above reference
- MDN array length
- ECMA 22.1.4.1 length
- ECMA array-index
And what makes me think about this is
- Why can’t the Vue array be monitored for changes? [question]
What helps me is zhihu @liuqipeng’s answer
Vue to array method hack
Vue handles array observe separately
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
// Determine whether the array instance has a __proto__ attribute, use protoAugment
// The protoAugment driver is the __proto__ of the override instance
// target.__proto__ = src
// Rewrite the new arrayMethods to value
augment(value, arrayMethods, arrayKeys)
// Then initialize the value of the observe existing index
this.observeArray(value)
} else {
this.walk(value)
}
Copy the code
Again, how to rewrite arrayMethods, in array.js, we can see
const arrayProto = Array.prototype
// Copy the array constructor prototype
Note here that the array constructor prototype is also an array
The __proto__ pointer to the prototype in the instance is also an array
// The array has no index because length = 0
// Instead have properties called array methods with values for corresponding functions
export const arrayMethods = Object.create(arrayProto)
// Override the following methods
const methodsToPatch = [
'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
Copy the code
As shown below, this is fine when I assign the __proto__ index to 0, but the rest of the attributes remain behind. We can think of an array constructor as an empty array, but it gives you several built-in methods by default.
Let’s see why we’re only rewriting these methods. Okay?
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
// Def is very important, which means to redefine attributes with Object.defineProperty
// But arrayMethods is an array, which is why we explained above
// The array constructor prototype is an empty array but defaults to property methods
// So the definition here is tricky
def(arrayMethods, method, function mutator (. args) {
const result = original.apply(this, args)
// ob is an observe instance
const ob = this.__ob__
let inserted
switch (method) {
// Why push and unshift are treated separately?
// We explained above that these two methods increase the index of the array, but the new index bit needs to be manually added
case 'push':
case 'unshift':
inserted = args
break
// In the same way, you need to manually observe the added value of the third parameter of splice
case 'splice':
inserted = args.slice(2)
break
}
// The rest of the methods update the original index, and the initialization has observed
if (inserted) ob.observeArray(inserted)
// notify change
// Then notify all subscribers to trigger a callback
ob.dep.notify()
return result
})
})
Copy the code
Why defineProperty can’t detect array length “changes”