Vue双向绑定原理实现

compile.js

// 编译
class Compile{
    constructor(el,vm){
        this.$el = el
        this.$vm = vm

        if(this.$el){
            // 1.获取到app下面所有的节点
            this.$Fragment = this.getNodeFragment(this.$el)
            // 2.进行编译
            this.compile(this.$Fragment)
            // 3.将编译好的节点插入app
            this.$el.appendChild(this.$Fragment)
        }
    }
    getNodeFragment(root){
        // 创建文档碎片
        var frag = document.createDocumentFragment()
        var child
        // firstChild返回所有子节点
        while(child = root.firstChild){
            // 将节点保存在了js的内存当中 页面上就不会有这个节点了
            frag.appendChild(child)
        }

        return frag
    }
    compile(fragment){
        var childNodes = fragment.childNodes
        // 遍历所有的子节点
        Array.from(childNodes).forEach((node)=>{
            // 判断文本节点
            if(this.isText(node)){
                // 文本节点的编译
                this.compileText(node)            
            }
            // 判断元素节点
            if(this.isElement(node)){
                var attrs = node.attributes

                Array.from(attrs).forEach((attr)=>{
                    // v-text sex
                    // @click handle
                    var key = attr.name
                    var value = attr.value

                    // 判断是否是指令
                    if(this.isDirective(key)){
                        // text
                        // 获取指令名称
                        var dir = key.substr(2)
                        // 调用指令对应的函数
                        this[dir+"update"] && this[dir+"update"](node,this.$vm[value])
                    }
                    // 判断是否是事件
                    if(this.isEvent(key)){
                        var dir = key.substr(1)
                        this.handleEvent(node,this.$vm,value,dir)
                    }
                })
            }

            // 如果子节点下面还有子节点那么就进行递归
            if(node.childNodes && node.childNodes.length>0){
                this.compile(node)
            }
        })
    }
    isText(node){
        // 判断文本节点 并且文本节点中必须要有{{内容}}
        return node.nodeType === 3 && /\{\{(.+)\}\}/.test(node.textContent)
    }
    isElement(node){
        return node.nodeType === 1
    }
    compileText(node){
        // 分别代表元素 vue的实例 {{属性}} 标识
        this.update(node,this.$vm,RegExp.$1,'text')
    }
    // 更新
    update(node,vm,exp,dir){
        var updateFn = this[dir+"update"]
        updateFn && updateFn(node,vm[exp])

        new Watcher(node,vm,exp,(value)=>{  
            updateFn && updateFn(node,vm[exp])
        })
    }
    textupdate(node,value){
        node.textContent = value
    }
    // 判断指令
    isDirective(attr){
        return attr.indexOf("v-") === 0
    }
    // 判断事件
    isEvent(attr){
        return attr.indexOf("@") === 0
    }
    // 事件处理
    handleEvent(node,vm,callback,type){
        // 判断methods是否存在以及callback函数是否在methods中 如果存在则进行绑定
        var fn = vm.$options.methods && vm.$options.methods[callback]
        node.addEventListener(type,fn.bind(vm))
    }
}

index.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">
        <div>{{username}}</div>
        <div>{{age}}</div>
        <div v-text="sex"></div>
        <button @click="handle">点击</button>
    </div>
</body>
</html>
<script src="./index.js"></script>
<script src="./compile.js"></script>
<script>
    var vm = new LiuyangVue({
        el:"#app",//挂载点
        data:{
            username:"liuyang",
            age:18,
            sex:"女"
        },
        methods:{
            handle(){
                this.username = "哈哈"
            }
        }
    })

    console.log(vm);
    
</script>

index.js

class LiuyangVue{
    // 配置项
    constructor(options){
        this.$options = options
        // 数据 
        // 给data上所有的属性进行遍历 给每个属性加上getter和setter方法
        this.$data = options.data
        // 挂载点
        this.$el = document.querySelector(options.el)
        // 数据劫持 作用:给data上的每个属性添加getter和setter方法
        this.observer(this.$data)

        // 进行编译 vue初始化的时候
        new Compile(this.$el,this)
    }

    observer(data){
        // 判断传入的data是不是一个对象
        if(!data || typeof data !== "object")
            return
        // 获取到data身上所有的key值进行遍历
        Object.keys(data).forEach(key=>{
            // 给data身上所有的属性添上getter和setter方法
            this.defineReactive(data,key,data[key])
            // 对象 key 配置项
            // Object.defineProperty("对象","key",{})
            // 将data身上所有的属性复制一份到vm的实例身上
            this.proxyData(key)
        })
    }
    proxyData(key){
        Object.defineProperty(this,key,{
            get(){
                return this.$data[key]
            },
            set(newVal){
                this.$data[key] = newVal
            }
        })
    }
    defineReactive(data,key,val){
        // 递归(对子属性对象的属性进行递归) 检测data属性的值是否还是一个对象 如果是则再进行遍历
        this.observer(val)

        var dep = new Dep()
        // 添加getter和setter方法
        Object.defineProperty(data,key,{
            get(){
                // 依赖收集
                Dep.target && dep.addDep(Dep.target)

                // 访问
                return val
            },
            set(newVal){
                // 设置
                if(newVal == val)
                    return

                val = newVal

                // 当设置的时候我们只要做一次通知更新即可
                dep.notify()
            }
        })
    }
}

class Dep{
    constructor(){
        // 存储所有的依赖
        this.deps=[]
    }
    addDep(dep){
        this.deps.push(dep)
    }
    notify(){
        this.deps.forEach((dep)=>{
            dep.update()
        })
    }
}

class Watcher{
    constructor(node,vm,exp,cb){
        this.$vm = vm
        this.$exp = exp
        this.cb = cb

        Dep.target = this
        this.$vm[this.$exp]//这一步是在做getter的触发 vm.username 做访问
        Dep.target = null
    }
    update(){
        this.cb.call(this.$vm,this.$vm[this.$exp])
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值