Vue2.x Vue3.0 source code analysis

Vue2.x / Vue3.0 source code analysis

I

1. Data driven (Vue's biggest feature)

I MVVM (data driven view): model view ViewModel

Traditional data driven approach

Use DOM operations to update the view

Updated method: vm=new Vue() is used for data initialization and view update. DOM operation is still used, but it is encapsulated in vm=new Vue(). We do not need to apply it ourselves. It is an internal application

2. Data hijacking (an important principle for realizing bidirectional data binding)

Hijack the data in a simulated Vue object. Once the data changes, it will be intercepted without direct change

Using object The defineproperty (target object, property, description) function is used for data hijacking

It mainly depends on the two methods get() and set() encapsulated in this function

get() {
          console.log("get", data.msg);
        }
 set(newValue) {
          console.log("set", newValue);
          if (newValue === data.msg) {
            return;
          }
          data.msg = newValue;
        }

3. Multi attribute data hijacking

By encapsulating a data hijacking function, the function includes traversing the attribute values of the whole object, data hijacking, data update, and data-driven view operations

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <div id="app">
    </div>
</head>
<body>
    <script src="js/vue.js"></script>
    <script>
        const data={
            name:"aa",
            age:18
        };
        let vm={}
        function handleData(data){
            Object.keys(data).forEach((key)=> {
            Object.defineProperty(vm,key,{
            get(){
                console.log('get',data[key]);
            },
            set(newValue){
                console.log('set',newValue);
                if(newValue===data[key])
                {
                    return
                }
                data[key]=newValue;//Data update
                //Data driven view
                document.querySelector("#app").textContent=document.querySelector("#app").textContent+"  "+data[key];


            }
        })
        });
        }
        handleData(data)
       
        console.log(vm.name);//get
        vm.name="bb";//set
        console.log(vm.age);//get
        vm.age=100;
    </script>

</body>
</html>

4. There is data hijacking of the object in the attribute

For example:

const data = {
        name: "aa",
        age: 18,
        friend: {
          gender: 0,
          name1: "bb",
        },
      };//friend is an object

Methods: define responsive data, hijack each attribute, and use recursion and traversal to realize the operation of data hijacking

5. Optimization scheme of data hijacking: Proxy proxy object

Vue3. Version 0 further optimizes the scheme of data hijacking:

Instead of thinking about internal attributes, you can directly represent the whole object

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <div id="app">{{msg}}</div>
  </head>
  <body>
    <script src="js/vue.js"></script>
    <script>
      const data = {
         name:'aaa',
         age:18,
         friend:{
             gender:"female",
             name:'bbb'
         }
      };
      let vm = new Proxy(data,{
          get(target,key){ //Target represents the target object and cannot be a concrete object
            console.log('get',target[key]);
            return target[key]
          },
          set(target,key,value){
            console.log('set',value);
            if(value===target[key])
            {
                return 
            }
            target[key]=value;
          }
      })
      console.log(vm.name);
      vm.age=100;
 
    </script>
  </body>
</html>

II

1. Publish and subscribe

utilize o n and on and on and emit to register events and trigger events, respectively.

e m i t use come touch hair , and emit is used to trigger, and emit is used to trigger and on is used to subscribe

let vm=new Vue();//They are all the same and cannot be split into three roles
      //Registration event
      //Parameters: event type, event handler
     vm.$on('change',()=>{
         console.log('event1');
     })
     vm.$on('change',()=>{
         console.log('event2');
     })
     vm.$emit('change')//Event type, event handling

2. Publish and subscribe source code

Idea:

Release( e m i t ) person transfer use matter piece in heart ( touch hair matter piece ) , however after book Read ( Emitters call the event center (trigger events) and subscribe( The "emit" user calls the event center (trigger event), and then the "on" user registers the task in the event center to subscribe (register event) [the external chain picture transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-rVHNMUnD-1627296233557)(Vue2.x Vue3.0 source code analysis. assets/image-20210716182850218.png)]

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <div id="app"></div>
  </head>
  <body>
    <script src="js/vue.js"></script>
    <script>
        //You can have multiple handler functions for the same event type
        class Vue1{
            constructor(){
                this.subs=Object.create(null)//Than this Sub = {} is better. It means there is no attribute. It is empty
                //The container is used to store the relationship between eventType and handler
                //{eventType1:[fun1,fun2..],eventType2:[],}
            }
            $on(eventType,handler){//Register events, subscribe, and register data with the handler
                this.subs[eventType]=this.subs[eventType]||[]
                this.subs[eventType].push(handler)
            }
            $emit(eventType){//Trigger events are not registered events. They cannot be initialized as above, but should be judged
                if(this.subs[eventType])
                {
                    this.subs[eventType].forEach(fun => {
                        fun()//Each eventType corresponds to more than one function. When this event is triggered, all the functions it includes will be triggered at the same time
                    });
                 }   
            }
        }
      let vm=new Vue1();
      //Registration event
      //Parameters: event type, event handler
     vm.$on('change',()=>{
         console.log('event1');
     })
     vm.$on('change',()=>{
         console.log('event2');
     })
     vm.$emit('change')//Event type, event handling
    
     
    </script>
  </body>
</html>

3. Observer mode

Compared with the publish subscribe model, the publisher is more closely related to the subscriber (observer), and there is no separate event center.

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-X6plTuJN-1627296233559)(Vue2.x Vue3.0 source code analysis. assets/image-20210716183445822.png)]

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>

  </head>
  <body>
    <script src="js/vue.js"></script>
    <script>
        class Publisher{
            constructor(){
                this.subs=[]
            }
            //Add subscriber
            addSub(sub){
               if(sub&&sub.update()){
                 this.subs.push(sub)
               }
            }
            //Notify subscribers
            notify(){
                this.subs.forEach(w=>{
                  w.update()
                })  
            }
        }   
        class Watcher{
            //Get publisher's notification information
            update(){
                console.log('update');
            }
        }
        

      const p=new Publisher()
      const w1=new Watcher()
      const w2=new Watcher()
      
      p.addSub(w1)
      p.addSub(w2)
     
    </script>
  </body>
</html>

4. Vue source code

Vue source code is compiled by means of data hijacking and template compilation

//compiler.js
// Template related operations
// dom :el
// data 
class Compiler{
    constructor(vm){
        this.el = vm.$el
        this.data = vm.$data

        this.compile(this.el)
    }

 //   compile
    compile(el){
        let childNodes = el.childNodes

        // Traversal and recursion
        Array.from(childNodes).forEach(node=>{
            // Code segmentation
            console.log('this');
            if(this.isTextNode(node)){ // text processing 
                console.log(this);
                compileText(node)
            }else if(this.isElementNode(node)){ //Element processing
                this.compileElement(node)
            }

            if(node.childNodes){
                this.compile(node) // recursion
            }
        })

    }

    // Compile text interpolation type processing {{msg}}: for example, hello world {{msg} = > Hello World Vue
    compileText(node){
        console.log(this);
        let value = node.textContent //content
        let reg = /\{\{(.+?)\}\}/ // Regular rules
        if(reg.test(value)){
            // Get: variable name of interpolation expression
            let  k = RegExp.$1.trim() 
            console.log(k);

            // replace
            node.textContent = value.replace(reg,this.data[k])

            //Data change - Notification subscription - Update - CB - compiler DOM operation
            new Watcher(this.data,k,(newVlaue)=>{
                console.log('compiler--callback');
                node.textContent = newVlaue
                console.log('view update');
            })
        }


    }

    // Compile element
    compileElement(node){
        // get attribute 

        let attributes = node.attributes

        // Traverse all attributes

        Array.from(attributes).forEach(attr=>{
            // V -: V text - > text get vue instruction  
            console.log(attr);
            let attrName = attr.name // Attribute name 

            // Is it instruction v-
            if(this.isDirective(attrName)){
                // Get the part v- after the attribute name -- instruction name
                attrName = attrName.substr(2)
                console.log(attrName);

                // Attribute value - data (key)
                let key = attr.value

                // text - mapping method. Different instructions and different processing methods are different functions
                this.update(node,attrName,key)

            }
        })

    }
   
    // Call the instruction function to update the dom content
    update(node,attrname,key){
        // if(attrname=='text'){
        //     this.textUpdate(node,attrname,key)
        // }

        // if(attrname=='model'){
        //     this.modelUpdate(node,attrname,key)
        // }

        let fn = this[attrname + 'Update']

        fn && fn.call(this,node,attrname,key)
        
    }

    // Code segmentation
    textUpdate(node,attrname,key){
        node.textContent = this.data[key]

        //Data change - Notification subscription - Update - CB - compiler DOM operation
        new Watcher(this.data,key,(newVlaue)=>{
            node.textContent = newVlaue
        })
}

    modelUpdate(node,attrname,key){
        // Note value
        node.value =this.data[key]
        
        new Watcher(this.data,key,(newVlaue)=>{
            node.value  = newVlaue
        })

        node.addEventListener('input',()=>{
           this.data[key] =node.value
        })
    }


    // Node judgment correlation
    isTextNode(node){
       return  node.nodeType === 3
    }

    isElementNode(node){
        return  node.nodeType === 1
    }

    isAttrNode(node){
        return  node.nodeType === 2
    }

    isDirective(attrName){
        return attrName.startsWith('v-')
    }


    // js dom 
    // jquery  dom 
    // template view data has no status tracking
    // MVVM: view data state synchronization, high data driving efficiency; Virtual DOM js {1} {2}
}
//Vue.js
class Vue{
    
    constructor(options){ 
        this.$options = options
        this.$data = options.data
        this.$el = typeof options.el ==='string'? document.querySelector(options.el) : options.el

        // Inject into Vue instance;
        this._proxyData(this.$data)

         // vm.$ Data = > responsive
         new Observer(this.$data)

         
        // Template compilation: parse the specified interpolation expression
        new Compiler(this)
    }

    // Data = > Data hijacking = > inject into Vue instance;
    _proxyData(data){
        // Traversal key
        Object.keys(data).forEach(key=>{

            Object.defineProperty(this,key,{
                get(){
                    return data[key]
                },
                set(nValue){
                    if( data[key]===nValue){
                       return
                    }

                    data[key] = nValue
                }
            })
        })

    }

   


}

//Observer.js
class Observer{
    constructor(data){
        this.walk(data)
    }
    // Core traversal
     walk(data){
        if(!data || typeof data !=='object'){
            return
        }

        Object.keys(data).forEach(key=>{
           
            this.defineReactive(data,key,data[key])

        })

    }


    // Define responsive data
    defineReactive(data,key,value){

        let publisher = new Publisher()


        let  that = this
        // console.log(data[key]);

     // Maximum call stack size exceeded at Object
    //    this.walk(data[key])  

       this.walk(value)

        Object.defineProperty(data,key,{
           
            get(){
                // Collect dependencies and add observers
                Publisher.target&&publisher.addSub(Publisher.target)

                return value
            },
            set(nValue){
                if( value===nValue){
                   return
                }
                // this points to data
                // console.log('this',this);

                value = nValue
                // Attribute assignment is an object
                that.walk(nValue)

                console.log('set--notify()');

                // Notification dependency
                // Send notification: data change = > observer update() = > template DOM ({SS}} = > value) = > views
                publisher.notify()
                
            }
        })
    }
}

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        hello world {{msg}}
        <h2 >
            sss{{msg}}
            <h3>hello {{msg}}</h3>
        </h2>
        <h2 v-text="msg"></h2>
        <input type="text" v-model='msg'>
    </div>

    <script src="./js/publisher.js"></script>
    <script src="./js/watcher.js"></script>
    <script src="./js/compiler.js"></script>
    <script src="./js/observer.js"></script>
    <script src="./js/vue.js"></script>
    <script>
       let vm =  new Vue({
            el:'#app',
            data:{
                msg:'hello vue1',
                friend:{
                    name:'zhangsan',
                    age:28
                }
            }
        })
    </script>
</body>
</html>

//publisher.js
class Publisher{
    constructor(){
        this.subs = []
    }

    // Add subscriber
    addSub(sub){
        if(sub && sub.update){
            this.subs.push(sub)
        }
    }

    // Notify subscribers
    notify(){
        console.log('publisher---notify()');
        this.subs.forEach(w=>{
            w.update() // appointment
        })
    }
}

// class Watcher{
//     //Get publisher's notification information
//     update(){
//         console.log('update');
//     }
// }

//watcher.js

class Watcher{


    constructor(data,key,cb){
        this.data = data
        this.key  = key
        this.cb = cb

        Publisher.target = this

        this.oldValue = data[key]
    }


    // Get publisher's notification information
    update(){
        console.log('watcher --- update');
        let newValue=this.data[this.key]

        if(this.oldValue===newValue){
            return
        }

        // Call the template to update the DOM
        this.cb(newValue)

    }
}

Keywords: Vue.js

Added by 01hanstu on Fri, 14 Jan 2022 04:49:54 +0200