vue 数据双向绑定实现

痛苦之源
• 阅读 2202

vue 数据双向绑定实现

之前每件事都差不多,直到现在才发现差很多。

现在才发现理清一件事的原委是多么快乐的一件事,我们共同勉励。

纸上得来终觉浅,绝知此事要躬行

懒得扯淡,直接正题

PS: 文章略长。

本文分3个部分来介绍:

  1. model -> view
  2. 编译器
  3. view -> model

vue 数据双向绑定实现

model -> view

其基于 订阅者-发布者模式,简单的讲就是订阅者订阅数据,一旦订阅的数据变更过后,更新绑定的view视图。

这里有明确的分工,分别是监听器、发布器和订阅器,这3者相互协作,各司其职。

  • 监听器:负责 创建 发布器;发布器 添加 订阅器发布器 通知 订阅器
  • 发布器:负责 添加 订阅器;通知 订阅器
  • 订阅器:负责 更新视图

目标效果

过2s数据更改,更新到视图

监听器

顾名思义,监听器,监听器,监听的就是数据的变化

创建 发布器;发布器 添加 订阅器发布器 通知 订阅器

需要解决订阅者的添加和发布器通知订阅器的时机

Object.difineProperty为我们提供了方便,其语法如下:

var obj = {};

Object.defineProperty(obj, 'a', {
    enumerable: true,
    configurable: true,
    value: 'a'
});

console.log(obj.a); // 输出a

definePropery 除了可以定义数值以外,还可以定义 get 和 set 访问器,如下:

var obj = {};
var value = 'a';

Object.defineProperty(obj, 'a', {
    enumerable: true,
    configurable: true,
    get: function () {
        console.log('获取 key 为 "a" 的值');
        return value;
    },
    set: function (val) {
        console.log('修改 key 为 "a" 的值');
        value = val;
    }    
});

console.log(obj.a);
obj.a = 'b';
console.log(obj.a);

运行结果如下所示:

vue 数据双向绑定实现

数据的变化无非就是读和写,由此,我们可以得出 订阅器的添加和发布器通知订阅器的时机,就是属性值的获取和重置。

具体代码

function observe(data) {
    if (!data || typeof data !== 'object') {
        return ;
    }

    Object.keys(data).forEach((val, key) => {
        defineReactive(data, val, data[val]);
    })
}

function defineReactive(data, key, val) {
    observe(val);
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get: function () {
            console.log(`获取参数${key},值为${val}`);
            return val;
        },
        set: function (newValue) {
            console.log(`修改参数${key},变为${val}`);
            val = newValue;
        }
    });
}

var obj = {
    type: 'object',
    data: {
        a: 'a'
    }
}

observe(obj);

发布器

添加 订阅器;通知 订阅器
function Dep () {
    this.subs = [];
}

Dep.prototype.addSub = function(sub){
    this.subs.push(sub);
};

Dep.prototype.notify = function(){
    this.subs.forEach(function(sub, index) {
        sub.update();
    });
};

发布器代码写好了,我们再重新修改一下监听器代码。主要修改点为:添加订阅器和发布器通知订阅器

 function defineReactive(data, key, val) {
+    var dep = new Dep();
     observe(val);
     Object.defineProperty(data, key, {
         enumerable: true,
         configurable: true,
         get: function () {
             console.log(`获取参数${key},值为${val}`);
+            dep.addSub(<watch>);
             return val;
         },
         set: function (newValue) {
+            dep.notify();
             console.log(`修改参数${key},变为${val}`);
             val = newValue;
         }

订阅器

更新视图
function Watcher (vm, key, cb) {
    this.vm = vm;
    this.key = key;
    this.cb = cb;
    this.value = this.get();
}

Watcher.prototype.update = function(){
    var oldValue = this.value;
    var value = this.vm.data[this.key];

    if (oldValue !== value) {
        this.value = value;
        this.cb.call(this, this.vm.data[this.key]);
    }
};

Watcher.prototype.get = function(){
    Dep.target = this;
    var value = this.vm.data[this.key];
    Dep.target = null;

    return value;
};

订阅器代码写好了,我们再重新修改一下监听器代码。主要修改点为:如何添加订阅器

         enumerable: true,
         configurable: true,
         get: function () {
-            dep.addSub(<watch>);
+            if (Dep.target) {
+                dep.addSub(Dep.target);
+            }
             return val;
         },
         set: function (newValue) {

vue 实现

function Vue (data, dom, key) {
    this.data = data;
    observe(data);
    dom.innerHTML = data[key];

    var watcher = new Watcher(this, key, function (name) {
        dom.innerHTML = name;
    });
}

实际使用

<div id="root"></div>
        var vm = new Vue({
            name: 'mumu'
        }, document.getElementById('root'), 'name');

        setTimeout(() => {
            vm.data.name = 'yiyi';
        }, 2000);

还有一点,通常数据的变更是直接使用vm.name,而非vm.data.name,其实也很简单,直接使用代理,vm.name读取和写都代理到vm.data.name上即可。

+    var self = this;
+    Object.keys(this.data).forEach(function(property, index) {
+        self.proxyProperty(property);
+    });
+
     var watcher = new Watcher(this, key, function (name) {
         dom.innerHTML = name;
     });
-}
+}
+
+Vue.prototype.proxyProperty = function(property){
+    Object.defineProperty(this, property, {
+        configurable: true,
+        get: function () {
+            return this.data[property];
+        },
+        set: function (value) {
+            this.data[property] = value;
+        }
+    });
+};

详细代码参考github项目

$ git clone https://github.com/doudounannan/vue-like.git
$ cd vue-like
$ git checkout model2view

编译器

上面的实例看起来,有点问题,我们是写死监听的数据,然后修改dom上的innerHTML,实际中,肯定不会这样,需要在dom中绑定数据,然后动态监听数据变化。

首先需要明确编译器 有哪些工作需要做

  • 解析 dom 结构,对 text 节点中绑定数据做一次模板字符串到数据的替换
  • 数据绑定时,添加对应的 订阅器

目标效果

视图绑定数据,2s后数据更新,更新到视图

实现

对dom结构的解析这里使用 文档片段,其dom操作性能优于其他。

function Compile (options, vm) {
    this.compile = this;
    this.vm = vm;
    this.domEle = document.getElementById(options.el);
    this.fragment = this.createElement(this.domEle);

    this.compileElement(this.fragment);
    this.viewRefresh();
}
Compile.prototype.createElement = function (ele) {
    var fragment = document.createDocumentFragment();
    var child = ele.firstChild;

    while (child) {
        fragment.appendChild(child);
        child = ele.firstChild;
    }

    return fragment;
}

Compile.prototype.compileElement = function (el) {
    var childNodes = el.childNodes;

    [].slice.apply(childNodes).forEach((node) => {
        var reg = /\{\{(\w+)\}\}/;
        var text = node.textContent;

        if (reg.test(text)) {
            this.compileText(node, reg.exec(text)[1]);
        }

        if (node.childNodes && node.childNodes.length > 0) {
            this.compileElement(node);
        }
    });
}

Compile.prototype.compileText = function (node, key) {
    var text = this.vm[key];
    var self = this;

    self.updateText(node, text);

    new Watcher(this.vm, key, function (newText) {
        self.updateText(node, newText);
    });
}

Compile.prototype.updateText = function (node, text) {
    node.textContent = text;
}

Compile.prototype.viewRefresh = function(){
    this.domEle.appendChild(this.fragment);
};

详细代码参考github项目

$ git clone https://github.com/doudounannan/vue-like.git
$ cd vue-like
$ git checkout compile

view -> model

这个比较简单,在compile 解析时,判断是否是元素节点,如果元素节点中包含指令v-model,从中读取监听的数据属性,再从 model中读取,除此以外还要绑定一个input事件,用于view -> model

目标效果

vue 数据双向绑定实现

详细代码参考github项目

$ git clone https://github.com/doudounannan/vue-like.git
$ cd vue-like
$ git checkout view2model

事件

目标效果

vue 数据双向绑定实现

详细代码参考github项目

$ git clone https://github.com/doudounannan/vue-like.git
$ cd vue-like
$ git checkout event

生命周期

比如说创建、初始化、更新、销毁等。

详细代码参考github项目

$ git clone https://github.com/doudounannan/vue-like.git
$ cd vue-like
$ git checkout lifecircle
点赞
收藏
评论区
推荐文章
02-Vue入门之数据绑定
02Vue入门之数据绑定02Vue入门之数据绑定2.1.什么是双向绑定?Vue框架很核心的功能就是双向的数据绑定。双向是指:HTML标签数据绑定到Vue对象,另外反方向数
九旬 九旬
4年前
前端培训-Vue专题之Vue基础
简介特点:MVVM框架,双向绑定,数据驱动,单页面,组件化。区别Vue和jQuery的区别:不直接操作DOM,而是操作数据。案例:HelloWorld你好,世界HTML代码:xml<h1msg</h1jQuery实现javascript$("h1").text("你好,世界");Vue实现javascriptthis.msg'你好,世界'
Easter79 Easter79
4年前
Vue 组件实现数据双向绑定(el
一、组件vmodel值通过props传入,必须定义为value!(https://oscimg.oschina.net/oscnet/66e6fa03cb5d23a24fd7d45e5e782b125ec.png)二、将传入的value在data中重新定义赋值,以便子组件改变值(子组件中不能直接修改props
Stella981 Stella981
4年前
Chrome接口请求一直是pending状态,但接口实际上是正常的
<divid"cnblogs\_post\_body"class"blogpostbody"<h21.现象</h2<p个别机器突然出现Chrome访问我司产品异常,本该通过接口获取的数据没有呈现,之前都是好好的,而且其他机器同样用同版本Chrome访问正常。</p<p出现问题的机器重装Chrome问题依然存在,直到重装了操作系统才恢
Wesley13 Wesley13
4年前
Java中Class对象详解
<divclass"htmledit\_views"id"content\_views"<phttps://blog.csdn.net/mcryeasy/article/details/52344729<br</p<p待优化整理总结</p<p</p<h1style"padding:0px;fontfamily:'apple
Stella981 Stella981
4年前
IE iframe cookie问题(p3p)
前段时间碰到一个问题,就是在IE下,使用iFrame嵌入页面时,该页面的会话级别的cookie无法写入,导致服务端始终无法获取JSESSIONID,每次都是产生一个新的,使得Session无法使用。只需要设置P3PHTTPHeader,在隐含iframe里面跨域设置cookie就可以成功。ASP直接在头部加了头部申明,测试有效。
Wesley13 Wesley13
4年前
P2P技术详解(三):P2P中的NAT穿越(打洞)方案详解(进阶分析篇)
1、引言接本系列的上一篇《P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解(基本原理篇)(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Flinks.jianshu.com%2Fgo%3Fto%3Dhttp%253A%252F%252Fwww.52im
Wesley13 Wesley13
4年前
P2P技术揭秘.P2P网络技术原理与典型系统开发
Modular.Java(2009.06)\.Craig.Walls.文字版.pdf:http://www.t00y.com/file/59501950(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fwww.t00y.com%2Ffile%2F59501950)\More.E
Wesley13 Wesley13
4年前
Java 9版本之后Base64Encoder和Base64Decoder无法继续使用解决办法
<divclass"htmledit\_views"id"content\_views"<p在项目开发过程中,因为重装系统,安装了Java10版本,发现sun.misc.Base64Encoder和sun.misc.Base64Decoder无法使用。</p<p<br</p<p<strong原因:</strong</p<p查看
Stella981 Stella981
4年前
JFinal使用笔记2
大部分步骤按cf官方的教程就可以了。遇到的问题如下:1、使用C3p0Plugin配置数据库连接,代码如下//配置C3p0数据库连接池插件//C3p0Pluginc3p0PluginnewC3p0Plugin(getProperty("jdbcUrl"),getProperty("user"),getP
手牵手带你实现mini-vue | 京东云技术团队
Vue的双向数据绑定实现原理是什么样的,如果让我们自己去实现一个这样的双向数据绑定要怎么做呢,本文就与大家分享一下Vue的绑定原理及其简单实现