zoukankan      html  css  js  c++  java
  • vue简版源码实现

    编写一个lvue.js,实现mvvm ,感兴趣的同学可以把代码拷贝到本地看一下实现思路

    1 html部分(测试用)

    <!--
     * @Author: your name
     * @Date: 2020-07-05 22:21:34
     * @LastEditTime: 2020-07-07 00:26:35
     * @LastEditors: Please set LastEditors
     * @Description: In User Settings Edit
     * @FilePath: /justdoit/vue-手写简版源码/lvue.html
    
    --> 
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
    </head>
    <body>
      <div id="app">
        <p @click="onclick">{{counter}}</p>
        <p l-text="counter"></p>
        <p l-html="desc"></p>
        <input l-model="inputVal" type="text">
        <p>{{inputVal}}</p>
      </div>
      <script src="lvue.js"></script>
    
      <script>
        const app = new LVue({
          el:'#app',
          data: {
            counter: 1,
            desc: 'lvue<span style="color:red">html绑定的span标签</span>',
            inputVal:"l-model双向绑定的值"
          },
          methods: {
            onclick() {
              // this必须是组件实例
              console.log(this.counter);
              
            }
          },
        })
        // setInterval(() => {
        //   app.counter++
        // }, 1000);
        
      </script>
    </body>
    </html>

    2 js部分

    /*
     * @Author: 
     * @Date: 2020-07-05 22:23:07
     * @LastEditTime: 2020-07-06 23:39:12
     * @LastEditors: Please set LastEditors
     * @Description: In User Settings Edit
     * @FilePath: /justdoit/vue-手写简版源码/lvue.js
     * 
     * 
     * 执行new Vue() 的时候做了哪些事情?
     * 1 对data中传入的数据做数据劫持,监听所有属性    --监听器:Observer
     * 2 对模版执行编译,找到动态绑定的数据{{}}和指令v-bind,v-if 等,从data中获取并初始化视图 --编译器:Compile
     * 3 定义一个更新函数和Watcher,将来在数据变化的时候,Watcher调用更新函数来更新视图 --监听器:Watcher
     * 4 由于data中的值可能被绑定了多次,所以data中的每个key都需要一个管家Dep来管理多个Watcher,将来数据变化,会找到对应的Dep,通知Watcher执行更新函数
     * 
     * 上面过程涉及到的几个角色及作用:
     * 1 Vue:框架的构造函数
     * 2 Observer:执行数据相应化(分辨数据是对象还是数组)
     * 3 Compile:编译模版,初始化视图,收集依赖(更新函数的实现,Watcher的创建)
     * 4 Watcher:执行更新函数
     * 5 Dep:管理多个Watcher,批量更新
     */ 
    class LVue {
        constructor(options){   //options传入的是vue实例
            // 保存选项
            this.$options = options
    
            this.$data = options.data;
            // this.$$data = options.data
            // 响应化处理
            objserve(this.$data) 
            // 代理$data中属性
            proxy(this) //this =>vm
    
            // 执行编译
            new Compile(this.$options.el,this)
        }
    }
    // 将来用户可能直接通过vm.xxx去访问vm.$data中的数据,这里做个方便用户操作的方法,将$data中的值遍历出来直接放到vm上,方便用户使用
    function proxy(vm){
        Object.keys(vm.$data).forEach(key=>{
            Object.defineProperty(vm,key,{
                get(){
                    return vm.$data[key]
                },
                set(newVal){
                    vm.$data[key] = newVal
                }
            })
        })
    }
    // 将来每一个响应式对象都会伴生一个Observer实例
    class Observer{
        constructor(value){
            this.value = value;
            // 判断value是obj还是数组
            this.walk(value)
        }   
        walk(obj){
            Object.keys(obj).forEach(key =>defineReactive(obj,key,obj[key]))
        }
        
    }
    function defineReactive(obj,key,val){
        // val可能还是个对象,需要递归一下
        objserve(val)
        // 每执行一次defineReactive就创建一个Dep实例
        const dep = new Dep()
        Object.defineProperty(obj,key,{
            get(){
                Dep.target && dep.addDep(Dep.target)
                return val
            },
            set(newVal){
                if(newVal !== val){
                    val = newVal
                    // 如果改变的是个对象,还需要调用一下
                    objserve(newVal)
                    console.log('set', newVal);
                    // 在这里已经监听到了数据的变化,后续可以做一些更新视图的操作
                    dep.notify()
                }
            }
        })
    }
    // Watcher:小秘书,界面中的每个依赖对应一个小秘书,更新视图用
    class Watcher{
        constructor(vm,key,updateFn){
            this.$vm = vm
            this.key = key
            this.updateFn = updateFn
            
            // 读一次数据,触发defineReactive里面的get()
            Dep.target = this
            this.$vm[this.key]
            Dep.target = null
        }
        // 将来管家调用
        update(){
            this.updateFn.call(this.$vm,this.$vm[this.key])
        }
    }
    // 如果一个对象有多个属性,循环调用defineReactive,传入每一个值去进行监听
    function objserve(obj){
        // 判断obj类型
        if(Object.prototype.toString.call(obj)!=="[object Object]"){
            return
        }
        new Observer(obj);
        
    }
    class Dep{
        constructor(){
            this.deps = []
        }
        addDep(watcher){
            this.deps.push(watcher)
        }
        notify(){
            this.deps.forEach(watcher=>{
                watcher.update()
            })
        }
    }
    // 编译过程
    class Compile {
        constructor(el,vm){
            this.$vm = vm
            this.$el = document.querySelector(el)
            if(this.$el){
                this.compile(this.$el)
            }
        }
        compile(el){
            // el是根节点,需要递归遍历,看每个节点是标签还是文字
            el.childNodes.forEach(node=>{
                if(this.isElement(node)){
                    console.log("是标签",node.nodeName)
                    this.compileElement(node)
                }else if(this.isInter(node)){
                    console.log("是插值表达式",node.textContent)
                    this.compileText(node)
                }
                // 如果当前标签里面还存在标签,递归一下向下查找
                if(node.childNodes){
                    this.compile(node)
                }
            })
        }
        // 判断是否是标签
        isElement(node){
            return node.nodeType === 1
        }
        // 判断是否是插值表达式
        isInter(node){
            return node.nodeType ===3 && /{{(.*)}}/.test(node.textContent)
        }
        
        compileElement(node){
            // 获取节点属性
            const nodeAttrs = node.attributes
            // 转成数组循环
            console.log(nodeAttrs)
            Array.from(nodeAttrs).forEach(attr=>{
                const attrName = attr.name  //指令名字
                const exp = attr.value  //
                console.log('typeof--------',attrName)
                if(attrName.indexOf('l-')!=-1){ //如果是指令 执行对应方法
                    const dir = attrName.substring(2)
                    // 执行对应的方法
                    this[dir] && this[dir](node,exp)
                }else if(attrName.startsWith('@')){
                    const eventName = attrName.slice(1)
                    this.listenerEvent(node,exp,eventName)
                    
                }
            })
        }
        // 所有动态绑定都需要创建更新函数,以及对应watcher实例
        update(node,exp,dir){
            // 更新的时候fn对应一个更新函数,例如指令是text的时候对应一个 textUpdater函数,在textUpdater中做视图修改操作
            const fn = this[dir+'Updater']
            fn && fn(node,this.$vm[exp])
            new Watcher(this.$vm,exp,function(val){
                fn && fn(node,val)
            })
        }
        textUpdater(node,val){
            node.textContent = val
        }
        htmlUpdater(node,val){
            node.innerHTML = val
        }
        // 插值文本的编译
        compileText(node){
            // 获取表达式的值
            console.log(RegExp.$1)
            this.update(node,RegExp.$1,'text')
            
        }
        // l-text
        text(node,exp){
            
            this.update(node,exp,'text')
        }
        // l-html
        html(node,exp){
            
            this.update(node,exp,'html')
        }
        // l-model
        model(node,exp){
            const tagName = node.tagName;
            if(tagName==='INPUT' || tagName === 'TEXTAREA'){
                node.value = this.$vm[exp]
                if(/msie/i.test(navigator.userAgent)) {   //ie浏览器
                    node.attachEvent("onpropertychange", this.handler(event,exp)); 
                } 
                else {//非ie浏览器
                    console.log("非ie浏览器")
                    node.addEventListener("input",(event)=>{
                        this.$vm[exp] = event.target.value
                        this.$vm.$data[exp] = event.target.value
                        console.log(this.$vm)
                    }, false); 
                } 
                
            }else{
                console.error("v-model只能设置在input或者textarea上")
            }
           
            
        }
        listenerEvent(node,exp,eventName){
            node.addEventListener(eventName,()=>{
                this.$vm.$options.methods[exp].call(this.$vm)
            })
        }
    }
  • 相关阅读:
    HDU 1010 Tempter of the Bone
    HDU 4421 Bit Magic(奇葩式解法)
    HDU 2614 Beat 深搜DFS
    HDU 1495 非常可乐 BFS 搜索
    Road to Cinema
    Sea Battle
    Interview with Oleg
    Spotlights
    Substring
    Dominating Patterns
  • 原文地址:https://www.cnblogs.com/panda-programmer/p/13258567.html
Copyright © 2011-2022 走看看