Vue核心源码分析—MVVM模型底层原理、订阅发布者模式(上)

        Vue.js 是一个提供了 MVVM(Model-View-ViewModel ) 风格的双向数据绑定、数据层和视图层通过DOM监听和Data绑定的方式,实现View 和 Model的一致性。

        view层和model层之间通过 ViewModel也就是Vue实例绑定在一起实现数据驱动,免去了频繁更新Dom的操作。MVVM实现的原理是用defineProperty方法进行数据劫持(拦截处理数据),即拦截目标属性定义给目标对象(要操作的对象,为这个对象定义属性),并给目标属性一定的特性。再通过发布订阅的方式对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者,执行更新DOM的操作。


    

{{msg}}

 但是这个方法不完善,因为当data中包含多属性是就无法进行获取或者设置,所以当data中还有对象的时候就应该遍历data

        const data ={
            name: "zhangsan",
            age: 18,
        }
        //模拟vue对象
        let vm = {}
       function handleData(data) {
            //遍历data中的属性
            Object.keys(data).forEach((key) => {
                //对每个属性进行数据劫持
                // 参数:对象 属性 对象增加的描述
                Object.defineProperty(vm, key,// key就是data里的动态属性
                    {
                        get() {
                            console.log('get', data[key]);
                        },
                        set(newValue) {
                            console.log('set', newValue);
                            if (newValue === data[key]) {
                                return
                            }
                            data[key] = newValue
                            document.querySelector('#app').textContent = data[key]
                        }
                    })
            })
        }
        handleData(data);

如果data中包含多属性,对象中还有多属性就要循环递归遍历,实现起来就很麻烦。 Vue3引入proxy代理监听变化之后就可以简单高效的拦截数据。

Vue核心源码数据驱动部分如下


    
{{msg}}

{{msg}}

class Vue{
    constructor(options){
        this.$options = options
        this._data = options.data
        this.$el = typeof options.el === 'string'?document.querySelector(options.el):options.el

        this._proxyData(this._data) //调用劫持数据,注入到vue实例上

         //执行 响应式处理
        new Observer(this._data)
        // 模板编译 解析指令 初始化dom
        new Compiler(this)
    }

    // 代理数据 数据劫持
    _proxyData(data){
        // 遍历key
        Object.keys(data).forEach(key=>{
            // 挂载 defineProperty()
            Object.defineProperty(this,key,{
                get(){
                    return data[key]
                },
                set(nValue){
                    if(data[key]===nValue){
                        return 
                    }
                    data[key]=nValue
                }
            })
        })
    }
}
class Observer{ //操作数据
    constructor(data){
        this.walk(data)
    }
    // 遍历
    walk(data){
        console.log(typeof data);
        
        if(!data || typeof data !== 'object'){
            console.log('lidata',data);
            
            return
        }
        Object.keys(data).forEach(key=>{
            console.log(key);
            
            this.defineReative(data,key,data[key])
        })
    }

    // 定义响应式数据
    defineReative(data, key,value) {
        let publisher = new Publisher()
        let that = this
        //走一遍walk方法 判断是不是对象
        // this.walk(data[key])  //是个死循环 占内存溢出
        this.walk(value)
        console.log('reay-walk');
        
        Object.defineProperty(data, key, {
            get() {
                console.log('get---');
                
                // 收集依赖,添加观察者
                Publisher.target && publisher.addSub(Publisher.target)
                return value
                
            },
            set(nValue) {
                if (value === nValue) {
                    return
                }
               
                value = nValue;
                // 赋新值是个对象
                // this.walk(nValue)  this指向data
                that.walk(nValue)
                console.log('set  publisher  notify');
                
                // 通知依赖
                // 发送通知 ->观察者通过update()更新到模板dom中
                publisher.notify()
            }
        })
    }
}
// 处理模板相关操作  需要dom:el   data
class Compiler{
    constructor(vm){
        this.el = vm.$el
        this.data = vm._data
        this.compile(this.el)
    }

    // 编译
    compile(el){   //处理el 
        let childNodes = el.childNodes //是个伪数组
        Array.from(childNodes).forEach(node=>{//伪数组转换真数组
            // 代码分割
            if(this.isTextNode(node)){//文本处理
                this.compileText(node)
            }else if(this.isElementNode(node)){//元素处理
                this.compileElement(node)
            }
            if(node.childNodes){
                this.compile(node)  //递归
            }
        }) 
    }

    // 编译文本  {{}}处理插值表达式
    compileText(node){
        let value = node.textContent
        let reg = /\{\{(.+?)\}\}/
        if(reg.test(value)){
            // 获取插值表达式的变量名
            let k = RegExp.$1.trim()  //第一个分组的内容()中
            // 替换
            node.textContent = value.replace(reg,this.data[k])
        
            // 通知订阅数据变化  ,调用updata方法,调用回调函数,执行dom操作
            new Watcher(this.data,k,(newValue)=>{
                node.textContent = newValue
            })
        }
    }

    // 编译元素  v-
    compileElement(node){
        // 获取多个属性  伪数组
        let attributes = node.attributes
        //转换成真数组 遍历所有属性 
        Array.from(attributes).forEach(attr=>{
            // v-:  v-text -> text v- 获取vue指令
            // console.log(attr);
            let attrName = attr.name  //属性名
            
            // 是否是指令 v-
            if(this.isDirective(attrName)){
                // 获取属性名中 v-后面的部分
                attrName = attrName.substr(2)
                // 获取属性值 -data(key)
                let key = attr.value
                // text - 映射方法  不同指令的处理方式,也就是不同函数
                this.update(node,attrName,key)
            }
        })


    }

    update(node,attrname,key){
        // if(attrname=='text'){
        //     this.textUpdate(node,attrname,key)
        // }
        let fn = this[attrname + 'Update']
        fn && fn.call(this,node,attrname,key)
    }

    textUpdate(node,attrname,key){
        node.textContent = this.data[key]
        new Watcher(this.data,key,(newValue)=>{
            node.textContent = newValue
        })
    }
    modelUpdate(node,attrname,key){

        node.value = this.data[key]
        new Watcher(this.data,key,(newValue)=>{
            node.textContent = newValue
        })

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

    // 判断是什么节点
    isTextNode(node){
        return node.nodeType === 3   //1是元素 2是属性 3是文本
    }
    isElementNode(node){
        return node.nodeType === 1   //1是元素 2是属性 3是文本
    }
    isAttrNode(node){
        return node.nodeType === 2   //1是元素 2是属性 3是文本
    }
    isDirective(attrName){
        return attrName.startsWith("v-")
    }


}
class Watcher{  //定义订阅者类
    constructor(data,key,cb){
        this.data = data
        this.key = key
        this.cb = cb

        Publisher.target = this
        this.oldValue = data[key]
    }

    update(){ //获取发布者发布通知的信息
        console.log('watcher  update');
        
        let newValue = this.data[this.key]

        if(this.oldValue === newValue){
            return
        }
        // 调用模板更新dom
        this.cb(newValue)

    }
}
class Publisher{  //定义发布者类
    constructor(){
        this.subs =[]
    }
    addSub(sub){ //添加订阅者
        if(sub && sub.update){
            this.subs.push(sub)
        }
    }
    notify(){ //通知订阅者
        this.subs.forEach(w=>{
            w.update()//约定必须有updata放啊
        })
    }
}

你可能感兴趣的:(Vue.js核心源码分析,vue.js)