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-inLoop 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 ofobject.nameWhen we assign, we’re actually assigning to a data attribute[[Value]]Assignment, same thing with values
  • Objects created through the third method are in pairsobject.nameValues 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 VUEvm.$setTo 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.

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”