前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >从单向到双向数据绑定0.前言1.单向数据(代表:react)2.观察者模式3.双绑的中间枢纽——Object.defineproperty(代表:vue)4. 脏值检测(代表:angular1)前面说

从单向到双向数据绑定0.前言1.单向数据(代表:react)2.观察者模式3.双绑的中间枢纽——Object.defineproperty(代表:vue)4. 脏值检测(代表:angular1)前面说

作者头像
lhyt
发布于 2018-10-31 07:50:14
发布于 2018-10-31 07:50:14
1.6K00
代码可运行
举报
文章被收录于专栏:lhyt前端之路lhyt前端之路
运行总次数:0
代码可运行

本文来自我的github

0.前言

用户最满意的,无非就是界面的操作能实事反应到数据。而实现这种的可以有双向数据绑定、单向数据流的形式。双向数据绑定是,ui行为改变model层的数据,model层的数据变了也能反映到ui上面。比如点击按钮,数字data+1,如果我们自己在控制台再给data+1,那么v层也能马上看见这个变化。而单向数据流就不同了,我们只有ui行为改变,data就改变并马上反馈到v层,而我们自己在控制台改变data这个值,v层居然不变(model是已经变了并没有反应),只能等到下一次ui行为改变,带上这个data结果一起处理。仅仅在V层的单向数据,真的能满足用户需求?数据很庞大的时候,双绑性能如何?其实,每一种都有每一种的适用场景,还是那句话,脱离实际场景谈性能,就是扯淡

1.单向数据(代表:react)

一般的过程:ui行为→触发action→改变数据state→mumtation再次渲染ui界面,通常就是基于view层,一个很简单的例子: html部分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input id="ipt" type="text" name="">
<p id="a"></p>
复制代码

js部分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var str = ''
a.innerHTML = str//初始化
ipt.oninput = function(){//点击触发action
	str = ipt.value//改变state状态值
	a.innerHTML = str//重新渲染
}
复制代码

但是如果在控制台获取input这个dom,在设置value,不会马上反映,只能等下一次带着这个结果一起作用。这仅仅是V->M的过程

我们再做一个超级简单的双绑: html部分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input id="ipt" type="text" name="">
<p id="a"></p>
复制代码

js部分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var $scope = {
	data:''
}
a.innerHTML = ''
setInterval(function(){
	a.innerHTML = $scope.data
},60)
ipt.oninput = function(){
	 $scope.data = ipt.value
}
复制代码

这里除了单向数据绑定,当你改变$scope.data,p标签的内容也是会马上改变。因为用了定时器,他会异步地将数据反映上去。

2.观察者模式

首先,我们先订阅事件,比如事件‘a’,回调函数是function (){console.log(1)},订阅后,如果事件‘a’被触发了,就调用回调函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function Event(){
	this.list=[],
	this.on=function(key,cb){//订阅事件
		if(!this.list[key]){
			this.list[key] = []
		}
		this.list[key].push(cb)
	},
	this.emit = function(){//触发事件
		var key = Array.prototype.shift.call(arguments)
		var e = this.list[key]
		if(!e){
			return
		}
		var args = Array.prototype.slice.call(arguments)
		for(var i = 0;i<e.length;i++){
			e[i].apply(null,args)
		}
	}
}
复制代码

尝试一下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var a = new Event()
a.on('a',function(x){console.log(x)})
a.emit('a',1)//1
复制代码

这样子,在1中单向数据的小例子,首先我们on里面加入事件a,回调是a.innerHTML = str,然后我们可以在改变model层的时候,顺便触发一下(emit(‘a’)),不就可以做到M->V的反映了吗?

对的,是行得通,可是这都是死的,也不能自动让他双向数据绑定,所以我们借用js底层的Object.defineproperty。

3.双绑的中间枢纽——Object.defineproperty(代表:vue)

在第二篇文章已经讲过,这里再重复一次:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var obj = {name:'pp'}
console.log(obj.name)//pp
Object.defineProperty(obj,'name',{
      get:function(){
        return 1
      },
      set:function(newVal){
		console.log(newVal)
      }
    })
console.log(obj.name)//1
obj.name = 2;//2
console.log(obj.name)//1
复制代码

这是vue双绑的核心思想,v层能让m层变了,m层也能让v层变了,只是不能互相关联起来,不能做到改变一个层另一个层也能改变。但是,现在就可以了。 html部分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input id="ipt" type="text" name="">
<p id="a"></p>
复制代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//js:
var data = {
	str:''
}
a.innerHTML = data.str//初始化
function E (){
	this.list=[],
	this.on=function(key,cb){//订阅事件
		if(!this.list[key]){
			this.list[key] = []
		}
		this.list[key].push(cb)
	},
	this.emit = function(){//触发事件
		var key = Array.prototype.shift.call(arguments)
		var e = this.list[key]
		if(!e){
			return
		}
		var args = Array.prototype.slice.call(arguments)
		for(var i = 0;i<e.length;i++){
			e[i].apply(null,args)
		}
	}
}
var e = new E()//实例化
e.on('change',function(x){//订阅change这个事件
	a.innerHTML = x
})
Object.defineProperty(data,'str',{
	set:function(newval){//当data.str被设置的时候,触发事件change
		e.emit('change',newval)
		return newval
	}
})
ipt.oninput = function(){
	data.str = ipt.value//用户的action
}
复制代码

这下,不仅仅是有改变input的内容的单向的数据绑定,而且你还可以去控制台改变data.str=1,p标签的内容马上变成1,实现了双向数据绑定。 我们的例子其实不用观察者模式都可以实现双绑,但是在实际应用中肯定也不可以不用观察者模式,为了代码可读性和可维护性以及拓展性。具体的v-model实现在前面文章已经讲过

点击跳转文章

到这里,你大概比较深入理解双向数据绑定是什么了。网上有很多人有vue双绑demo,但是他们有一部分是仅仅单向绑定的,不妨手动去控制台改一下那个核心绑定的数据,V层的显示内容能马上变化的就是双绑、不能马上有变化的只是单向数据

4. 脏值检测(代表:angular1)

前面说的定时器双绑是扯淡

前面特地埋了个坑,关于Angular脏检查,并不是一些人想象的那样子用定时器周期性进行脏检测(我前面写的那个超级简单的双绑就是人们传闻的angular)

只有当UI事件,ajax请求或者 timeout 等异步事件,才会触发脏检查。而我们前面的vue,当我们在控制台改了数据,就可以马上反映到v层。angular并没有这个操作,也没有意义。因为双绑的M->V一般就是基于ui行为、定时器、ajax这些异步动作,所以这就知道为什么ng-model只能对表单有效了。想做到像vue那样的极致双绑,能够在控制台改个数据就改变视图的,大概就只有defineproperty(听说新版vue现在用ES6的proxy了)和定时器轮询了吧。

在angular1中,私有变量以$$开头,$$watch是一个存放很多个绑定的对象的数组,用$watch方法来添加的,每一个被绑定的对象属性是:变量名、变量旧值、一个函数(用来返回变量新值)、检测变化的回调函数。 对于为什么使用一个函数来记录新值(类似vue的computed)?这样子可以每次调用都得到数据上最新的值,如果把这个值写死,不就是不会变化了吗?这是监控函数的一般形式:从作用域获取值再返回。 接着我们对$scope的非函数数据进行绑定,再到 核心的$digest循环,对于每一个$$watch里面的每一个watch,我们使用 getNewValue() 并且把scope实例 传递进去,得到数据最新值。然后和上一次值进行比较,如果不同,那就调用 getListener,同时把新值和旧值一并传递进去。 最终,我们把last属性设置为新返回的值,也就是最新值。$digest里会调用每个getNewValue(),因此,最好关注监听器的数量,还有每个独立的监控函数或者表达式的性能。 在作用域上添加数据本身不会有性能问题。如果没有监听器在监控某个属性,它在不在作用域上都无所谓。$digest并不会遍历作用域的属性,它遍历的是监听器。一旦将数据绑定到UI上,就会添加一个监听器。 最后,我们需要将新的变量值更新到DOM上,只要加上ng的指令,并解释,触发$digest循环即可 html:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    <input type="text" ng-bind="s" />
    <div ng-bind="s"></div>
复制代码

js:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function Scope(){
        this.$$watchers=[];         //监听器
    }

    Scope.prototype.$watch=function(name,exp,listener){
        this.$$watchers.push({
            name:name,                              //数据变量名
            last:'',                                //数据变量旧值
            newVal:exp,                             //返回数据变量新值的函数
            listener:listener || function(){}       //监听回调函数,变量“脏”时触发
        })
    }

    Scope.prototype.$digest=function(){
        var bindList = document.querySelectorAll("[ng-bind]");      //获取所有含ng-bind的DOM节点
        var dirty=true;
        while(dirty){
            dirty=false;
            for(var i=0;i<this.$$watchers.length;i++){
                var newVal=this.$$watchers[i].newVal();
                var oldVal=this.$$watchers[i].last;
                if(newVal!==oldVal && !isNaN(newVal) && !isNaN(oldVal)){
                    dirty=true;
                    this.$$watchers[i].listener(oldVal,newVal);
                    this.$$watchers[i].last=newVal;
                    for (var j = 0; j < bindList.length; j++) {
                        //获取DOM上的数据变量的名称
                        var modelName=bindList[j].getAttribute("ng-bind");
                        //数据变量名相同的DOM才更新
                        if(modelName==this.$$watchers[i].name) {
                            if (bindList[j].tagName == "INPUT") {
                                //更新input的输入值
                                bindList[j].value = this[modelName];
                            }
                            else {
                                //更新非input的值
                                bindList[j].innerHTML = this[modelName];
                            }
                        }
                    }
                }
            }
        }
    };
        var $scope=new Scope();
        $scope.count=0;
        var inputList=document.querySelectorAll("input[ng-bind]");         
        for(var i=0;i<inputList.length;i++){
            inputList[i].addEventListener("input",(function(index){
                return function(){
                    $scope[inputList[index].getAttribute("ng-bind")]=inputList[index].value;
                    $scope.$digest();           //调用函数时触发$digest
                }
            })(i));
        }
        //绑定非函数数据
        for(var key in $scope){
            if(key!="$$watchers" && typeof $scope[key]!="function") {     
                $scope.$watch(key, (function (index) {
                    return function(){
                        return $scope[index];
                    }
                })(key))
            }
        }
        $scope.$digest();//第一次digest
复制代码

当然,还会有一个问题,当有两个$watch循环监听(watch1监听watch2watch2监听watch1),一个$digest循环执行很多次,而且是多余操作(并且可能把浏览器炸了)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var scope = new $scope();
scope.a = 5;
scope.b = 1;
scope.$watch('a', function(scope) {
  return scope[this.name]
 },
 function(newValue, oldValue) {
  scope.b ++;
 })
 
scope.$watch('b', function(scope) {
  return scope[this.name]
 },
 function(newValue, oldValue) {
  scope.a ++;
 })
复制代码

angular有一个概念叫迭代的最大值:TTL(short for Time To Live)。这个值默认是10。因为digest经常被执行,而且每个digest运行了所有的$watch,再加上用户一般不会创建10个以上链状的监听器。 angular的处理办法是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$scope.prototype.$digest = function() {
        var dirty = true;
        var checkTimes = 0;
         while(dirty) {
            dirty = this.$$digestOnce();
            checkTimes++;
             if(checkTimes>10 &&dirty){
                   throw new Error();
         }
     };
};
复制代码

对于双绑,如果是大循环,循环改变一个值,vue的setter这种即时性的双绑就会在每一次循环都跑一次,而angular1的脏检测这种慢性双绑你可以控制在循环后才一次跑一次,性能取舍就看实际场景吧。

单向数据流和单向数据绑定是什么区别呢?

单向数据流,你得按照他的顺序办事。比如我们假设有一个这样的生命周期:1.从data里面读取数据2.ui行为(如果没有ui行为就停在这里等他有了为止)3.触发data更新4.再回到步骤1

改了一个数,v层不能反回头来找他来更新v层视图(从步骤2跳回去1),你得等下一个循环(转了一圈)的步骤1才能更新视图。react都是这样子,你得setState触发更新,如果你this.state = {...},是没用的,他一直不变。

单向数据绑定,就是绑定事件,比如绑定oninput、onchange、storage这些事件,只要触发事件,立刻执行对应的函数。所以,不要再说一个input绑一个oninput,然后回调改变一个视图层数据就叫他双向数据绑定了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018年04月14日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Angular与MVVM框架
本文作者:IMWeb daihuimi 原文出处:IMWeb社区 未经同意,禁止转载 本文从新人角度讲一讲对angular中MVVM模式的理解,以及angular特性的源码实现。 MVVM核心
IMWeb前端团队
2018/01/08
3.9K0
Angular与MVVM框架
Vue.js 双向数据绑定基本实现认知
对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃避方式,是对大众理想的懦弱回归,是随波逐流,是对内心的恐惧 ——赫尔曼·黑塞《德米安》
山河已无恙
2024/05/10
2060
Vue.js 双向数据绑定基本实现认知
VUE面试题
Trident内核代表产品Internet Explorer,又称其为IE内核。Trident(又称为MSHTML),是微软开发的一种排版引擎。使用Trident渲染引擎的浏览器包括:IE、傲游、世界之窗浏览器、Avant、腾讯TT、Netscape 8、NetCaptor、Sleipnir、GOSURF、GreenBrowser和KKman等。
李才哥
2019/07/10
2.8K0
VUE面试题
Vue 2 常见面试题速查
当一个 Vue 实例创建时,Vue 会遍历 data 中的属性,用 Object.defineProperty 将它们转为 getter / setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
Cellinlab
2023/05/17
1.2K0
Vue 2 常见面试题速查
angularjs源码笔记(4)--scope
在ng的生态中scope处于一个核心的地位,ng对外宣称的双向绑定的底层其实就是scope实现的,本章主要对scope的watch机制、继承性以及事件的实现作下分析。
alexqdjay
2022/01/04
1.3K0
Vue2.X是如何利用Object.defineProperty()实现数据绑定的
Vue2.X是如何利用Object.defineProperty()实现数据绑定的
伯约同学
2022/02/15
4740
第217天:深入理解Angular双向数据绑定的原理
双向绑定是新的前端框架中频繁出现的一个新词汇,也是mvvm的核心原理。angularjs五条核心信念中的数据驱动,便是由双向绑定进行完成。
半指温柔乐
2018/09/11
3.7K0
第217天:深入理解Angular双向数据绑定的原理
Angularjs1.X进阶笔记(1)—两种不同的双向数据绑定
html-Controller的双向数据绑定,在开发中非常常见,也是Angularjs1.x的宣传点之一,使用中并没有太多问题。
大史不说话
2019/03/01
3.5K0
Angularjs1.X进阶笔记(1)—两种不同的双向数据绑定
再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结
ng-bind 单向数据绑定($scope -> view),用于数据显示,简写形式是 {{}}。
周陆军
2018/08/02
7.9K0
vue双向数据绑定原理
目前几种主流的mvc(vm)框架都实现了单向数据绑定,而我所理解的双向数据绑定无非就是在单向绑定的基础上给可输入元素(input、textare等)添加了change(input)事件,来动态修改model和 view,并没有多高深。所以无需太过介怀是实现的单向或双向绑定。 实现数据绑定的做法有大致如下几种:
念念不忘
2019/03/29
2.2K0
vue双向数据绑定原理
JavaScript实现简单的双向数据绑定
双向数据绑定简单来说就是UI视图(View)与数据(Model)相互绑定在一起,当数据改变之后相应的UI视图也同步改变。反之,当UI视图改变之后相应的数据也同步改变。
laixiangran
2018/07/25
1.9K0
前端面试题angular_Vue前端面试题
1,ng-if 跟 ng-show/hide 的区别有哪些? 第一点区别是,ng-if 在后面表达式为 true 的时候才创建这个 dom 节点,ng-show 是初始时就创建了,用 display:block 和 display:none 来控制显示和不显示。 第二点区别是,ng-if 会(隐式地)产生新作用域,ng-switch 、 ng-include 等会动态创建一块界面的也是如此。 这样会导致,在 ng-if 中用基本变量绑定 ng-model,并在外层 div 中把此 model 绑定给另一个显示区域,内层改变时,外层不会同步改变,因为此时已经是两个变量了。
全栈程序员站长
2022/11/07
14.2K0
AngularJS一些简单处理得到性能提升
谈起angular的脏检查机制(dirty-checking), 常见的误解就是认为: ng是定时轮询去检查model是否变更。 其实,ng只有在指定事件触发后,才进入$digest cycle:
javascript.shop
2019/09/04
1.7K0
AngularJS一些简单处理得到性能提升
Vue响应式系统原理并实现一个双向绑定
我们知道Dep.target在创建Watcher的时候是null,并且它只是起到一个标记的作用,当我们创建Watcher实例的时候,我们的Dep.target就会被赋值到Watcher实例,进而放入target栈中,我们这里调用的是pushTarget函数:
yyds2026
2022/10/19
3460
2、Angular JS 学习笔记 – 双向数据绑定和Scope概念
Angular 中的数据绑定是自动从模型和视图间同步数据,Angular的这种数据绑定实现让你可以将应用中的模型和视图的数据看作一个源, 视图在任何时候都是对模型的一个投影,当模型发生变化,相关的视图也会发生变化,反之亦然。
前Thoughtworks-杨焱
2021/12/08
13.3K0
一面高频vue面试题
eventBus事件总线适用于父子组件、非父子组件等之间的通信,使用步骤如下: (1)创建事件中心管理组件之间的通信
bb_xiaxia1998
2022/10/28
8080
【AngularJS】 # AngularJS入门
ng-app 指令定义一个 AngularJS 应用程序。 若不声明,将直接显示表达式。
全栈程序员站长
2022/09/15
23.3K0
【AngularJS】 # AngularJS入门
Vue 源码解析:深入响应式原理
Vue.js 最显著的功能就是响应式系统,它是一个典型的 MVVM 框架,模型(Model)只是普通的 JavaScript 对象,修改它则视图(View)会自动更新。这种设计让状态管理变得非常简单而直观,不过理解它的原理也很重要,可以避免一些常见问题。下面让我们深挖 Vue.js 响应式系统的细节,来看一看 Vue.js 是如何把模型和视图建立起关联关系的。
前端迷
2019/12/09
1.1K0
Vue 源码解析:深入响应式原理
前端MVC学习总结(一)——MVC概要与angular概要、模板与数据绑定
张果
2018/01/04
15.5K0
前端MVC学习总结(一)——MVC概要与angular概要、模板与数据绑定
javascript基础修炼(9)——MVVM中双向数据绑定的基本原理
MVVM模型是前端单页面应用中非常重要的模型之一,也是Single Page Application的底层思想,如果你也因为自己学习的速度拼不过开发框架版本迭代的速度,或许也应该从更高的抽象层次去理解现代前端开发,因为其实最核心的经典思想几乎都是不怎么变的。关于MVVM的文章已经非常多了,本文不再赘述。
大史不说话
2018/12/12
1.1K0
相关推荐
Angular与MVVM框架
更多 >
LV.1
这个人很懒,什么都没有留下~
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文