Open zhengwei1949 opened 7 years ago
首先我们要理解数据绑定。我们看到的网站页面中,是由数据和ui两部分组合而成。将ui转换成浏览器能理解的语言,便是html和css主要做的工作。而将数据显示在页面上,并且有一定的交互效果(比如点击等用户操作及对应的页面反应)则是js主要完成的工作。 在以前的开发模式中,这一步一般通过jq操作DOM结构:
<div class="wrapper"> <div></div> <p> <span>这里有内容</span> <span></span><!--我们想把内容写在这里面--> </p> </div> <script> var str = "Hello World";//这个数据是这里是写死的,现实案例肯定是通过ajax来自数据库的 var oDemo = document.querySelector('.wrapper span:nth-child(2)'); oDemo.innerHTML = str; </script>
从而进行更新页面。但这样带来的问题是大量的dom操作,如果我们的页面结构发生了变化,我们的js业务代码被迫也要进行变更。
如果能在开始的时候,便已经确定好从后端获取的数据到页面上需要进行的操作,当数据发生改变,页面的相关内容也自动发生变化,这样便能极大地方便前端工程师的开发。在新的框架中(angualr,react,vue等),通过对数据的监视,发现变化便根据已经写好的规则进行修改页面,便实现了数据绑定。可以看出,数据绑定是M(model,数据)通过VM(model-view,数据与页面之间的变换规则)向V(view)的一个修改。
如果我们用angular可以写成:
<div class="wrapper"> <div> <input type="text" ng-model="abc"> </div> <p> <span>这里有内容</span> <span>{{abc}}</span><!--我们想把内容写在这里面--> </p> </div> <script> var myApp = angular.module('myApp',[]); myApp.controller('myController',['$scope',function($scope){ $scope.abc = 'Hello World'; }]) </script>
如果我们用vue,我们可以写成:
这里有内容
如果我们用react,我们可以写成: ```jsx //这里我采用的是非受控表单控件实现的 import React from 'react' class Hello extends React.Component{ constructor(){ super(props); this.state = { abc:'Hello World' } } handleChange = ()=>{ this.setState({ abc:this.refs.txt.value }) } render(){ return ( <div class="wrapper"> <div>{{abc}}</div> <input type="text" onInput={this.handleChange} ref="txt"> <p> <span>这里有内容</span> <span>{abc}</span>{<!--我们想把内容写在这里面-->} </p> </div> ) } }
在用户操作页面(比如在Input中输入值)的时候,数据能及时发生变化,并且根据数据的变化,页面的另一处也做出对应的修改。有一个常见的例子就是淘宝中的购物车,在商品数量发生变化的时候,商品价格也能及时变化。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <input type="text" id="txt" ng-model="abc"> <div ng-bind="abc"></div> <input type="text" ng-model="abc"> <script> var eleList = document.querySelectorAll('[ng-bind="abc"]'); var inputEleList = document.querySelectorAll('[ng-model="abc"]'); var obj = { val:100, set:function(val){ this.val = val; apply(); } } function apply(){ for(var i=0;i<eleList.length;i++){ eleList[i].innerHTML = obj.val; } for(var i=0;i<inputEleList.length;i++){ inputEleList[i].value = obj.val; inputEleList[i].oninput = function(){ obj.set(this.value); } } } render() </script> </body> </html>
实现原理:
其实这里的关键点就是对input的oninput的回调函数里面多做了一些事情。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <input type="text" v-model="b"> <input type="text" v-model="b"> <input type="text" v-model="b"> <input type="text" v-model="b"> <p v-text="b"></p> <p v-text="b"></p> <script> var bValue = 'Hello World'; var obj = {}; Object.defineProperty(obj,'b',{ get:function(){//getter属性 return bValue; }, set:function(newVal){//setter属性 bValue = newVal; render(); } }) var oPList = document.querySelectorAll('[v-text="b"]'); var inputList = document.querySelectorAll('[v-model="b"]'); function render(){ for(var i=0;i<oPList.length;i++){ oPList[i].innerHTML = obj.b; } for(var i=0;i<inputList.length;i++){ inputList[i].value = obj.b; } } render(); for(var i=0;i<inputList.length;i++){ inputList[i].oninput = function(){ console.log(1111) obj.b = this.value; } } </script> </body> </html>
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。
上面是我们自己写的demo,只是简单的理解一下vue实现的原理,但是真实的vue代码比这要更加的复杂。因为如果你做任何的改变都立马进行DOM的更新,性能会变得很差。
vue的解决方案是:只要观察到数据的变化,vue会开启一个队列,并缓冲在同一个事件循环中发生的所有的数据改变。如果同一个watcher被多次触发,只会一次推入到队列中。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作上非常重要。然后,在下一个的事件循环tick中,vue刷新队列并执行实际(已去重)的工作。Vue在内部尝试对异步队列使用原生的Promise.then和MutationObserver,如果执行环境不支持,会采用setTimeout(fn,0)代替。
如果你确实需要数据立马更新到视图当中,你可以使用Vue.nextTick(callback).(换句话说,如果你改变了数据模型,但是视图并没有更新,你可以考虑是不是需要使用Vue.nextTick)
只要我们给页面中的标签加上了ng-bind,ng-model等指令,或者在控制器当中使用了如上的angular内置的api的话,angular会为这些地方绑定了一个watcher,也就是记录一下这块的值(oldValue),然后当我们尝试去改变比如改变绑定了ng-model的地方的值(newValue)的时候,angular就会通过$apply重新计算一遍我们$scope上面所有的函数返回值、表达式的值,如果发现newValue的值和oldValue的值发生了变化,则会去更新视图。
会导致页面view和model刷新的还有:ajax相关的$http等angular的内置的api。
当然,如果我们使用自己的方法,不能触发这个过程,但我们也是可以通过主动调用$scope.$appl强制进行刷新。
https://www.imooc.com/video/16704 参考这个讲解Object.defineProperty
理解双向数据绑定
首先我们要理解数据绑定。我们看到的网站页面中,是由数据和ui两部分组合而成。将ui转换成浏览器能理解的语言,便是html和css主要做的工作。而将数据显示在页面上,并且有一定的交互效果(比如点击等用户操作及对应的页面反应)则是js主要完成的工作。 在以前的开发模式中,这一步一般通过jq操作DOM结构:
从而进行更新页面。但这样带来的问题是大量的dom操作,如果我们的页面结构发生了变化,我们的js业务代码被迫也要进行变更。
如果能在开始的时候,便已经确定好从后端获取的数据到页面上需要进行的操作,当数据发生改变,页面的相关内容也自动发生变化,这样便能极大地方便前端工程师的开发。在新的框架中(angualr,react,vue等),通过对数据的监视,发现变化便根据已经写好的规则进行修改页面,便实现了数据绑定。可以看出,数据绑定是M(model,数据)通过VM(model-view,数据与页面之间的变换规则)向V(view)的一个修改。
各框架当中针对双向数据绑定的语法
如果我们用angular可以写成:
如果我们用vue,我们可以写成:
这里有内容
在用户操作页面(比如在Input中输入值)的时候,数据能及时发生变化,并且根据数据的变化,页面的另一处也做出对应的修改。有一个常见的例子就是淘宝中的购物车,在商品数量发生变化的时候,商品价格也能及时变化。
自己实现双向数据绑定
实现原理:
其实这里的关键点就是对input的oninput的回调函数里面多做了一些事情。
vue的双向数据绑定的原理
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。
上面是我们自己写的demo,只是简单的理解一下vue实现的原理,但是真实的vue代码比这要更加的复杂。因为如果你做任何的改变都立马进行DOM的更新,性能会变得很差。
vue的解决方案是:只要观察到数据的变化,vue会开启一个队列,并缓冲在同一个事件循环中发生的所有的数据改变。如果同一个watcher被多次触发,只会一次推入到队列中。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作上非常重要。然后,在下一个的事件循环tick中,vue刷新队列并执行实际(已去重)的工作。Vue在内部尝试对异步队列使用原生的Promise.then和MutationObserver,如果执行环境不支持,会采用setTimeout(fn,0)代替。
如果你确实需要数据立马更新到视图当中,你可以使用Vue.nextTick(callback).(换句话说,如果你改变了数据模型,但是视图并没有更新,你可以考虑是不是需要使用Vue.nextTick)
angular双向数据绑定原理
只要我们给页面中的标签加上了ng-bind,ng-model等指令,或者在控制器当中使用了如上的angular内置的api的话,angular会为这些地方绑定了一个watcher,也就是记录一下这块的值(oldValue),然后当我们尝试去改变比如改变绑定了ng-model的地方的值(newValue)的时候,angular就会通过$apply重新计算一遍我们$scope上面所有的函数返回值、表达式的值,如果发现newValue的值和oldValue的值发生了变化,则会去更新视图。
会导致页面view和model刷新的还有:ajax相关的$http等angular的内置的api。
当然,如果我们使用自己的方法,不能触发这个过程,但我们也是可以通过主动调用$scope.$appl强制进行刷新。