vue源码之双向绑定原理

了解Object.defineProperty

了解过vue绑定原理的人都知道。双向绑定的原理是利用数据劫持结合发布者–订阅者模式的方式,通过Object.defineProperty来劫持各个属性setter、getter,在数据发生变动时发布消息给订阅者,触发响应的回调函数。
简单了解一下Object.defineProperty,具体用法查看MDN

手动实现简单的绑定

var obj  = {};
Object.defineProperty(obj, 'name', {
        get: function(val) {
            console.log('获取值被修改的值')
            return val;
        },
        set: function (newVal) {
            console.log('我被设置了'+ newVal)
        }
})
obj.name = '隔壁老王';//在给obj设置name属性的时候,触发了set这个方法
var val = obj.name;//在得到obj的name属性,会触发get方法

这样我们就可以在get和set中触发其他函数,从而来实现监听数据变动的目的。
根据以上描述,我们可以实现一个简单的双向绑定:代码如下

<!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="bindDemo"></div>
    <input type="text" id="iptVal">
    <script>
        var bindDemo = document.getElementById('bindDemo')
        var iptVal = document.getElementById('iptVal')
        var obj = {}
        Object.defineProperty(obj, 'name',{
            get:function(){
                return val
            },
            set:function(newVal){ // 给对象设置值得时候会触发该方法
                console.log(newVal)
                bindDemo.innerHTML = newVal
            }
        })
        iptVal.addEventListener('input', function(e){
            obj.name = e.target.value
        })
        obj.name = '老李' // 给obj设置了name属性从而触发了set方法
    </script>
</body>
</html>

这样就实现了一个简单的双向绑定。

vue双向绑定

原理图镇楼:

劫持监听所有属性
通知变化
通知变化
添加订阅者
解析指令
订阅数据变化
初始化视图
更新视图
MVVM
Observer
Dep
Watcher
compile
updater

原理图解析:
1、observer的作用:通过object.defineProperty()循环劫持vue中data的所有属性值,从而利用get和set来通知订阅者Dep,从而来更新视图。
2、指令解析:我们都知道在vue中实现双向绑定的常用指令有:v-model,v-text,{{}}等等。也就是说在渲染html节点时,碰到这些指令的时候会进行指令解析。每碰到一个指令,就会在Dep中增加一个订阅者,这个订阅者只是更新自己指令对应的数据。每当set方法触发,就会循环触发Dep中对应的订阅者。
实现一个observer监听器,通过递归的方法遍历所有的对象以及对象中的对象也就是属性值,从而来监听所有的属性

所有对象的属性劫持

<!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="bindDemo"></div>
    <input type="text" id="iptVal">
    <script>
        var bindDemo = document.getElementById('bindDemo')
        var iptVal = document.getElementById('iptVal')
        function defineReactive(obj,key,val){
            observe(val);
            Object.defineProperty(obj, key, {
                get: function(){
                    //在这里进行依赖收集
                    return val
                },
                set: function(newVal){
                    if(newVal === val) return
                    console.log(newVal)
                    iptVal.value = newVal
                    bindDemo.innerHTML = newVal
                }
            })
        }
        /**通过遍历所有属性的方式对这个obj进行defineReactive的处理***/ 
        function observe (obj) {
            debugger
            if(!obj || (typeof obj !=='object')) {
                return
            } 
            Object.keys(obj).forEach(function(key){
                defineReactive(obj, key, obj[key]);
            })
        }
        var dataList ={
            person:{
                name: '老王',
                age: '17'
            }
        }
        observe(dataList)
        iptVal.addEventListener('input', function(e) {
            // 给obj的name属性赋值,进而触发该属性的set方法
            dataList.person.name = e.target.value;
        });
        dataList.person.name = '老李'
        // 这样就实现了所有的对象以及属性的监听
    </script>
</body>
</html>

以上代码实现了对象属性值的劫持,下面通过解析指令实现对view和model的绑定

指令解析

 /** 解析指令,实现对view和model的绑定*/ 
 compile(root,vm){
       // var _this = this
		var nodes =root.children
       // 节点类型为元素
       for (let i = 0; i < nodes.length; i++) {
           var node = nodes[i]
           if (node.children.length) {
               vm.compile(node,vm)
           }
           if (node.hasAttribute('v-click')) {
              node.onclick=(function(e){
                   var attrval = nodes[i].getAttribute('v-click')
                   console.log(attrval)
                   return vm.methods[attrval].bind(vm.data)
              })()
           }
           if (node.hasAttribute('v-model')&&(node.tagName == 'INPUT' || node.tagName == 'TEXTAREA' )) {
               node.addEventListener('input',(function(e){
                   var name= node.getAttribute('v-model')
                   new watcher(vm, node, name, 'value') 
                   return function(){
                       vm.data[name] = nodes[e].value
                   }
               })(i))
           }
           if (node.hasAttribute('v-bind')) {
               var name = node.getAttribute('v-bind')
               new watcher(vm, node, name, 'innerHTML') 
           }
           
       }
   }
}

订阅器

创建一个可以容纳订阅者的消息订阅器Dep,订阅器Dep主要负责收集订阅者,然后在属性变化的时候执行对应订阅者的更新函数。所以显然订阅器需要有一个容器,这个容器就是list,将上面的Observer稍微改造下,植入消息订阅器:

 defineReactive (obj,key,val){
    const dep = new Dep()
    Object.defineProperty(obj,key,{
        enumerable: true,
        configurable: true,
        get:function(){
            /*****进行依赖收集(需要一个方法)将Dep.target(即当前的Watcher对象存入dep的subs中)******/
            if (Dep.target) {
                dep.addsub(Dep.target)
            }
            return val 
        },
        set:function(newVal){
            if (newVal === val) return
            val = newVal
            dep.notify()
        }
    })
}
// 构造订阅者Dep
class Dep {
    constructor(){
        /* 用来存放Watcher对象的数组 */
        this.subs = []
    }
    /*在subs中添加一个watch对象*/
    addsub(sub){
        this.subs.push(sub)
    }
    /*通知所有对象更新视图*/ 
    notify(){
        this.subs.forEach((sub) =>{
            sub.update()
        })
    }
} 

从代码上看,我们将订阅器Dep添加一个订阅者设计在getter里面,这是为了让Watcher初始化进行触发,因此需要判断是否要添加订阅者,至于具体设计方案,下文会详细说明的。在setter函数里面,如果数据变化,就会去通知所有订阅者,订阅者们就会去执行对应的更新的函数。到此为止,一个比较完整Observer已经实现了,接下来我们开始设计Watcher。

watch

我们知道,监听器Observer是在get函数执行了添加订阅者Wather的操作的,所以我们只要在订阅者Watcher初始化的时候触发对应的get函数去执行添加订阅者操作即可。而触发get函数只要获取对应的属性值就可以了。核心原因就是因为我们使用了Object.defineProperty( )进行数据监听。

 class watcher{
  constructor(vm, node, name, type){
       /* 在new一个Watcher对象时将该对象赋值给Dep.target,在get中会用到 */
       Dep.target = this
       this.name = name //指令对应的值
       this.node = node //节点
       this.vm = vm     //指令所属Vue
       this.type = type //绑定的属性值,本例为InnerHTML
       this.update()
       Dep.target = null
   }
   update() {
       this.get()
       // this.node.nodeValue = this.value 
       this.node[this.type] = this.value 
   }
   get() {
       this.value = this.vm.data[this.name]
   }
}

结语

到此为止,vue双向绑定的原理基本实现。这篇文章只是粗略的的概述了一下vue双向绑定的原理。本文的完整代码请参考这里。如果你觉得还行的话点个赞就行。如果发现有什么不足的话,欢迎指出。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
Vue是一种流行的前端框架,它提供了双向数据绑定功能,这种绑定使得应用程序的数据和界面之间的交互更加简单,同时减少了手动更新DOM的需求。在Vue中,双向数据绑定的实现源码是非常重要的。 当我们使用双向数据绑定时,Vue会监听指定属性的变化,并且在属性发生变化时更新DOM。Vue使用Object.defineProperty()方法来检测属性的变化,当属性发生变化时,Vue会触发一个回调函数,这个回调函数会负责更新DOM。 在Vue中,双向数据绑定的实现源码是非常简洁而优雅的。当我们定义一个属性并在模板中使用时,Vue会自动创建一个Watcher对象,这个Watcher对象会在属性的值发生变化时被调用。当Watcher被调用时,Vue会执行一系列的操作,包括计算属性的值、调用指令函数等。 Vue的双向数据绑定还需要考虑到性能的问题。为了确保Vue应用程序的性能表现,Vue限制了双向绑定的次数。当一个Watcher对象被调用时,Vue会检测Watcher是否已经调用过一次,如果是,则忽略进一步的更新操作。这种限制保证了Vue应用程序的性能表现,但同时也保证了数据的正确性。 总之,Vue的双向数据绑定源码解析非常重要,它涵盖了Vue应用程序中最重要的功能之一。Vue的双向数据绑定源码简单而优雅,同时还需要考虑到性能的问题,这使得Vue在应用程序中具有非常高的性能表现和数据的正确性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值