内容简介:来公司一年多了,接触了angular,vue这两种主流的MVVM框架,之前一直没有去思考其实现的原理,但框架其实也是用基础的js方法实现的,所以还是很有必要去了解下其原理的,在这里简单的总结一下。实现数据绑定的大致几种做法:发布者-订阅者模式:一般通过sub,pub的方式实现数据和视图的绑定监听,更新数据方式通常做法是 vm.set('property', value),现在用的较少,这里不再阐述其原理了,有兴趣的同学可以点击这里
来公司一年多了,接触了angular,vue这两种主流的MVVM框架,之前一直没有去思考其实现的原理,但框架其实也是用基础的js方法实现的,所以还是很有必要去了解下其原理的,在这里简单的总结一下。
1,几种实现双向绑定的方法
实现数据绑定的大致几种做法:
- 发布者-订阅者模式(backbone.js)
- 脏值检查(angular.js)
- 数据劫持(vue.js)
发布者-订阅者模式:一般通过sub,pub的方式实现数据和视图的绑定监听,更新数据方式通常做法是 vm.set('property', value),现在用的较少,这里不再阐述其原理了,有兴趣的同学可以点击这里
脏值检查:ng是通过脏检查的检测机制来对比数据是否发生了变更,来决定是否更新视图。那么脏检查是怎么触发的呢,大致有一下几种情况:
- UI事件,如ng-click
- ajax请求
- timeout延迟
- 执行$apply()(非ng事件时需我们手动触发脏检查)
这里我们就脏值检查举个小栗子:
// html <div ng-app="app"> <div ng-controller="myController"> <span>{{count}}</span> <button ng-click="counter=counter+1">increase</button> </div> </div> // js var app = angular.module('app', []); app.controller('myController', function($scope) { $scope.count = 0; }) 复制代码
看下这时的效果
我们发下这时通过ng-click形成的数据更改是实时响应的。来,让我们换种实现方式:
<div ng-app="app"> <div ng-controller="CounterCtrl"> <span>{{data}}</span> <button my-click>click</button> </div> </div> app.controller('CounterCtrl', function($scope) { $scope.data = 0; }) app.directive('myClick', function() { return function(scope, element, attr) { element.on('click', function() { scope.data++; console.log(scope.data); }) } }) 复制代码
这时的效果
我们发现虽然data的值在更改,但是view却并没有更新,这是因为我没有用ng事件来触发UI事件,所以没有执行脏检查,这个时候我们可以手动加下$apply试下效果。
数据劫持:vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
2,数据劫持的代码实现
这里我们用数据劫持的方式来实现一个简易的数据绑定,我们来写一下:
简单的写下html
<div id='app'> <h3>姓名</h3> <p>{{name}}</p> <h3>年龄</h3> <p>{{age}}</p> </div> 复制代码
接下来准备我们要展示数据
document.addEventListener('DOMContentLoaded', function() { let opt = { el:'#app', data:{ name:'准备中...', age:30 } } let vm = new MVVM(opt); setTimeout(() => { opt.data.name = '小明'; }, 2000); }) 复制代码
好,到这里准备工作都做好了,接下来是重头戏,不多说,直接上代码:
class MVVM { constructor(opt) { this.opt = opt; this.observe(opt.data); let root = document.querySelector(opt.el); this.complie(root); } // 绑定观察者对象 observe(data) { Object.keys(data).forEach(key => { // 声明一个观察者对象 let obv = new Observer(); // 记录data[key]的值,防止set的时候递归调用报错 data['_' + key] = data[key]; Object.defineProperty(data, key, { get() { Observer.target && obv.addSubNode(Observer.target); return data['_' + key]; }, set(newVal) { obv.update(newVal); data['_' + key] = newVal; } }) }) } // 初始化页面,遍历 DOM,收集每一个key变化时,随之调整的位置,以观察者方法存放起来 complie(node) { [].forEach.call(node.childNodes, child =>{ if(!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)){ let key = RegExp.$1.trim() child.innerHTML = child.innerHTML.replace(new RegExp('\\{\\{\\s*'+ key +'\\s*\\}\\}', 'gm'),this.opt.data[key]) // 记录当前的DOM节点 Observer.target = child; // 调用set函数 this.opt.data[key]; Observer.target = null; } else if (child.firstElementChild) { this.compile(child); } }) } } // 常规观察者类 class Observer{ constructor() { this.subNode = [] } addSubNode(node){ this.subNode.push(node) } update(newVal){ this.subNode.forEach(node=>{ node.innerHTML = newVal }) } } 复制代码
好了至此就是所有的代码,我们可以把opt声明为全局变量,这样我们就可以在控制台修改相关数据,然后实时观察修改结果。
具体的实现代码参照了这篇文章 50行代码的MVVM,感受闭包的艺术 ,当时看到的时候有些地方比较困惑,理解了之后这里将其贴上来,注释上我自己的理解,仅供学习参考吧。(感觉data['_' + key] 这种记录方式不太好,引入了无关的值)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Timer机制源码浅析
- 浅析数据库并发控制机制
- 浅析Vue 中 $nextTick 机制
- 浅析Dubbo的SPI扩展机制
- [译] Python 进阶:浅析「垃圾回收机制」
- Python3之对象垃圾收集机制浅析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。