vue 双向数据绑定的实现学习(二)- 监听器的实现

废话:上一篇https://www.cnblogs.com/adouwt/p/9928278.html

提到了vue实现的基本实现原理:Object.defineProperty() -数据劫持 和 发布订阅者模式(观察者),下面讲的就是数据劫持在代码中的具体实现。

1.先看如何调用

  

new一个对象,传入我们的参数,这个Myvue ,做了啥?

  

上面看到了在实例化一个Myvue 对象的时候,会执行init方法, init 方法做了两个事,调用了observer 方法,和 实例化调用了 compile 方法。 到这里我们就明白了,实例化一个Myvue后,我们要做的就是监听数据变化和编译模板 。

上面Object.key() 方法,实例化时传入的data里面对应的变量缓存到 Myvue 对象的 $prop上,这样方便在后续处理数据。怎么个方便法呢!...

2.observer 的实现

  observer ,模式里面的角色定位 他是一个发布者,也可以理解为是一个观察者

function observer (data) {
    if(!data || typeof data !== ‘object‘) {
        return;
    }
    Object.keys(data).forEach(key => {
        // 对每个属性监听处理
        defineReactive(data, key, data[key]);
    })
}

  defineReactive

function defineReactive (data,key,value) {
    // 每次访问/修改属性的时候 实例化一个调度中心Dep
    var dep = new Dep();
    Object.defineProperty(data,key,{
        get: function() {
           // 添加到watcher 的Dep 调度中心
            if (Dep.target) { // Dep.target 是个什么鬼? 转到watcher.js 它是某个订阅者 watcher
                dep.addSub(Dep.target);  //这个代码段的意思: 如果有订阅者(访问/修改属性的时候) 就将这个订阅者统一放进 Dep 调度中心中
            }
            // console.log(`${key}属性被访问了`)
            return value
        },
        set: function (newValue) {
            if (value != newValue) {
                // console.log(`${key}属性被重置了`)
                value = newValue
                dep.notify(); //我这里有做改动了,通知调度中心的notify方法
            }
        }
    })
    // 递归调用,observe 这个value
    observer(value)
}

  Dep: 这里是所有订阅者的一个调度中心,它不是直接监听 发布者的信息,发布者将要发布的信息 发布到 一个中介、调度中心(Dep),由这个Dep 来调度信息给哪个订阅者(Watcher)

// 统一管理watcher订阅者的Dep (调度中心)  Dispatch center
function Dep () {
    // 所有的watcher 放进这里统一管理
    this.subs = []
}
Dep.target = null;
// 通知视图更新dom的 notify的方法
Dep.prototype.notify   = function () {
    // this.subs 是上面订阅器watcher 的集合
    this.subs.forEach(sub => {
        // sub 是某个Watcher 具体调用某个Watcher的update 方法
        sub.update()
    })
}

// 添加订阅者的方法
Dep.prototype.addSub  = function (sub) {
    this.subs.push(sub)
}

3.订阅器Watcher

// 具体的订阅器Watcher
// 传入一个vue 的实例, 监听的属性, 以及处理的回调函数
function Watcher (vm,prop,callback) {
    this.vm  =  vm;
    this.$prop = prop;
    this.value = this.get();
    this.callback = callback; // 具体watcher所具有的方法,不同的watcher 不同的回调函数,处理不同的业务逻辑
 }
// 添加watcher 获得属性的get 方法,当有属性访问/设置 的时候,就产生订阅者 将这个订阅者放进调度中心
Watcher.prototype.get = function () {
    Dep.target = this;
    // 获得属性值
    const value = this.vm.$data[this.$prop];
    return value
}
// 添加watcher的更新视图的方法
Watcher.prototype.update = function () {
    // 当属性值有变化的时候,执行方法,更新试图
    const value = this.vm.$data[this.$prop];
    const oldValue = this.value;
    // update 执行的时候,先获取 vm 中data实时更新的属性值,this.value 是vm data中之前的老值
    if (oldValue != value) {
        // console.log(‘人家通知了,我要改变了‘)
        // 把刚刚获取的更新值赋给之前vm data 中的值
        this.value =  value
        // 执行回调函数 具体怎么处理这个,看实际调用时候 callback 的处理
        this.callback(this.value)
    }
}

4.模板编译

(为了直接看到页面数据变化的效果,在模板编译的核心数据处理上做了dom 操作,下一篇将讲模板编译的一些细节处理)

// dom模板编译 vm 就是我们最上面的Myvue 对象
function Compile (vm) {
    this.vm = vm;
    this.$el = vm.el;
    // this.data = vm.data;
    this.fragment = null; // 用作后面模板引擎 创建文档片段
    this.init()
}

Compile.prototype = {
    // init 方法简单处理,直接做dom 操作,后面会用详细的模板引擎的学习
    init: function () {
        let value = this.vm.$data.name // 初始化获取到的值 放进dom节点中
        document.querySelector(‘.form-control‘).value = value;
        document.querySelector(‘.template‘).textContent  = value
        // 通知订阅者更新dom
        new Watcher(this.vm,this.vm.$prop, (value) => {
            document.querySelector(‘.form-control‘).value = value;
            document.querySelector(‘.template‘).textContent  = value
        })
        document.querySelector(‘.form-control‘).addEventListener(‘input‘,(e) => {
            let targetValue = e.target.value
            if(value !== targetValue) {
                this.vm.$data.name = e.target.value // 将修改的值 更新到 vm的data中
                document.querySelector(‘.form-control‘).value = targetValue; // 更新dom 节点
                document.querySelector(‘.template‘).textContent  = targetValue
            }

        },false)
    }
}

这样就可以看到 在表单中,数据的双向绑定了。

未完待续,错误之处,敬请指出,共同进步!

下一篇 vue 双向数据绑定的实现学习(三)- 模板编译

附:演示代码:

js:

function Myvue (options) {
    this.$options = options
    this.$el = document.querySelector(options.el);
    this.$data = options.data;
    Object.keys(this.$data).forEach(key => {
        this.$prop = key;
    })
    this.init()
}
Myvue.prototype.init = function () {
    // 监听数据变化
    observer(this.$data);
            // 获得值
            // let value = this.$data[this.$prop];
            // 不经过模板编译直接 通知订阅者更新dom
            // new Watcher(this,this.$prop,value => {
            //     console.log(`watcher ${this.$prop}的改动,要有动静了`)
            //     this.$el.textContent = value
            // })
    //通知模板编译来执行页面上模板变量替换
    new Compile(this)
}

function observer (data) {
    if(!data || typeof data !== ‘object‘) {
        return;
    }
    Object.keys(data).forEach(key => {
        // 对每个属性监听处理
        defineReactive(data, key, data[key]);
    })
}

function defineReactive (data,key,value) {
    // 每次访问/修改属性的时候 实例化一个调度中心Dep
    var dep = new Dep();
    Object.defineProperty(data,key,{
        get: function() {
           // 添加到watcher 的Dep 调度中心
            if (Dep.target) { // Dep.target 是个什么鬼? 转到watcher.js 它是某个订阅者 watcher
                dep.addSub(Dep.target);  //这个代码段的意思: 如果有订阅者(访问/修改属性的时候) 就将这个订阅者统一放进 Dep 调度中心中
            }
            // console.log(`${key}属性被访问了`)
            return value
        },
        set: function (newValue) {
            if (value != newValue) {
                // console.log(`${key}属性被重置了`)
                value = newValue
                dep.notify(); //我这里有做改动了,通知调度中心的notify方法
            }
        }
    })
    // 递归调用,observe 这个value
    observer(value)
}

// 统一管理watcher订阅者的Dep (调度中心)  Dispatch center
function Dep () {
    // 所有的watcher 放进这里统一管理
    this.subs = []
}
Dep.target = null;
// 通知视图更新dom的 notify的方法
Dep.prototype.notify   = function () {
    // this.subs 是上面订阅器watcher 的集合
    this.subs.forEach(sub => {
        // sub 是某个Watcher 具体调用某个Watcher的update 方法
        sub.update()
    })
}

// 添加订阅者的方法
Dep.prototype.addSub  = function (sub) {
    this.subs.push(sub)
}

// 具体的订阅器Watcher
// 传入一个vue 的示例, 监听的属性, 以及处理的回调函数
function Watcher (vm,prop,callback) {
    this.vm  =  vm;
    this.$prop = prop;
    this.value = this.get();
    this.callback = callback; // 具体watcher所具有的方法,不同的watcher 不同的回调函数,处理不同的业务逻辑
 }
// 添加watcher 获得属性的get 方法,当有属性访问/设置 的时候,就产生订阅者 将这个订阅者放进调度中心
Watcher.prototype.get = function () {
    Dep.target = this;
    // 获得属性值
    const value = this.vm.$data[this.$prop];
    return value
}
// 添加watcher的更新视图的方法
Watcher.prototype.update = function () {
    // 当属性值有变化的时候,执行方法,更新试图
    const value = this.vm.$data[this.$prop];
    const oldValue = this.value;
    // update 执行的时候,先获取 vm 中data实时更新的属性值,this.value 是vm data中之前的老值
    if (oldValue != value) {
        // console.log(‘人家通知了,我要改变了‘)
        // 把刚刚获取的更新值赋给之前vm data 中的值
        this.value =  value
        // 执行回调函数 具体怎么处理这个,看实际调用时候 callback 的处理
        this.callback(this.value)
    }
}

// dom模板编译 vm 就是我们最上面的Myvue 对象
function Compile (vm) {
    this.vm = vm;
    this.$el = vm.el;
    // this.data = vm.data;
    this.fragment = null; // 用作后面模板引擎 创建文档片段
    this.init()
}

Compile.prototype = {
    // init 方法简单处理,直接做dom 操作,后面会用详细的模板引擎的学习
    init: function () {
        let value = this.vm.$data.name // 初始化获取到的值 放进dom节点中
        document.querySelector(‘.form-control‘).value = value;
        document.querySelector(‘.template‘).textContent  = value
        // 通知订阅者更新dom
        new Watcher(this.vm,this.vm.$prop, (value) => {
            document.querySelector(‘.form-control‘).value = value;
            document.querySelector(‘.template‘).textContent  = value
        })
        document.querySelector(‘.form-control‘).addEventListener(‘input‘,(e) => {
            let targetValue = e.target.value
            if(value !== targetValue) {
                this.vm.$data.name = e.target.value // 将修改的值 更新到 vm的data中
                document.querySelector(‘.form-control‘).value = targetValue; // 更新dom 节点
                document.querySelector(‘.template‘).textContent  = targetValue
            }

        },false)
    }
}

html:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Vue双向绑定原理及实现</title>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
        crossorigin="anonymous">
        <style>
            #app {
                margin: 20px auto;
                width: 400px;
                padding: 50px;
                text-align: center;
                border: 2px solid #ddd;
            }
        </style>
    </head>

    <body>
        <div id="app">
            <input class="form-control" v-model="name" type="text">
            <h1 class="template">{{name}}</h1>
        </div>
        <script src="./js/index1.js"></script>
        <script>
            const vm = new Myvue({
                el: "#app",
                data: {
                    name: "vue 双向数据绑定test1"
                }
            });
        </script>
    </body>
</html>

原文地址:https://www.cnblogs.com/adouwt/p/10039900.html

时间: 2024-11-05 21:49:05

vue 双向数据绑定的实现学习(二)- 监听器的实现的相关文章

angular和vue双向数据绑定

angular和vue双向数据绑定的原理(重点是vue的双向绑定) 我在整理javascript高级程序设计的笔记的时候看到面向对象设计那章,讲到对象属性分为数据属性和访问器属性,我们平时用的js对象90%以上都只是用到数据属性;我们向来讲解下数据属性和访问器属性到底是什么? 数据属性:数据属性包含一个数据值的位置,在这个位置可以读取和写入值. 访问器属性:访问器属性不包含数据值;他们包含一对getter和setter函数在读取访问器属性时,会调用getter函数,这个函数负责返回有效的值,在写

React 事件对象、键盘事件、表单事件、ref获取dom节点、react实现类似Vue双向数据绑定

1.案例实现代码 import React, { Component } from 'react'; /** * 事件对象.键盘事件.表单事件.ref获取dom节点.react实现类似Vue双向数据绑定 * 事件对象: 在触发DOM上的某个事件时,会产生一个事件对象event,这个对象包含着所有与事件有关的信息 * 表单事件: 获取表单的值 * 1.监听表单的改变事件 ---onChange * 2.在改变的事件里面获取表单输入的值 ---event * 3.把表单输入的值赋值给username

Vue双向数据绑定简易实现

一.vue中的双向数据绑定主要使用到了Object.defineProperty(新版的使用Proxy实现的)对Model层的数据进行getter和setter进行劫持,修改Model层数据的时候,在setter中可以知道对那个属性进行修改了,然后修改View的数据. 二.简易版双向数据绑定 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> &

vue双向数据绑定原理探究(附demo)

昨天被导师叫去研究了一下vue的双向数据绑定原理...本来以为原理的东西都非常高深,没想到vue的双向绑定真的很好理解啊...自己动手写了一个. 传送门 双向绑定的思想 双向数据绑定的思想就是数据层与UI层的同步,数据再两者之间的任一者发生变化时都会同步更新到另一者. 双向绑定的一些方法 目前,前端实现数据双向数据绑定的方法大致有以下三种: 1.发布者-订阅者模式(backbone.js) 思路:使用自定义的data属性在HTML代码中指明绑定.所有绑定起来的JavaScript对象以及DOM元

vue双向数据绑定简易demo

1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <meta http-equiv="X-UA-Comp

Vue双向数据绑定的手动实现

class Asarua { // 传入一个对象,也就是实例化的时候的参数 constructor(options) { this.$data = options.data; // 获取挂载的dom this.$el = document.querySelector(options.el); this.$methods = options.methods; this.$options = options; // 定义一个容器,来存放所有的订阅者 this.observable = {}; thi

vue双向数据绑定

父组件引用子组件时,可以使用 v-model="params"  绑定一个变量, 在子组件中,需要使用props去接收一个value变量,即父组件绑定到的params的值 在子组件中,使用this.$emit("input" , someData); 可以触发父组件的input事件,someData是参数,它的值会赋给父组件的params的变量. 这种写法是一种简化的写法,是vue的语法糖.他也可以写成 <v-children v-bind:value=&qu

vue双向数据绑定原理

1.实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值通知订阅者2.实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,以及绑定相应的更新函数3.实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图4.mvvm入口,整合以上三者 原文地址:https://blog.51cto.com/13550695/2467904

vue 双向数据绑定原理

采用defineProperty的两个方法get.set 示例 1 <!-- 表单 --> 2 <input type="text" id="input"> 3 <!-- 展示 --> 4 <p id="desc"></p> 1 let obj = {}; 2 let temp = {};//采用临时变量代理obj 3 Object.defineProperty(obj,'name',{