Address: www.kilig.xyz/blog?id=687…

Before I’ve always wanted to comb combing Vue source (the carding Vue source rather than the other, is a development mainly use or Vue, some of the internal principle also exposed to some, but are fragmented, not enough system, the other is because the Vue source of difficulty is relatively low, there are also many articles can refer to the document). I haven’t started writing yet. I just did it recently because I feel a little out of my way to learn. After all, Vue3 has come out

The body of the

How to read Vue source code, online related articles and suggestions are a lot, but not necessarily suitable for myself, I plan to write this series by reading the source code to achieve a simple version of Vue2 way for a long time, if there is a better way, then to adapt.

Why directly from the responsive part of the code, on the one hand, Vue’s responsive has been platitude, there are a lot of related articles, but many details have a lot of disagreement, such as whyArrays are updated by index data views are not updatedAnd so on, some things do not go to practice, talk about the general lack of confidence, on the other hand, they do not have what reading ideas, from the most familiar with the start, the efficiency will be a little higher.

Project environment

Vue uses rollup. There are many articles on the web, but it is just a JS module wrapper, but it is more suitable for the class library scenario.

Type checking. Vue uses Facebook Flow to do static type checking. Flow rather than TypeScript is probably more in line with Vue2’s lighter nature and easier to migrate. Type checking is not a part of the reading process to consider, try to focus on some of the core code logic.

Initialize the

Vue initialization is very simple, using the official website tutorial in the example, concise and clear.

The usage is as follows: pass in arguments and instantiate a Vue

<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, Initial - scale = 1.0 "> < title > Document < / title > < / head > < script SRC =" https://cdn.jsdelivr.net/npm/vue/dist/vue.js "> < / script >  <body> <div id="app"> {{ message }} </div> </body> <script> var app = new Vue({ el: '#app', data: { message: 'Hello Vue! ' } }) </script> </html>Copy the code

 

The console prints the global Vue initialized by the above code

$options: Vue.$options: Vue.$options: Vue

Entry file SRC/core/instance/index. Js

Import {initMixin} from './init' // declare constructor Vue function Vue (options) {// Need to instantiate with new, otherwise throw error if (! (this instanceof Vue)) {throw('Vue is a constructor and should be called with the 'new' keyword')} /* Initialize The vue.prototype. _init method is defined in initMixin */ this._init(options)} initMixin(Vue) export default VueCopy the code

There’s not much to say about the code here, but when I look at the code in this section, because the _init method raises some questions about the process of instantiating an object, I thought, can I take a little note

This is the instance itself after new Vue, which means that when the constructor executes, the instance of new has already declared and allocated space, and the constructor is just doing some assignment for the instance attributes.

This is the basics, but I haven’t thought about it before, assuming that the instantiation process would be

Call execution constructor => declare and initialize => generate instance

Get back to business

Responsive implementation

The data was hijacked

Start entering the data hijacking listening implementation

// src/core/instance/init.js import { initState } from './state' export function initMixin( Vue ) { Vue.prototype._init = function (options) {const vm = this vm.$options = optionsCopy the code

 

The next step is to initialize data

Initialize data, initialize what

  • Access to the data

Get the data we passed to the constructor

  • Convert data to objects

    There are two ways to define data: object literals and functions (when using components, data must be functions)

    Var app = new Vue({el: '#app', data: {message: 'Hello Vue! }}) var app = new Vue({el: '#app', // function data (){return {message: 'Hello Vue! '}}})Copy the code

    As for the use of components here, why does data have to be a function

  • Hijack data and listen to data




The code for this process is as follows

// src/core/instance/state.js import { observe } from '.. /observe/index' export function initState(vm) {const opts = vm.$options if (opts.data) {// Initialize data initData(VM)}} Function initData (vm) {// Add data to vm, $options. Data // Store data as an object data = vm._data = typeof data === 'function'? Data. The call (vm and vm) : data | | {} / / hijacked to monitor data object (the reactive principle of Vue) observe (data)}Copy the code

 

Implement observe (use object.defineProperty)

// src/core/observe/index.js export function observe (){ return new Observe() } class Observe{ constructor(value){ // This.walk (value)} // Add getter and setter walk(obj){const keys = object.keys (obj) for (let I = 0; i < keys.length; DefineReactive (obj,keys[I])}} function defineReactive(obj,key) {let val = Obj,key,{enumerable: true, 64x: 64x // Cx // Obj,key,{enumerable: true, signals: True, get(){console.log(' fetch data ${key}: ',val) return val}, Set (newVal){console.log(' changed data ${key}: ',val) if (newVal === val) {return}else{val = newVal} // Continue hijacking data (when changing the type of this property, Observe (newVal)}})}Copy the code

 

The above code completes the hijacking of a simple data type.

Special handling of array types

Why simple data type hijacking? There is a problem (because I didn’t notice this detail when I first looked at the source code, so I only simplified the logic of such a simple data hijacking). In the process of Vue development, there is often a reactive application scenario where the property type is array. Vue does not detect the behavior of changing the data by means of array index, and the code we implemented above can hijack the array type, we can write an example to test it

function observeArr(obj,key,value) { let val = value Object.defineProperty(obj, key, { enumerable: true, configurable: True, the get () {the console. The log (' read the value: 'value) return value}, the set (newVal) {if (newVal = = = value | | (newVal! == newVal && value ! == value)) {return} else {console.log(" changed value: ", newVal) value = newVal}}})} observeArr(testArr, '2', 9)Copy the code

So object.defineProperty is data of type Array that can be hijacked

Obviously, Vue is rightAn array ofData of the type is treated specially and setter/getter is not set for it. Why do you do this?

Suppose we define such a data type based on our business requirements

Data: {table1: [[1, [,3,4,5,6 [2], [[8,9,10], the final three [10]]], 13, 14], [15 and 16th]]}Copy the code

Array nested array nested array infinite nesting doll. Either the array is large (this scenario will be encountered more in development), or the array of infinite nesting dolls is large and has multiple explosions. So hijacking an array by index is very, very, very performance costly (not only to recursively traverse the array, but also to create space for hijacking). So Vue does something special for arrays, and the official instructions are that the response can only be triggered in the following way

[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
Copy the code

Why can these methods trigger updates because Vue overrides them internally, calling some method to be reactive when calling the method on the array

However, these methods are not the only ones that trigger reactive (view updates). The following array structure can trigger updates by changing values directly

Data () {return {test: [{a:1111}]}} // You can update the view with test[0]. A = newValCopy the code

Developers may not have noticed this problem before.

Now that the logic is clear, start the process of hijacking data when the property type is array

 

The first implementation of the array when the item object data hijacking.

Iterate over the array, adding an item of type Object to the responsive data

This type of judgment is actually done in the Observe class walk method. The primitive type calls Object.keys(), returns an empty array, and naturally does not perform the defineReactive method to add data to reactive data

In addition, a layer of undefinded and null judgments is applied in the observe function because object.keys () encounters undefined and null values

When you instantiate Observe, determine the data type and invoke the corresponding processing method

// src/core/observe/index.js import { isObject } from './.. /.. /share/util.js' export function observe (value){// If (! IsObject (value)) {return} return new Observe(value)} class Observe{constructor(value){// Check whether the data is array if (array.isarray (value)) {this.observeArray(value)} else {this.walk(value)}} Const keys = object.keys (obj) for (let I = 0; i < keys.length; DefineReactive (obj,keys[I])}} observeArray (items) {for (let I = 0, l = items.length; i < l; I ++) {observe(items[I])}}} function defineReactive(obj,key) {let val = obj[key] observe(val) Object.defineProperty(obj,key,{ enumerable: true, configurable: True, get(){console.log(' fetch data ${key}: ',val) return val}, Set (newVal){console.log(' changed data ${key}: ',val) if (newVal === val) {return}else{val = newVal} // Continue hijacking data (when changing the type of this property, Observe (newVal)}})}Copy the code

 

//src/share/util.js

export function isObject (obj){ return obj ! == null && typeof obj === 'object' }

Copy the code

The following implements the override array method (which also notifies data changes when an array’s overridden method is called)

  • When the type is array, override the object’s __proto__, adding to __proto__ the seven methods we mentioned above that need to be overridden to change the array itself
  • The __proto__ attribute overridden is an object whose __proto__ must continue pointing to Array, inheriting other methods of the Array
  • Methods that add elements to an array need to add the added data to the responsive data

 

In particular, overriding the __proto__ attribute requires a passObject.defineProperty()Set __proto__ to non-enumerable if passed directly through. Will recurse infinitely when entering the later part of the loop

(Yes, in the beginning when I tried to rewrite __proto__ property with the. Simplified code, I had a space overflow and thought for a long time why this happened and didn’t see the instructions on the Internet)

// src/core/observe/index.js import { isObject } from './.. /.. /share/util.js' import { def } from '.. /util/lang.js' import {arrayMethods} from './array' export function observe (value){// Check whether value is undefined or null if (! IsObject (value)) {return} return new Observe(value)} class Observe{constructor(value){// add ob_ attribute to def(value, '__ob__', If (array.isarray (value)) {value.__proto__ = arrayMethods This.observearray (value)} else {this.walk(value)}} Add getter and setter walk(obj){/* object.keys () with undefined or null */ const keys = object. keys(obj) for (let I = 0; i < keys.length; DefineReactive (obj,keys[I])}} observeArray (items) {for (let I = 0, l = items.length; i < l; I ++) {observe(items[I])}}} function defineReactive(obj,key) {let val = obj[key] observe(val) Object.defineProperty(obj,key,{ enumerable: true, configurable: True, get(){console.log(' fetch data ${key}: ',val) return val}, Set (newVal){console.log(' changed data ${key}: ',val) if (newVal === val) {return}else{val = newVal} // Continue hijacking data (when changing the type of this property, Observe (newVal)}})}Copy the code

 

/ / SRC/core/observe/array. Js export const arrayMethods = Object. The create (array. The prototype) / / rewrite the following method, these methods all can change the array itself const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', ForEach (function (method) {// Get the original implementation of this method from the array const original = Array.prototype[method] arrayMethods[method] = function (... args) { console.log(ob) const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': [insert = args.slice(2) break} [insert = args.slice(2) break] [insert = args.slice(2) break] [insert = args.slice(2) break] [insert = args.slice(2) break] Inserted) ob.observearray (inserted) return result}})Copy the code

 

This is basically the code for data hijacking in the reactive part, but let’s look at the view layer part first. It’s too much. I’ll write it next