浅谈Vue双向数据绑定的原理

2字数 1533阅读 18954

☞☞ 个人主页欢迎访问 ☜☜


大家好,我是苏日俪格,在很多次面试的时候, 面试官都会多多少少问到vue的双向数据绑定的原理是什么? 这个问题就很尴尬了, 我当初来现在的这家公司的时候初试和复试都遇到了这个问题, 不仅如此, 之前面试的几家公司也都问了这个让我值得深思又避免不了尴尬的问题, 在这两天完成了自己的任务后, 静了下心来回想一下这个问题.

在网上查找许多资料, 可能是IT界的大佬们讲的太专业太深奥了, 也可能是我真的真的是一个名副其实的理工科生, 对文字理解能力差吧, 可我还是没懂, 后来让我这个从来不爱读书的孩子在书中找到了这个黄金屋, 偶然发现了有关绑定数据属性的问题, 下面我把我的理解跟大家分享一下,本文纯属个人理解,有哪里不对的地方请在评论区指出,大家一起学习共同进步。

所谓双向数据绑定, 无非就是数据层和视图层中的数据同步, 在写入数据时视图层实时的跟着更新, 之前在网上看到大佬们是这么描述的:

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

一共分四步, 每一步都有大堆解释和一大堆的代码, 然而我真的只是一个名副其实的理工科生, 看到了文中的发布者-订阅者模式, 于是乎我去网上各种百度, 个人理解就是getter函数里面执行的任务就是watcher订阅者, 而setter函数执行的任务就是发布者; 相信很多人看过了这个也是一知半解, 下面我来解释一波:

ECMAScript中有两种属性: 数据属性和访问器属性, 数据属性一般用于存储数据数值, 访问器属性对应的是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

在修改属性的特性或者定义访问器属性的时候, 需要借助ECMAScript 5中的一个方法: Object.defineProperty(), 这个方法接收三个参数: 属性所在对象, 属性的名字, 描述符对象; 为对象定义多个属性的话, 就用函数的复数写法:Object.defineProperties();

那么通过这个ES5的方法就可以直接很简单粗暴的说明双向绑定的原理:

<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(111)
        return val;
    },
    set: function(newVal) {
        oInp.value = newVal;
        oBox.innerHTML = newVal;
    }
});
oInp.addEventListener('input', function(e) {
    obj.name = e.target.value;
});
obj.name = '苏日俪格';

那么实现数据双向绑定的核心就是利用为每一个属性都创建了订阅者的实例对象, 以便观察, getter函数里面返回一个value值,在setter函数中写入修改后的值并调用update方法更新视图的数据值, Configurable和Enumerable这两个特性描述符默认是true, 因此不用写

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]);
     });
}

根据此文章相关信息, 后来又写了一个简化版的Vue,希望可以帮助到大家,有兴趣的可以star/fork一下

如果想要仔细研究的可以参考一下他的博客或者http://www.cnblogs.com/canfoo/p/6891868.html

希望大家阅读完本文可以有所收获,因为能力有限,掌握的知识也是不够全面,欢迎大家提出来一起分享!谢谢O(∩_∩)O~

欢迎访问我的GitHub,喜欢的可以star,项目随意fork,支持转载但要下标注,同时恭候:个人博客

等一下( •́ .̫ •̀ ),我还有最后一句话:
我爱你,
他们说,
海最深邃,
干净而又透明 ,
我想 ,
是因为他们没有看到你的眼睛 ,
再见...

推荐阅读更多精彩内容