手写vue

159 阅读1分钟

image.png

设计理念 数据驱动 依赖收集

1、data Bindings 数据绑定响应式
2、Dom listener
3、编译:模板引擎和渲染函数
数据响应式:监听

vue2:Object.defineProperty()
vue3:Proxy

模板引擎:描述视图、模板语法、转换为函数

插值{{}}
指令 v-bind, v-on,v-model, v-for, v-if

渲染 将模板转换为html

模板 vdom=>dom

编译模板中vue模板特殊语法,初始化视图、更新视图 image.png

html版本的

<div id='app'>
    <p>{{counter}}</p>
</div>
<script scr="node_modules/vue/dist/vue.js"></script>
<script>
    const app = new Vue({
        el:'#app',
        //model
        data:{
            counter:1
        }
    })
    setInterval(()=>{
        app.counter++
    },1000)
</script>
[myVue.js]

class myVue{
    constructor(options){
        this.$options = options
        this.$data = options.data
        
         //1 对data遍历 做响应式处理
        observe(this.$data)
        
        //对data做代理 可以直接使用data中的key
        proxy(this)
        
        //编译模板
        new Compile(options.el,this)
    }

}


//Vue.util.defineReactive(this,'current','/')
function defineReactive(obj,key,val){
    //递归
    observe(val)
    
    //data中的key 与dep一一对应
    const dep = new Dep()
    
    //属性拦截
    Object.defineProperty(obj,key,{
        get(){
            //依赖收集建立
            Dep.target && dep.addDep(Dep.target)
            return val
        },
        set(newVal){
           if(newVal !== val){
               observe(newVal)
               val = newVal
               // 通知更新
              dep.notify()
           } 
        }
    })
}

//遍历传入的obj属性,执行响应式处理
function observe(obj){
    Object.keys(obj).forEach(key=>{
        if(typeof obj !=='object' || obj ==null){
          return obj
        }
        defineReactive(obj,key,obj[key])
    })
}

//将data代理一下
function proxy(vm){
    Object.keys(vm.$data).forEach(key=>{
        // data的key直接放到vm上
        Object.defindePropery(vm,key,{
            get(){
                return vm.$data[key]
            }
            set(v){
                vm.$data[key] =v
            }
        })
    })
}

// 1.全量更新 2.利用vdom,对比得到dom操作
function update(){
}

//动态新增属性
function set(obj,key,val){
    defineReactive(obj,key,val)
}


数组会复杂 需要拦截数组的7个方法 push pop shift unshift。。。

遍历模板,解析动态部分,初始化

[compile]

class compile{
    constructor(el,vm){
        this.$vm = vm
        //获取dom节点
        const dom = document.querySelector(el)
        
        this.complie(dom)
    }
    
    compile(el){
        //遍历
        const childNodes = el.childNodes
        childNodes.forEach(node=>{
            if(this.isElement(node)){
                //元素 解析指令、属性绑定、事件等
                const  attrs = node.attributes
                Arry.form(attrs).forEach(attr =>{
                    //判断是否是动态的属性 指令 v-xx='counter'
                    const attrName = attr.name
                    const exp = attr.value
                    if(this.isDir(attrName)){
                        const dir = attrName.substring(2)
                        //指令是否存在,并执行
                        this.[dir] && this[dir](node,exp)
                    }
                })
                //递归
                if(node.childNodes.length>0){
                    this.complie(node)
                }
            }else if(this.isInter(node)){
                //插值表达式
                this.compileText(node)
            }
        })
    }
     
    // 重构 抽象 (节点 ,表达式, 指令名称)
    update(node,exp,dir){
        //初始化
        const fn = this[dir + 'Updater']
        fn && fn(node,this.$vm[exp])
        //创建watcher实例,负责后续更新
        new Water(this.$vm,exp,function(val){
             fn && fn(node,val)
        })
    }
   
    
    //v-text 只显示文本
    text(node,exp){
        this.update(node,exp,'text')
         //node.textContent = this.$vm[exp]
    }
    
    textUpdater(node,val){
        node.textContent = val
    }
    
    //v-html 显示html内容
    html(node,exp){
        //node.innerHTML = this.$vm[exp]
        this.update(node,exp,'html')
    }
    
    htmlUpdater(node,val){
        node.innerHTML = val
    }
    
    
    compileText(node){
        //获取正则的动态部分并赋值
       // node.textContent = this.$vm[RegExp.$1]
        this.update(node,RegExp.$1,'text')
    }
    
    isElement(node){
        return node.nodeType ===1
    }
    
    //  {{name}}
    isInter(node){
        return node.nodeType ===3 && /\{\{(.*)\}\}/.test(node.textContent)
    }
    
    isDir(attrName){
        return attrName.startsWith('v-')
    }
}

负责节点的更新

 
class Watcher{
    constructor(vm,key,updater){
        this.vm = vm
        this.key = key
        this.updater = updater
        
        //读取当前值,触发依赖收集 静态存watcher
        Dep.target = this
        //触发get
        this.vm[this.key]
        Dep.target = null
        
    }
    
    //dep会调用
    update(){
        //对最新值更新
        this.updater.call(this.vm,this.vm[this.key])
    }
}

Dep和属性key有一一个对应关系,负责通知watcher更新

class Dep{
    constructor(){
        this.deps = []
    }
    
    addDep(){
        this.deps.push(dep)
    }
    
    notify(){
        this.deps.forEach(dep=>dep.update())
    }
    
}

相当做了一个简易的vue1版本,不含虚拟dom