Recently ready to start Vue source learning, and every Vue important knowledge points will be recorded. We know that the core concept of Vue is data-driven view, where all operations need to be handled only in the data layer and not in the view layer. Here we will learn the responsivity principle of Vue. The responsivity principle of Vue2.0 is implemented based on Object.defineProperty. Vue listens for changes in data through getter/setter methods on incoming data object properties, relies on getter collection, and setter methods notify observers and update views when data changes.

1. Set up the development environment using rollup

Install the rollup environment

npm i @babel/preset-env @babel/core rollup rollup-plugin-babel rollup-plugin-serve cross-env -D
Copy the code

Configure a rollup

// rollup.config.js
import babel from "rollup-plugin-babel"
import serve from "rollup-plugin-serve"


export default {
    input: './src/index.js'.// Pack the entry
    output: {
        file: 'dist/umd/vue.js'.// Exit path
        name: 'Vue' , // Specify the name of the packaged global variable
        format: 'umd' , // Unified module specification
        sourcemap: true.// es6->es5
    },
    plugins: [// The plug-in to use
        babel({
            exclude:'node_modules/**' // Exclude files
        }),
        process.env.ENV==='development'? serve({open:true.openPage:'/public/index.html'.// The default path to start HTML
            port:3000.contentBase: ' '}) :null]}Copy the code

Project structures,

Here we set up a Vue project with the main code under SRC

2. Exploration of responsive principle

1.Object.defineProperty

To understand how Vue2 responds, we need to take a quick look at Object.defineProperty

Object.defineproperty () is used to define a new property directly on an Object, or to modify an existing property. By default, attribute values added using Object.defineProperty() are immutable.

Object.defineProperty(obj,prop,desc)
Copy the code
  • Obj: Objects for which attributes need to be defined
  • Prop: Object property to be defined
  • Desc: Property descriptor

This method is least compatible with IE8, which is why Vue is least compatible with IE8.

2.Vue initialization process

Let’s first analyze what the initialization of a Vue does. We usually write something like this when using a Vue:

const vm = new Vue({
  el:'#app'.data(){
    return {
      name: 'the south nine'}}})Copy the code

We know that Vue can only be initialized with the new keyword, so Vue should be a constructor that calls this._init. OK, we can do that ourselves

import {initMixin} from "./init"
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')}// An alarm is generated if the call is not made through new in the development environment
  /* Calls _init initialization, which is the */ hanging from the Vue prototype
  this._init(options)
  / / options is the new Vue is the incoming parameters, including: el, data, computed, watch, and the methods...
}

initMixin(Vue) // Add the _init method to the Vue prototype
export default Vue
Copy the code

We are going to write init.js, where we hang methods on the Vue prototype: _init,$mount,_render,$nextTick, etc

import {initState} from "./state"

// The only thing initMixin does is hang the _init method on the Vue prototype
export function initMixin(Vue){
    // Initialize the process
    Vue.prototype._init = function (options){
        // console.log(options)
        const vm = this // Use this.$options in vue
        vm.$options = options

        // * Initialize props, methods, data, computed, and watch*/
        initState(vm) 
      // initState first, and then there are many initialization events: initialization life cycle, initialization events, initialization render, etc}}Copy the code

To initialize data, we know that Vue can pass data as an object or a method, so we need to determine the data type of the passed data. If it is an object, it is directly passed to Observe, and if it is a method, it is performed first and then returned to Observe.

function initData(vm) {
    console.log('Initialize data',vm.$options.data)
    // Data initialization
    let data = vm.$options.data;
    data = vm._data =  typeof data === 'function' ? data.call(this) : data
    // Object hijacking, user changed data == "refresh page
    // MVVM mode data-driven view

    // object.definePropety () adds get and set methods to attributes
    observe(data)  // The response principle
}
Copy the code

3. Responsivity principle

To make data observable, we all know that Vue2 is implemented via Object.defineProperty. We know that Object.defineProperty can only hijack objects, not arrays, so we need to determine the data type, handle the array separately, override the method on the array stereotype, and notify the subscriber when the array changes

// Redefine ES5 with Object.defineProperty
// Object.defineProperty is not compatible with IE8 and below, so VUe2 is not compatible with IE8 version
import {isObject,def} from ".. /util/index"
import {arrayMethods} from "./array.js"  // Array methods
export function observe (data) {
    // console.log(data,'observe')
    let isObj = isObject(data)
    if(! isObj)return 
    return new Observer(data) // Observation data
}

 class Observer {
     constructor(v){
        // If there are too many data layers, the attributes in the object need to be recursively resolved, adding set and GET methods in order
        def(v,'__ob__'.this)
        if(Array.isArray(v)) {
            // The index is not monitored if it is an array, as this can cause performance problems
            // The index push shift unshift is rarely manipulated in front-end development
            v.__proto__ = arrayMethods
            // Check if the array contains objects
            this.observerArray(v)
        }else{
          // The object calls walk to hijack
            this.walk(v)
        }
        
     }
     observerArray(value) {
         for(let i=0; i<value.length; i++) { observe(value[i]) } }/* Iterate over each object and bind getters and setters for them. This method can only be called */ if the data type is an object
     walk(data) {
         let keys = Object.keys(data); // Get the object key
         keys.forEach(key= > {
            defineReactive(data,key,data[key]) // Define a responsive object}}})function  defineReactive(data,key,value){
     observe(value) // Implement depth monitoring recursively, pay attention to performance
     Object.defineProperty(data,key,{
         get(){
             // Dependency collection
             / / get the value
            return value
         },
         set(newV) {
             / / set the value
            if(newV === value) return
            observe(newV) // To continue hijacking newV, the user may set the new value as an object
            value = newV
           /* Dep objects inform all observers, discussed next */
      			//dep.notify()
            console.log(The value has changed.,value)
         }
     })
 }
Copy the code

4. Array method rewrite


/ / rewrite the array of seven methods: push, pop, shift, unshift, reverse, sort, splice causes changes in the array itself

let oldArrayMethods = Array.prototype
// value.__proto__ = arrayMethods 
// arrayMethods.__proto__ = oldArrayMethods
export let arrayMethods = Object.create(oldArrayMethods)

const methods = [
    'push'.'pop'.'shift'.'unshift'.'reverse'.'sort'.'splice'
]

methods.forEach(method= >{
    arrayMethods[method] = function(. args) {
        console.log('User called:'+method,args)
        const res = oldArrayMethods[method].apply(this, args) // Call the native array method
        // The added element may still be an object

        let inserted = args // The element currently inserted
        // The newly inserted element in the array needs to be observed again to respond
        let ob = this.__ob__
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args
                break;
            case 'splice':
                inserted = args.slice(2)
                break;
            default:
                break;
        }
        if(inserted) {
            ob.observerArray(inserted)  // Continue with the new attributes
        }

        console.log('Array updated:'+ JSON.stringify(inserted))
        // Notify all registered observers of reactive processing, which will be discussed next time
        // ob.dep.notify() 
        return res
    }
})

Copy the code

OK, now we can test our Vue

let vm = new Vue({
  el:'#app'.data(){
    return{
      a:1.b: {name:'nanjiu'},
      c: [{name:'front end'}}},computed:{}
})
vm._data.a = 2
vm._data.c.push({name:'sss'})
Copy the code

Here the console should print something like this:

So we have implemented Vue data response, but it looks a little bit weird, we want the data in Vue data to be obtained directly through this, not through this._data, this is easy, we just need to make another layer of proxy to implement.

5. The agent

export function proxy (target,sourceKey,key) {
    // target: the target object to be proxied. SourceKey: the object to be proxied
  	const _that = this
    Object.defineProperty(target, key, {
        enumerable: true.configurable: true.get: function(){
            return _that[sourceKey][key]
        },
        set: function(v){
            _that[sourceKey][key] = v
        }
    })
}
Copy the code

Then call that method in initData

function initData(vm) {
    console.log('Initialize data',vm.$options.data)
    // Data initialization
    let data = vm.$options.data;
    data = vm._data =  typeof data === 'function' ? data.call(this) : data
    // Object hijacking, user changed data == "refresh page
    // MVVM mode data-driven view
     Object.keys(data).forEach(i= > {
        proxy.call(vm,vm,'_data',i)
    })
    // object.definePropety () adds get and set methods to attributes
    observe(data)  // The response principle
}
Copy the code

Then we can happily use this to access data directly

3. Summary

Object defineProperty is used for Vue2. Object defineProperty is used for Vue2. Object defineProperty is used for Vue2, Object defineProperty is used for Vue2. The bottom layer changes reactive processing to proxy, which also works for array hijacking. Here we have only explored how Vue is responsive, how it collects dependencies and notifying views of updates

Think the article is good, you can click a like ah ^_^ plus welcome to pay attention to the comment exchange ~