千家信息网

js怎么实现数据双向绑定

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,本篇内容主要讲解"js怎么实现数据双向绑定",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"js怎么实现数据双向绑定"吧!双向绑定:双向绑定基于MVVM模型:
千家信息网最后更新 2026年11月01日js怎么实现数据双向绑定

本篇内容主要讲解"js怎么实现数据双向绑定",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"js怎么实现数据双向绑定"吧!

双向绑定:

双向绑定基于MVVM模型:model-view-viewModel

model: 模型层,负责业务逻辑以及与数据库的交互
view:视图层,负责将数据模型与UI结合,展示到页面中
viewModel:视图模型层,作为model和view的通信桥梁

双向绑定的含义:当model数据发生变化的时候,会通知到view层,当用户修改了view层的数据的时候,会反映到模型层。

而双向数据绑定的好处在于:只关注于数据操作,DOM操作减少

Vue.js实现的原理就是采用的访问器监听,所以这里也采用访问器监听的方式实现简单的数据双向绑定。

访问器监听的实现,主要采用了javascript中原生方法:Object.defineProperty,该方法可以为某对象添加访问器属性,当访问或者给该对象属性赋值的时候,会触发访问器属性,因此利用此思路,可以在访问器属性中添加处理程序。

这里先实现一个简单的input标签的数据双向绑定过程,先大致了解一下什么是数据的双向绑定。

以上的代码中首先对Input标签对象进行获取,然后对input元素对象添加监听事件(blur),当事件被触发的时候,也就是view层发生变化的时候,就需要去通知model层去更新数据,这里的model层利用的是一个没有原型的空对象(使用空对象的原因:避免获取某属性的时候,由于原型链的存在,造成数据的误读)。

使用Object.defineProperty的方法,为该对象的指定属性添加访问器属性,当该对象的属性被修改,就会触发setter访问器,我们这里就可以为view层的数据赋值,更新view层的数据,这里的view层指的是Input标签的属性value。

看一下效果:

在文本框中输入一个数据,在控制台打印model.user可以看到数据已经影响到了model层

接着在控制台手动修改model层的数据:model.user = '9090';
此时可以看到数据文本框也被相应的进行了修改,影响到了view层

好啦,实现了最简单的只针对于文本框的数据双向绑定,我们可以从以上的案例中可以发现以下的实现逻辑:

①. 要实现view层到model的数据通信,就需要知道view层的数据变化了,以及view层的值,但是一般要获取到标签本身的值,除非有内置属性,比如:input标签的value属性,可以获得文本框的输入值

②. 利用Object.defineProperty实现model层向view层的通信,当数据被修改,就会立马触发访问器属性setter,从而可以通知使用了该属性的所有view层去更新他们的现在的数据(观察者)

③. 被绑定的数据需要是作为一个对象的属性,因为Object.defineProperty是对某一个对象的属性开启的访问器特性。

争对以上的总结,我们可以设计出类似于vue.js的数据双向绑定模式:
利用自定义指令实现view到model层的数据通信
利用Object.defineProperty实现model层到view层的数据通信。

这里的实现涉及到三个主要的函数:

  • _observer: 对数据进行处理,重写每一个属性的getter/setter

  • _compile:对自定义指令(这里只涉及了e-bind/e-click/e-model)进行解析,并在解析过程中为节点绑定原生处理事件,以及实现view层到model层的绑定

  • Watcher: 作为model与view的中间桥梁,当model发生变化进一步更新view层

实现代码:

html

        双向数据绑定            

eBind.js

function EBind(options) {    this._init(options);}// 根据所给的自定义参数,进行数据双向绑定的初始化工作EBind.prototype._init = function (options) {    // options是初始化时的数据,包括el,data,method    this.$options = options;    // el是需要管理的Element对象,el:#app this.$el:id为app的Element对象    this.$el = document.querySelector(options.el);    // 数据    this.$data = options.data;    // 方法    this.$methods = options.methods;    // _binding保存着model与view的映射关系,也就是Wachter的实例,当model更新的时候,更新对应的view    this._binding = {};    // 重写 this.$data的get和set方法    this._obverse(this.$data);    // 解析指令    this._compile(this.$el);}// 该函数的作用:对所有的this.$data里面的属性进行监听,访问器监听,实现model到view层的数据通信。当model层改变的时候通知view层EBind.prototype._obverse = function (currentObj, completeKey) {    // 保存上下文    var _this = this;    // currentObj就是需要重写get/set的对象,Object.keys获取该对象的属性,得到的是一个数组    // 对该数组进行遍历    Object.keys(currentObj).forEach(function (key) {        // 当且仅当对象自身的属性才监听        if (currentObj.hasOwnProperty(key)) {            // 如果是某一对象的属性,则需要以person.age的形式保存            var completeTempKey = completeKey ? completeKey + '.' + key : key;            // 建立需要监测属性的关联            _this._binding[completeTempKey] = {                _directives: [] // 存储所有使用该数据的地方            };            // 获取到当前属性的值            var value = currentObj[key];            // 如果值是对象,则遍历处理,对每个对象属性都完全监测            if (typeof value == 'object') {                _this._obverse(value, completeTempKey);            }            var binding = _this._binding[completeTempKey];            // 修改对象的每一个属性的get和set,在get和set中添加处理事件            Object.defineProperty(currentObj, key, {                enumerable: true,                configurable: true, // 避免默认为false                get() {                    return value;                },                set(v) {                    // value保存当前属性的值                    if (value != v) {                        // 如果数据被修改,则需要通知每一个使用该数据的地方进行更新数据,也即:model通知view层,Watcher类作为中间层去完成该操作(通知操作)                        value = v;                        binding._directives.forEach(function (item) {                            item.update();                        })                    }                }            })        }    })}// 该函数的作用是:对自定义指令进行编译,为其添加原生监听事件,实现view到model层的数据通信,也即当view层数据变化之后通知model层数据更新// 实现原理:通过托管的element对象:this.$el,获取到所有的子节点,遍历所有的子节点,查看其是否有自定义属性,如果有指定含义的自定义属性// 比如说:e-bind/e-model/e-click则根据节点上添加的自定义属性的不同为其添加监听事件// e-click添加原生的onclick事件,这里主要注意点就是:需要将this.$method中指定方法的上下文this改为this.$data// e-model为绑定的数据更新,这里只支持input,textarea标签,原因:采用标签自带的value属性实现的view到model层的数据通信// e-bindEBind.prototype._compile = function (root) {    // 保存执行上下文    var _this = this;    // 获取到托管节点元素的所有子节点,只包括元素节点    var nodes = root.children;    for (let i = 0; i < nodes.length; i++) {        // 获取到子节点/按顺序        var node = nodes[i];        // 如果当前节点有子节点,则继续逐层处理子节点        if (node.children.length) {            this._compile(node);        }        // 如果当前节点绑定了e-click属性,则需要为当前节点绑定onclick事件        if (node.hasAttribute('e-click')) {            // hasAttribute可以获取到自定义属性            node.addEventListener('click',(function () {                // 获取到当前节点的属性值,也就是方法                var attrVal = node.getAttribute('e-click');                // 由于绑定的方法里面的数据要使用data里面的数据,所以需要将执行的函数的上下文,也就是this改为this.$data                // 而使用bind,不使用call/apply的原因是onclick方法需要触发之后才会执行,而不是立马执行                return _this.$methods[attrVal].bind(_this.$data);            })())        }        // 只对input和textarea标签元素可以施行双向绑定,原因:利用这两个标签的内置的value属性实现双向绑定        if (node.hasAttribute('e-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {            // 给element对象添加监听input事件 ,第二个参数是一个立即执行函数,获取到节点的索引值,执行函数内部代码,返回事件处理            node.addEventListener('input', (function (index) {                // 获取到当前节点的属性值,也就是方法                var attrVal = node.getAttribute('e-model');                // 给当前element对象添加model到view层的映射                _this._binding[attrVal]._directives.push(new Watcher({                    name: 'input',                    el: node,                    eb: _this,                    exp: attrVal,                    attr: 'value'                }))                // 如果input标签value值改变,此时需要更新model层的数据,也就是view层到model层的改变                return function () {                    // 获取到绑定的属性,以.为分隔符,如果只是一个值,就直接获取当前值,如果是个对象(obj.key)的形式,则绑定的其实obj对象                    // 中的key的值,此时就需要获取到key,并对key进行赋值为已改变的input标签的value值                    var keys = attrVal.split('.');                    // 获取上一步得到的属性的集合中最后一个属性(最后一个属性才是真正被绑定的值)                    var lastKey = keys[keys.length - 1];                    // 获得真正被绑定的值的父对象                    // 因为如果是对象,比如:obj.key.val,则需要找到key的引用,因为这里要改变的是val                    // 通过引用key 从而改变val的值,但是如果直接获取到的val的引用,val是数值型存储,赋值给另一个变量的时候,其实是新开辟的一个空间                    // 并不能直接改变model层也就是this.$data里面的数据,而引用数据存储的话,赋值给另一个变量,另一个变量的修改,会影响原来的引用的数据                    // 所以这里需要找到真正被绑定值的父对象,也就是obj.key里面的obj值                    var model = keys.reduce(function (value, key) {                        // 如果不是对象,则直接返回属性value                        if (typeof value[key] !== 'object') {                            return value;                        }                        return value[key];                        // 这里使用model层作为起始值,原因:keys里面记录的是this.$data里面的属性,所以需要从父对象this.$data出发去找目标属性                    }, _this.$data);                    // model也就是之前说得父对象,obj.key中的obj,而lastkey也就是真正被绑定的属性,找到了之后就需要对其更新为节点的值啦。                    // 这里的model层被修改会触发_observe里面的访问器属性setter,所以如果其他地方也使用了这个属性的话,也会相应的发生改变哦                    model[lastKey] = nodes[index].value;                }            })(i))        }        // 对节点上绑定e-bind,为其添加model到view的映射即可,原因:e-bind实现的是model到view的数据通信,而在this._observer中        // 已经通过definePrototype实现了,所以这里只需要添加通信,便于在_oberver中实现。        if(node.hasAttribute('e-bind')) {            var attrVal = node.getAttribute('e-bind');            _this._binding[attrVal]._directives.push(new Watcher({                name: 'text',                el: node,                eb: _this,                exp: attrVal,                attr: 'innerHTML'            }))        }    }}/** * options 属性: * name: 节点名称:文本节点:text, 输入框:input * el: 指令对应的DOM元素 * eb: 指令对应的EBind实例 * exp: 指令对应的值:e-bind="test";test就是指令对应的值 * attr: 绑定的属性值, 比如:e-bind绑定的属性,其实会反应到innerHTML中,v-model绑定的标签会反应到value中 */function Watcher(options) {    this.$options = options;    this.update();}Watcher.prototype.update = function () {    // 保存上下文    var _this = this;    // 获取到被绑定的对象    var keys = this.$options.exp.split('.');    // 获取到DOM对象上要改变的属性,对其进行更改    this.$options.el[this.$options.attr] = keys.reduce(function (value, key) {        return value[key];    }, _this.$options.eb.$data)}

实现效果:

到此,相信大家对"js怎么实现数据双向绑定"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

数据 属性 对象 节点 双向 标签 方法 时候 更新 监听 也就是 事件 通信 变化 指令 数据通信 处理 函数 原因 上下 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 深圳市人人互联网科技有限公司 北京路客互联网科技有限公司 为想互联网科技怎么样 科技平台互联网 互联网科技公司的优势 嘟嘟瓜互联网科技 狮子座互联网科技培训 互联网科技论坛 哈尔滨秒房互联网科技有限公司 海南科技产业互联网平台 深圳前海聚择互联网科技有限公司 成都互联网科技公司工作好不好 互联网科技和信息 深圳育鹏互联网科技有限公司招聘 如何创新互联网科技 南通创西澳互联网科技有限公司 吉林省码上互联网科技电话 科技互联网行业投资研究 广州智享互联网科技 张家口科技产业互联网平台 云南咖啡 湖北九亚互联网络科技有限公司 互联网金融与金融科技 喔噻互联网科技 佩信高科技互联网 华夏恒生互联网科技013171 互联网金融运营模式中的金融科技 科技互联网公司两年变化 互联网领先科技成果宣传视频 东风日产互联网科技公司叫什么
0