精华内容
下载资源
问答
  • vue双向数据绑定原理
    2020-06-20 12:03:41

    1. 原理

    1.1 vue双向数据绑定原理,又称vue响应式原理,是vue的核心,双向数据绑定是通过数据劫持结合发布订阅模式的方式来实现的,也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变

    1.2 vue双向数据绑定原理的实现,其核心是Object.defineProperty()方法

    1.3 Object.defineProperty(obj, prop, descriptor)方法,接收三个参数,分别为obj(定义其上属性的对象)prop(定义或修改的属性)descriptor(具体的改变方法),就是用这个方法来定义一个值,当调用时我们使用了它里面的get方法,当我们给这个属性赋值时,又用到了它里面的set方法

    let obj = {}
    Object.defineProperty(obj, 'prop', {
        get(){
            console.log('调用get方法')
        }
        set(newValue){
            console.log('调用set方法,方法的值是' + newValue)
        }
    })
    obj.prop    //调用get方法
    obj.prop = 'p'    //调用set方法,方法的值是p

    2. 简单实现js的双向数据绑定

    <!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">
            <input type="text" id="a">
            <span id="b"></span>
        </div>
    </body>
    <script>
        var obj = {};  
        var value = 'p'
        Object.defineProperty(obj, 'value', {
            get: function () {
                return value
            },
            set: function (newValue) { 
                value = newValue
                document.getElementById('a').value = value           
                document.getElementById('b').innerHTML = value
            }
        })
        document.addEventListener('keyup', function (e) {
            obj.value = e.target.value
        })
    </script>
    </html>

    上面代码说明,随着文本框输入文字的变化,span中会同步显示相同的文字内容,从而实现了model到view以及view到model的双向绑定,然后我们通过添加事件监听keyup来触发set方法,而set再修改了访问器属性的同时,也修改了dom样式,改变了span标签内的文本

    3. 真正的双向数据绑定原理

    实现vue的双向数据绑定,是采用数据劫持结合发布者和订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调,getter函数里面执行的任务是watcher订阅者, 而setter函数执行的任务是发布者,双向数据绑定原理的实现主要就是如下几步
    1)实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,当对象的属性有变化时可拿到最新值并通知订阅者
    2)实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
    3)实现一个Watcher,作为连接Observer和Compile的中间桥梁,能够订阅并收到每个数据对象属性变动的通知,执行指令绑定的相应回调函数,从而达到更新视图的目的
    4)mvvm入口函数,整合以上三者

    es中有两种属性,数据属性和访问器属性,数据属性一般用于存储数据的数值,访问器属性对应的是set/get操作,不能直接存储数据值,每种属性又都含有如下四个特性

    数据属性

    1)Configurable:表示能否通过delete将此属性删除,能否把属性修改为访问器属性,默认为false。当把属性Configurable设置为false后,该属性不能通过delete删除,并且也无法再将该属性的Configurable设置为true
    2)Enumerable:表示属性可否被枚举,即是否可以通过for in循环返回,默认false
    3)Writable:表示是否可以修改属性的值,默认false
    4)Value:该属性的数据值, 默认是undefined

    访问器属性

    1)Configurable:表示能否通过delete将此属性删除,能否把属性修改为数据属性,默认为false。当把属性Configurable设置为false后,该属性不能通过delete删除,并且也无法再将该属性的Configurable设置为true
    2)Enumerable:表示属性可否被枚举,即是否可以通过for in循环返回,默认false
    3)Get:读取属性时调用的函数, 默认为undefined
    4)Set:写入属性时调用的函数, 默认是undefined

    实现数据双向绑定的核心就是利用为每一个属性都创建了订阅者的实例对象, 以便观察, getter函数里面返回一个value值,在setter函数中写入修改后的值并调用update方法更新视图的数据值,其核心代码如下

    <input type="text" id="inp" />
    <div id="box"></div>
    <script>
        let obj = {}
        let oInp = document.getElementById('inp')
        let oBox = document.getElementById('box')
        Object.defineProperty(obj, 'name', {
            configurable: true,
            enumerable: true,
            get: function() {
                console.log('调用了get方法')
                return val
            },
            set: function(newVal) {
                console.log('调用了set方法')
                oInp.value = newVal
                oBox.innerHTML = newVal
            }
        })
        oInp.addEventListener('input', function(e) {
            obj.name = e.target.value
        })
        obj.name = '你好vue'
        function defineReactive (obj, key, val) {
            var dep = new Dep()  //构造函数  其原型是为属性添加订阅者
            Object.defineProperty(obj, key, {
                get: function() {
                    if(Dep.target) {
                        dep.addSub(Dep.target)  //添加订阅者到Dep实例对象
                    }
                    return val  // 返回监听到的value值
                },
                set: function (newVal) {
                    if(newVal === val) return
                        val = newVal  // 写入新的value值
                        dep.notify()  // 作为发布者发出通知,dep会迭代调用各自的update方法更新视图
                }
            })
        }
        function observe(obj, vm) {
            Object.keys(obj).forEach(function(key) {
                defineReactive(vm, key, obj[key])
             })
        }
    </script>

     

     

    更多相关内容
  • 本篇文章主要介绍了详解Vue双向数据绑定原理解析 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • vue.js是采用 数据劫持 结合 发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的 setter ,getter ,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。 具体步骤 需要 observer...

    在这里插入图片描述

    vue.js是采用 数据劫持 结合 发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的 settergetter ,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。

    具体步骤

    • 需要 observer 的数据对象进行 递归遍历,包括子属性对象的属性,都加上 settergetter 这样的话,给这个对象的某个属性 赋值,就会触发 setter ,那么就能监听到了数据变化。
    • compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
    • watcher 订阅者是 Observercompile 之间通信的桥梁,主要做的事情是:
      1)在自身实例化时往属性订阅器(dep)里面添加自己
      2)自身必须有一个update() 方法
      3)当属性变动 dep.notice() 通知时,能调用自身的 update() 方法,并触发 compile中绑定的回调
    • MVVM 作为数据绑定的入口,整合Observer、CompileWatcher 三者,通过Observer 来监听自己的model数据变化,通过Compile 来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

    什么是setter、getter

    答:首先,别误以为他们就是一会要说的get、set。
    对象有两种属性:

    • 数据属性 : 就是我们经常使用的属性
    • 访问器属性 : 也称存取器属性(存取器属性就是一组获取和设置值的函数)

    在这里插入图片描述
    在这里插入图片描述
    数据属性就是a和b;

    get和set就是关键字 它们后面各自对应一个函数,这个函数就是上面红字部分所讲的,存储器属性。

    get对应的方法称为getter,负责获取值,它不带任何参数。set对应的方法为setter,负责设置值,在它的函数体中,一切的return都是无效的。

    什么是Object.defineProperty()?

    答:我们先看一句定义:

    对象是由多个名/值对组成的无序的集合。对象中每个属性对应任意类型的值。

    定义对象可以使用构造函数或字面量的形式:
    在这里插入图片描述
    除了以上添加属性的方式,当然还可以使用Object.defineProperty定义新属性或修改原有的属性;

    语法

    Object.defineProperty(obj, prop, descriptor)

    参数

    • obj: 必需。目标对象;
    • prop: 必需。需定义或修改的属性的名字;
    • descriptor: 必需。目标属性所拥有的特性;

    返回值

    传入函数的对象,即第一个参数obj;

    原理

    在vue中v-modelv-name{{}} 等都可以对数据进行展示,也就是说假如一个属性都通过这三个指令了,那么每当这个属性改变的时候,相应的这个三个指令的html视图也必须改变;

    于是vue中就是每当有这样的可能用到双向绑定的指令,就在一个Dep 中增加一个订阅者(addSub),其订阅者只是更新自己的指令对应的数据,也就是 v-model='name'{{name}} 有两个对应的订阅者,各自管理自己的地方;

    每当属性的set方法触发,就循环更新 Dep 中的订阅者(notify);

    集合上面的那张图来看,就是 Observer 一旦有了 set 触发,就会通知到 Dep ,那Dep接到通知之后呢?从图上来看,下面所讲的就应该是 Compile 了,也很简单:

    首先,先要知道它负责干什么?

    compile主要做的事情是解析模板指令,将模板中的变量替换成数据;

    其次知道它什么时候要工作,只有两种情况,先上图:
    在这里插入图片描述
    1)初始化,init的时候 初始化渲染页面视图;

    2)将每个指令对应的节点绑定更新函数,添加监听数据的订阅者;

    Dep负责维护依赖,而订阅者则来自于compile,一旦有数据变动,则会通过Watcher绑定更新函数,此时Watcher也向Dep中添加了订阅者,一旦Dep接到Observer的通知,它就会再去通知Watcher,Watcher则会调用自身的update()方法,并触发Compile中绑定的回调,更新视图;

    总结

    首先我们为每个vue属性用Object.defineProperty()实现数据劫持,为每个属性分配一个订阅者集合的管理数组dep;

    然后在编译的时候在该属性的数组dep中添加订阅者,v-model会添加一个订阅者,{{}}也会,v-bind也会,只要用到该属性的指令理论上都会;

    接着为input会添加监听事件,修改值就等于为该属性赋值,则会触发该属性的set方法,在set方法内通知订阅者数组dep,订阅者数组循环调用各订阅者的update方法更新视图。

    展开全文
  • proxy方法 ...可查看Vue 2.0双向绑定原理的实现 具体实现过程的代码如下: 1、定义构造函数 function Vue(option){ this.$el = document.querySelector(option.el); //获取挂载节点 this.$data =
  • vue2双向数据绑定原理

    2022-03-21 14:59:14
    vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。 具体步骤: 第一步: 需要...

    先上文字描述:

    vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。

    具体步骤:

    第一步: 需要observer(观察者)对数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter
    这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化

    第二步: compile(模板解析器)解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

    第三步: Watcher(订阅者)ObserverCompile之间通信的桥梁,主要做的事情是:
    1、在自身实例化时往属性订阅器(dep)里面添加自己
    2、自身必须有一个update()方法
    3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。

    第四步: MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

    回答以上内容即可,下方内容,可以帮助大家理解

    代码实现vue2双向数据绑定

    在这里插入图片描述

    vue.js文件

    class Vue{
        constructor(options){
            this.$data = options.data
            // 调用数据劫持的方法
            Observe(this.$data)
            // 属性代理
            Object.keys(this.$data).forEach(key=>{
                Object.defineProperty(this,key,{
                    enumerable:true,
                    configurable:true,
                    get(){
                        return this.$data[key]
                    },
                    set(newValue){
                        this.$data[key] = newValue
                    }
                })
            })
            // 调用模板编译的函数
            Compile(options.el,this)
        }
    
    }
    // 定义一个数据劫持的方法
    function Observe(obj){
        // 递归的终止条件
        if(!obj || typeof obj !== 'object') return
        const dep = new Dep()
        // 通过Object.keys(obj) 获取到当前obj上的每个属性
        Object.keys(obj).forEach(key=>{
            // 当前被循环的key所对应的属性值
            let value = obj[key]
            // 把value这个子节点,进行递归
            Observe(value)
            // 需要为当前的key所对应的属性,添加getter和setter
            Object.defineProperty(obj,key,{
                enumerable:true,
                configurable:true,
                get(){
                    Dep.target && dep.addSub(Dep.target)
                    console.log(`有人读取了${key}的值`);
                    return value
                },
                set(newVal){
                    value = newVal
                    Observe(value)
                    dep.notify()
                },
               
            })
        })
    }
    // 对HTML结构进行模板编译的方法
    function Compile(el,vm){
        // 获取el对应的DOM元素
        vm.$el = document.querySelector(el)
        const fragment = document.createDocumentFragment()
        while((childNode = vm.$el.firstChild)){
            fragment.appendChild(childNode)
        }
        // 进行模板编译
        replace(fragment)
    
        vm.$el.appendChild(fragment)
    
        function replace(node){
            // 定义匹配插值表达式的正则
            const regMustache = /\{\{\s*(\S+)\s*\}\}/
            // 证明当前的node节点是一个文本子节点,需要进行正则的替换
            if(node.nodeType===3){
                // 注意:文本子节点,也是一个DOM对象,如果要获取文本子节点的字符串内容,需要调用textContent属性获取
                const text = node.textContent
                const execResult = regMustache.exec(text)
                if(execResult){
                    const value = execResult[1].split('.').reduce((newObj,k)=>newObj[k],vm)
                    node.textContent = text.replace(regMustache,value)
    
                    new Watcher(vm,execResult[1],(newValue)=>{
                        node.textContent = text.replace(regMustache,newValue)
                    })
                }
                // 终止递归的条件
                return
            }
    
            // 判断当前的node节点是否为input输入框
            if(node.nodeType===1 && node.tagName.toUpperCase() === 'INPUT'){
                const attrs = Array.from(node.attributes)
                const findResult = attrs.find((x)=>x.name==='v-model')
                if(findResult){
                    const expStr = findResult.value
                    const value = expStr.split('.').reduce((newObj,k)=>newObj[k],vm)
                    node.value = value
                    new Watcher(vm,expStr,(newValue)=>{
                        node.value = newValue
                    })
                    // 监听文本框的input输入事件,拿到文本框最新的值,把最新的值,更新到vm上即可
                    node.addEventListener('input',(e)=>{
                        const keyArr = expStr.split('.')
                        const obj = keyArr.slice(0,keyArr.length-1).reduce((newObj,k)=>newObj[k],vm)
                        obj[keyArr[keyArr.length-1]] = e.target.value
                    })
    
                }
            }
            // 证明不是文本节点,可能是一个DOM元素,需要进行递归处理
            node.childNodes.forEach(child=>replace(child))
        }
    }
    // 收集依赖/收集订阅者
    class Dep{
        constructor(){
            // 今后,所有的watcher都要存到这个数组中
            this.subs = []
        }
        // 向 subs 数组中,添加watcher的方法
        addSub(watcher){
            this.subs.push(watcher)
        }
        // 负责通知每个watcher的方法
        notify(){
            this.subs.forEach(watcher=>watcher.update())
        }
    }
    
    class Watcher{
        constructor(vm,key,cb){
            this.vm = vm
            this.key = key
            this.cb = cb
            Dep.target = this
            key.split('.').reduce((newObj,k)=>newObj[k],vm )
            Dep.target = null
        }
        update(){
            const value = this.key.split('.').reduce((newObj,k)=>newObj[k],this.vm)
            this.cb(value)
        }
    }
    

    html文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <div id="app">
            <h3>姓名:{{name}}</h3>
            <h3>年龄:{{age}}</h3>
            <h3>info.a的值是:{{info.a}}</h3>
            <div>name的值:<input type="text" v-model="name"></div>
            <div>info.a的值:<input type="text" v-model="info.a"></div>
        </div>
        <script src="./vue.js"></script>
        <script>
            const vm = new Vue({
                el:"#app",
                data:{
                    name:'zs',
                    age:20,
                    info:{
                        a:'a1',
                        c:'c1'
                    }
                }
            })
        </script>
    </body>
    </html>
    
    
    展开全文
  • 主要介绍了Angular和Vue双向数据绑定的实现原理(重点是vue的双向绑定),非常不错,具有参考借鉴价值,感兴趣的朋友一起看看吧
  • 本文主要介绍了vue中数据绑定原理的实现,分享给大家,也给自己留个笔记,具体如下: vue中的响应式数据绑定是通过数据劫持和观察者模式来实现的。当前学习源码为vue2.0 源码关键目录 src |---core | |---...
  • 这篇文章主要介绍了Vue双向数据绑定实现原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一、概述   vuejs是采取数据劫持结合发布者-订阅者模式的...
  • 到这里我们的整个的模板编译也已经完成,不过这里我们并没有实现过多的指令,我们只是简单的实现了 v-model 指令,本意是通过这篇文章让大家熟悉与认识 Vue双向绑定原理,并不是去创造一个新的 MVVM 实例。...

    在线使用-线上测试-源码

    //代码:
    <div id="app">
        <input v-model="name" type="text">
        <h1>{{ name }}</h1>
    </div>
    <script src="./js/observer.js"></script>
    <script src="./js/watcher.js"></script>
    <script src="./js/compile.js"></script>
    <script src="./js/index.js"></script>
    <script>
    const vm = new Mvue({
        el: "#app",
    	data: {
    		name: "我是摩登"
    	}
    });
    </script>

    数据绑定

    在正式开始之前我们先来说说数据绑定的事情,数据绑定我的理解就是让数据M(model)展示到 视图V(view)上。我们常见的架构模式有 MVC、MVP、MVVM模式,目前前端框架基本上都是采用 MVVM 模式实现双向绑定,Vue 自然也不例外。但是各个框架实现双向绑定的方法略有所不同,目前大概有三种实现方式。

    • 发布订阅模式
    • Angular 的脏查机制
    • 数据劫持

    而 Vue2.0 则采用的是数据劫持与发布订阅相结合的方式实现双向绑定,数据劫持主要通过 Object.defineProperty 来实现。

    Object.defineProperty

    这篇文章我们不详细讨论 Object.defineProperty 的用法,我们主要看看它的存储属性 get 与 set。我们来看看通过它设置的对象属性之后有何变化。

    var people = {
        name: "Modeng",
        age: 18
    }
    people.age; //18
    people.age = 20;

    上述代码就是普通的获取/设置对象的属性,看不到什么奇怪的变化。

    var modeng = {}
    var age;
    Object.defineProperty(modeng, 'age', {
      get: function () {
        console.log("获取年龄");
        return age;
      },
      set: function (newVal) {
        console.log("设置年龄");
        age = newVal;
      }
    });
    modeng.age = 18;
    console.log(modeng.age);

    你会发现通过上述操作之后,我们访问 age 属性时会自动执行 get 函数,设置 age 属性时,会自动执行 set 函数,这就给我们的双向绑定提供了非常大的方便。

    剖析

    我们知道 MVVM 模式在于数据与视图的保持同步,意思是说数据改变时会自动更新视图,视图发生变化时会更新数据。

    所以我们需要做的就是如何检测到数据的变化然后通知我们去更新视图,如何检测到视图的变化然后去更新数据。检测视图这个比较简单,无非就是我们利用事件的监听即可。

    那么如何才能知道数据属性发生变化呢?这个就是利用我们上面说到的 Object.defineProperty 当我们的属性发生变化时,它会自动触发 set 函数从而能够通知我们去更新视图。

    实现

    通过上面的描述与分析我们知道 Vue 是通过数据劫持结合发布订阅模式来实现双向绑定的。我们也知道数据劫持是通过 Object.defineProperty 方法,当我们知道这些之后,我们就需要一个监听器 Observer 来监听属性的变化。得知属性发生变化之后我们需要一个 Watcher 订阅者来更新视图,我们还需要一个 compile 指令解析器,用于解析我们的节点元素的指令与初始化视图。所以我们需要如下:

    • Observer 监听器:用来监听属性的变化通知订阅者
    • Watcher 订阅者:收到属性的变化,然后更新视图
    • Compile 解析器:解析指令,初始化模版,绑定订阅者

    监听器 Observer

    监听器的作用就是去监听数据的每一个属性,我们上面也说了使用 Object.defineProperty 方法,当我们监听到属性发生变化之后我们需要通知 Watcher 订阅者执行更新函数去更新视图,在这个过程中我们可能会有很多个订阅者 Watcher 所以我们要创建一个容器 Dep 去做一个统一的管理。

    function defineReactive(data, key, value) {
      //递归调用,监听所有属性
      observer(value);
      var dep = new Dep();
      Object.defineProperty(data, key, {
        get: function () {
          if (Dep.target) {
            dep.addSub(Dep.target);
          }
          return value;
        },
        set: function (newVal) {
          if (value !== newVal) {
            value = newVal;
            dep.notify(); //通知订阅器
          }
        }
      });
    }
    
    function observer(data) {
      if (!data || typeof data !== "object") {
        return;
      }
      Object.keys(data).forEach(key => {
        defineReactive(data, key, data[key]);
      });
    }
    
    function Dep() {
      this.subs = [];
    }
    Dep.prototype.addSub = function (sub) {
      this.subs.push(sub);
    }
    Dep.prototype.notify = function () {
      console.log('属性变化通知 Watcher 执行更新视图函数');
      this.subs.forEach(sub => {
        sub.update();
      })
    }
    Dep.target = null;

    以上我们就创建了一个监听器 Observer,我们现在可以尝试一下给一个对象添加监听然后改变属性会有何变化。

    var modeng = {
      age: 18
    }
    observer(modeng);
    modeng.age = 20;

    我们可以看到浏览器控制台打印出 “属性变化通知 Watcher 执行更新视图函数” 说明我们实现的监听器没毛病,既然监听器有了,我们就可以通知属性变化了,那肯定是需要 Watcher 的时候了。

    订阅者 Watcher

    Watcher 主要是接受属性变化的通知,然后去执行更新函数去更新视图,所以我们做的主要是有两步:

    1. 把 Watcher 添加到 Dep 容器中,这里我们用到了 监听器的 get 函数
    2. 接收到通知,执行更新函数。
    function Watcher(vm, prop, callback) {
      this.vm = vm;
      this.prop = prop;
      this.callback = callback;
      this.value = this.get();
    }
    Watcher.prototype = {
      update: function () {
        const value = this.vm.$data[this.prop];
        const oldVal = this.value;
        if (value !== oldVal) {
          this.value = value;
          this.callback(value);
        }
      },
      get: function () {
        Dep.target = this; //储存订阅器
        const value = this.vm.$data[this.prop]; //因为属性被监听,这一步会执行监听器里的 get方法
        Dep.target = null;
        return value;
      }
    }

    这一步我们把 Watcher 也给弄了出来,到这一步我们已经实现了一个简单的双向绑定了,我们可以尝试把两者结合起来看下效果。

    function Mvue(options, prop) {
    	this.$options = options;
    	this.$data = options.data;
    	this.$prop = prop;
    	this.$el = document.querySelector(options.el);
    	this.init();
    }
    Mvue.prototype.init = function () {
        observer(this.$data);
        this.$el.textContent = this.$data[this.$prop];
        new Watcher(this, this.$prop, value => {
    	    this.$el.textContent = value;
    	});
    }

    这里我们尝试利用一个实例来把数据与需要监听的属性传递进来,通过监听器监听数据,然后添加属性订阅,绑定更新函数。

    <div id="app">{{ name }}</div>
    const vm = new Mvue({
    	el: "#app",
    	data: {
    		name: "我是摩登"
    	}
    }, "name");

    我们可以看到数据已经正常的显示在页面上,那么我们在通过控制台去修改数据,发生变化后视图也会跟着修改。

    到这一步我们我们基本上已经实现了一个简单的双向绑定,但是不难发现我们这里的属性都是写死的,也没有指令模板的解析,所以下一步我们来实现一个模板解析器。

    Compile 解析器

    Compile 的主要作用一个是用来解析指令初始化模板,一个是用来添加添加订阅者,绑定更新函数。

    因为在解析 DOM 节点的过程中我们会频繁的操作 DOM, 所以我们利用文档片段(DocumentFragment)来帮助我们去解析 DOM 优化性能。

    function Compile(vm) {
      this.vm = vm;
      this.el = vm.$el;
      this.fragment = null;
      this.init();
    }
    Compile.prototype = {
      init: function () {
        this.fragment = this.nodeFragment(this.el);
      },
      nodeFragment: function (el) {
        const fragment = document.createDocumentFragment();
        let child = el.firstChild;
        //将子节点,全部移动文档片段里
        while (child) {
          fragment.appendChild(child);
          child = el.firstChild;
        }
        return fragment;
      }
    }

    然后我们就需要对整个节点和指令进行处理编译,根据不同的节点去调用不同的渲染函数,绑定更新函数,编译完成之后,再把 DOM 片段添加到页面中。

    Compile.prototype = {
      compileNode: function (fragment) {
        let childNodes = fragment.childNodes;
        [...childNodes].forEach(node => {
          let reg = /\{\{(.*)\}\}/;
          let text = node.textContent;
          if (this.isElementNode(node)) {
            this.compile(node); //渲染指令模板
          } else if (this.isTextNode(node) && reg.test(text)) {
            let prop = RegExp.$1;
            this.compileText(node, prop); //渲染 {{ }}模板
          }
    
          //递归编译子节点
          if (node.childNodes && node.childNodes.length) {
            this.compileNode(node);
          }
        });
      },
      compile: function (node) {
        let nodeAttrs = node.attributes;
        [...nodeAttrs].forEach(attr => {
          let name = attr.name;
          if (this.isDirective(name)) {
            let value = attr.value;
            if (name === "v-model") {
              this.compileModel(node, value);
            }
            node.removeAttribute(name);
          }
        });
      },
      //略
    }

    因为代码比较长如果全部贴出来会影响阅读,我们主要是讲整个过程实现的思路,文章结束我会把源码发出来,有兴趣的可以去查看全部代码

    到这里我们的整个的模板编译也已经完成,不过这里我们并没有实现过多的指令,我们只是简单的实现了 v-model 指令,本意是通过这篇文章让大家熟悉与认识 Vue 的双向绑定原理,并不是去创造一个新的 MVVM 实例。所以并没有考虑很多细节与设计。

    现在我们实现了 Observer、Watcher、Compile,接下来就是把三者给组织起来,成为一个完整的 MVVM。

    创建 Mvue

    这里我们创建一个 Mvue 的类(构造函数)用来承载 Observer、Watcher、Compile 三者。

    function Mvue(options) {
      this.$options = options;
      this.$data = options.data;
      this.$el = document.querySelector(options.el);
      this.init();
    }
    Mvue.prototype.init = function () {
      observer(this.$data);
      new Compile(this);
    }

    然后我们就去测试一下结果,看看我们实现的 Mvue 是不是真的可以运行。

    <div id="app">
        <h1>{{ name }}</h1>
    </div>
    <script src="./js/observer.js"></script>
    <script src="./js/watcher.js"></script>
    <script src="./js/compile.js"></script>
    <script src="./js/index.js"></script>
    <script>
    	const vm = new Mvue({
    		el: "#app",
    		data: {
    			name: "完全没问题,看起来是不是很酷!"
    		}
    	});
    </script>

    我们尝试去修改数据,也完全没问题,但是有个问题就是我们修改数据时时通过 vm.$data.name 去修改数据,而不是想 Vue 中直接用 vm.name 就可以去修改,那这个是怎么做到的呢?其实很简单,Vue 做了一步数据代理操作。

    数据代理

    我们来改造下 Mvue 添加数据代理功能,我们也是利用 Object.defineProperty 方法进行一步中间的转换操作,间接的去访问。

    function Mvue(options) {
      this.$options = options;
      this.$data = options.data;
      this.$el = document.querySelector(options.el);
      //数据代理
      Object.keys(this.$data).forEach(key => {
        this.proxyData(key);
      });
    
      this.init();
    }
    Mvue.prototype.init = function () {
      observer(this.$data);
      new Compile(this);
    }
    Mvue.prototype.proxyData = function (key) {
      Object.defineProperty(this, key, {
        get: function () {
          return this.$data[key]
        },
        set: function (value) {
          this.$data[key] = value;
        }
      });
    }

    到这里我们就可以像 Vue 一样去修改我们的属性了,非常完美

     

    展开全文
  • vue3双向数据绑定原理

    2021-10-12 15:44:17
    1.1 通过ref起别名,通过$refs获取实例、 绑定input事件、将值定义在响应数据rective <input type="text" ref="reftxt" @input="getValue" :value="txt"> 1.2 导入需要的钩子 reactive(响应数据)、toRefs...
  • angular,react,vue等mv*模式的框架都实现了数据双向绑定;angular是通过脏检查即新老数据的比较来确定哪些数据发生了变化,从而将它更新到viewvue则是通过设置数据的get和set函数来实现的,这种方式在性能上是...
  • Vue 双向数据绑定原理

    千次阅读 2020-09-16 23:13:40
    1、vue双向数据绑定原理,又称vue响应式原理,是vue的核心,双向数据绑定是通过数据劫持结合发布者订阅者模式的方式来实现的,通过Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给...
  • 本文主要介绍了vue双向数据绑定原理,文章结尾附上完整demo下载。具有一定的参考价值,下面跟着小编一起来看下吧
  • 主要介绍了vue中双向数据绑定原理与常见操作技巧,结合实例形式详细分析了vue中双向数据绑定的概念、原理、常见操作技巧与相关注意事项,需要的朋友可以参考下
  • 主要介绍了Vue实现双向绑定原理以及响应式数据的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 本篇文章主要介绍了浅谈vue中数据双向绑定的实现原理 ,主要使用v-model这个数据双向绑定,有兴趣的可以了解一下
  • 前言 虽然工作一直使用Vue作为基础库,但是对于其实现机理仅限于道听途说,这样对长期的技术发展很不利。所以最近攻读了其源码的一...Vue双向数据绑定的设计思想为观察者模式,为了方便,下文中将被观察的对象称
  • 原理Vue2.x): Vue.js它是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter;在数据变动时发布消息给订阅者,触发相应的监听回调。 我们知道,实现MVVM...
  • vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。 vue会遍历此data对象所有的属性 并使用Object.defineProperty把这些属性全部转为gettter/setter 而每个组件实例都有watcher对象 会在组件...
  • 先声明,该代码是网上一位大佬提供的,但是学习它的代码过程...该压缩文件内容是vue数据双向绑定的实现与原理解析,提供核心完整代码,并有我的代码注释,浅显易懂,但是需要es6的学习和有一定的javascript基础才行。
  • Vue数据双向绑定原理解析

    千次阅读 2020-05-16 16:03:26
    Vue数据双向绑定的原理解析 思路     所谓MVVM数据双向绑定,...    Vue 实现的双向数据绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的 setter,getter,在
  • 主要为大家详细介绍了Vue数据双向绑定底层实现原理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • let toProxy ...// effect 会执行两次 默认先执行一次 之后依赖的数据变化了 再次执行 // console.log(obj.name); } ) obj . name = 'lokka' ; console . log ( obj . name ) ;
  • vue数据双向绑定原理

    2022-04-19 15:08:03
    1、vue双向数据绑定是 通过 数据劫持 并结合 发布-订阅模式 的方法来实现的。 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变。 2、其中 数据劫持 是通过 Object....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 17,481
精华内容 6,992
关键字:

vue中双向数据绑定原理