[路飞]-手写vue

156 阅读1分钟

我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

vue是一个mvvm的框架, 由model, view,viewmodel组成,三要素是数据响应式,模板引擎及其渲染

1.先来一个小例子
// 数据的响应式的实现
function defineReactive(obj, key ,val){
    // 递归
    const dep = new Dep()
    observe(val)
    Object.defineProperty(obj ,key, {
        get(){
            dep.addDep()
            console.log('get',val)
            return val
        },
        set(newVal){
            if(newVal != val){
                val = newVal
                watchers.forEach(w=> w.update()) 
            }
        }
    })
}
2.遍历传入obj的所有属性,执行响应式处理
function observe(obj){
    //首先判断obj是对象
    if(typeof obj != 'object' || obj == null){
        return obj
    }
    Object.keys(obj).forEach(key => defineReactive(obj, key , obj[key]))
}

3.新属性的响应式处理

function set(obj ,key ,val){
    defineReactive(obj ,key val)
}

4.Vue类


function proxy(vm){
    Object.keys(vm).forEach(key => {
        Object.defineProperty(vm ,key, {
            get(){
                Dep.target && dep.addDep(Dep.target)
                return vm.$data[key]
            },
            set(v){
                vm.$data[key] = val
                dep.notify()
            }
        })
    })
}
class MVue{
    constructor(options){
        this.$options = options
        this.$data = options.data
        // 1、响应式:对data进行数据响应式处理
        observe(this.$data)
        // 1.1 将vue实例做个代理
        proxy(this)
        // 2、编译
        new Compile(options.el,this)
    }
}
5.遍历模板树,解析其中动态变量部分,初始化获得渲染函数
class Compile{
    constructor(){
       //获取宿主元素dom
       const el = document.querySelector(el)
       // 编译它
       this.compile(el)
    }
    compile(el){
        const childNodes = el.childNodes;
        childNodes.forEach(node => {
            if(this.ieElement(node)){
                //元素 解析动态指令 属性 事件
                console.log('编译元素' + node.nodeName)
                const attrs = node.attributes
                Array.from(attrs).forEach(attr => {
                    // 判断是否是一个动态属性
                    const attrName = attr.name
                    const exp = attr.value
                    // v-text \ v-html \ v-xx
                    if(this.isDir(attrName)){
                        const dir = attrName.substring(2)
                        this[dir] && this[dir](node , exp)
                    }
                })
                if(node.childNodes.length){
                    this.compile(node)
                }
            }else if(this.isInter(node)){
                //插值
                this.compileText(node)
            }
        })
    }
    isDir(attrName){
        return attrName.startsWith('v-')
    }
    // 处理所有绑定 dir就是指令名称
    update(node, exp, dir){
        // 初始化
        const fn = this[dir + 'updater']
        fn && fn(node, this.$vm[exp])
        // 创建watcher实例
        new Watcher(this.$vm, exp, (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(node , exp){
        this.update(node , exp, 'html')
        //node.innerHTML = this.$vm[exp]
    }
    htmlUpdater(node,val){
        node.innerHTML = val
    }
    //解析{{}}
    compileText(){
        this.update(node, RegExp.$1, 'text')
        // node.textContent = this.$vm[RegExp.$1]
    }
    // 是否是元素
    isElement(node){
        return node.nodeType == 1
    }
    // 是否是插值
    isInter(node){
        return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent)
    }
}
this.watchers = []
// 负责具体节点更新
class Watcher{
    constructor(vm ,key ,updater){
        this.vm = vm;
        this.key = key 
        this.updater = updater
        // 读当前值, 触发依赖收集
        Dep.target = this
        this.vm[this.key]
        Dep.target = null
    }
    update(){
        const val = this.vm[this.key]
        this.updater.call(this.vm , val)
    }
}

// Dep和响应式属性key之间有一一对应关系
// 负责通知watcher更新
class Dep {
    constructor(){
        this.deps = []
    }
    addDep (watcher){
        this.deps.push(watcher)
    }
    notify(){
        this.deps.forEach(w => w.update())
    }
}