一、Vue简介
1.1 Vue是什么
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
由于笔者水平有限,如有不足和不正确的地方,请评论指出。
1.2 Vue解决了什么问题
数据的双向绑定
组件化管理
1.3 怎么学习Vue
二、 MVVM
2.1 顺便摘要下廖雪峰JavaScript教程的一段前端的发展史
在上个世纪的1989年,欧洲核子研究中心的物理学家Tim Berners-Lee发明了超文本标记语言(HyperText Markup Language),简称HTML,并在1993年成为互联网草案。从此,互联网开始迅速商业化,诞生了一大批商业网站。
最早的HTML页面是完全静态的网页,它们是预先编写好的存放在Web服务器上的html文件。浏览器请求某个URL时,Web服务器把对应的html文件扔给浏览器,就可以显示html文件的内容了。
如果要针对不同的用户显示不同的页面,显然不可能给成千上万的用户准备好成千上万的不同的html文件,所以,服务器就需要针对不同的用户,动态生成不同的html文件。一个最直接的想法就是利用C、C++这些编程语言,直接向浏览器输出拼接后的字符串。这种技术被称为CGI:Common Gateway Interface。
很显然,像新浪首页这样的复杂的HTML是不可能通过拼字符串得到的。于是,人们又发现,其实拼字符串的时候,大多数字符串都是HTML片段,是不变的,变化的只有少数和用户相关的数据,所以,又出现了新的创建动态HTML的方式:ASP、JSP和PHP——分别由微软、SUN和开源社区开发。
在ASP中,一个asp文件就是一个HTML,但是,需要替换的变量用特殊的<%=var%>标记出来了,再配合循环、条件判断,创建动态HTML就比CGI要容易得多。
但是,一旦浏览器显示了一个HTML页面,要更新页面内容,唯一的方法就是重新向服务器获取一份新的HTML内容。如果浏览器想要自己修改HTML页面的内容,就需要等到1995年年底,JavaScript被引入到浏览器。
有了JavaScript后,浏览器就可以运行JavaScript,然后,对页面进行一些修改。JavaScript还可以通过修改HTML的DOM结构和CSS来实现一些动画效果,而这些功能没法通过服务器完成,必须在浏览器实现。
第一阶段,直接用JavaScript操作DOM节点,使用浏览器提供的原生API:
var dom = document.getElementById('name'); dom.innerHTML = 'Homer'; dom.style.color = 'red';
第二阶段,由于原生API不好用,还要考虑浏览器兼容性,jQuery横空出世,以简洁的API迅速俘获了前端开发者的芳心
$('#name').text('Homer').css('color', 'red');
第三阶段,MVC模式,需要服务器端配合,JavaScript可以在前端修改服务器渲染后的数据。
现在,随着前端页面越来越复杂,用户对于交互性要求也越来越高,想要写出Gmail这样的页面,仅仅用jQuery是远远不够的。MVVM模型应运而生。
MVVM最早由微软提出来,它借鉴了桌面应用程序的MVC思想,在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。
把Model和View关联起来的就是ViewModel。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。
其实从jq语法的引入操作DOM结构,变的容易的多了。但是如果能直接该表javaScript对象就能导致DOM结构做出对应的变化,那该多好呀,而MVVM就把开发者从DOM的繁琐步骤中解脱出来了,而更加关注Mode的变化。
三、步步为营
3.1 主流双向绑定的做法
手动绑定
脏值检查(angular.js)
数据劫持
具体的做法可以参考javascript实现数据双向绑定的三种方式
3.2 简要概述以上做法:
双向绑定从本质上来说无非两部分 Model->View 与 View->Model
3.2.1 首先是Model->View的思路
model无非是个Object,或者是如Vue里面是个全局的vm.data
view 在html上无疑是个树形的标签结构,所以也就是node这样结构
最直接的做法遍历。
先看下最基本的vue代码
html
<div id="app"> <input type="text" v-model="input" id="input"> {{text}} <p>{{input}}</p> <p id="show"></p></div>
可以看到Vue里面绑定数据无非两种,<input type="text" v-model="input" id="input">
其中 v-model
加载<>中,也就是给标签增加新的属性,和data-
的方式增加属性一般无二,(PS:顺便提及小程序中函数传参,运用就是这样的方法)。
So, a:for也罢,v-model也罢,或者其他各种种种无非是标识符不同而已,万变不离其中。第二部分就是关于'{{}}',因为其实在标签内部,比如<p>{{input}}</p>
可以看到, {{input}}并不作为app的子节点,所以当为元素节点的是,判断是否有子节点,有则再次调用scan函数。
所以有了,第一简单的方法就是每次改变data数值的时候,直接再次调用scan函数(PS:因为scan方法因为 先遍历node列表,再遍历该节点的属性,所以会是双层遍历)
也就是简单绑定的方法
/** * 设置数据后扫描 */ function mvSet(key, value){ data[key] = value; scan(); }
第二种脏值检查
直接封装和执行$digest()
或$apply()
/** * 脏循环检测 * @param {[type]} elems [description] * @return {[type]} [description] */ var digest = function(elems) { /** * 扫描带指令的节点属性 */ for (var i = 0, len = elems.length; i < len; i++) { var elem = elems[i]; for (var j = 0, len1 = elem.attributes.length; j < len1; j++) { var attr = elem.attributes[j]; if (attr.nodeName.indexOf('q-event') >= 0) { /** * 调用属性指令 */ var dataKey = elem.getAttribute('ng-bind') || undefined; /** * 进行脏数据检测,如果数据改变,则重新执行指令,否则跳过 */ if(elem.command[attr.nodeValue] !== data[dataKey]){ command[attr.nodeValue].call(elem, data[dataKey]); elem.command[attr.nodeValue] = data[dataKey]; } } } } }
第三种方式 采用Object.defineProperty对数据对象做属性get和set的监听,但是需要注意的是为了保存传进来的数值,并且避免无效循环,采用如下方法用于独立的函数,value来存储对应的对应的数值。
function defineProperty(vm, key, val){ Object.defineProperty(vm, key, { get: function (){ return val; }, set: function (newValue){ document.getElementById("show").innerHTML = newValue; document.getElementById("input").value = newValue; if(newValue === val){ return; } val = newValue; } }); } function observe(data, vm){ Object.keys(data).forEach(function(key){ defineProperty(vm, key, data[key]); });
3.2.2 View->Model
View到Model无非一些可以改变的标签,比如input等,而view到Model基本的思路都是原生的事件的一些方法。比如如下代码。
document.getElementById('input').addEventListener('keyup', function (e) { obj.txt = e.target.value; });
3.2.3 关于设计模式
从Model->View以及后面的从 View->Model相信大家也能看到,其实这三种绑定方式,最大区别体现在Model->层。虽然我们可以通过遍历的方式对应地修改对应的标签的属性。也能通过我们自己指定的标识符比如’v-model‘, 'ng-text','{{}}',甚至比如采用自己的名称的前缀比如笔者的'sl-text'等等来采用需要双向绑定的标签元素采用列表的统一管理,这样能减少遍历次数,也可以对于v-model绑定的属性,通过列表添加到该标签,作为其的一个属性,但是是否还能进一步优化。
引用一张''Header First"设计模式上的观察者模式一图,如下:
image.png
在javascript没有像协议这样的语法,不过原理还是一致,改良好的双向数据绑定模型如下代码。
//第三部分 function Watcher(vm, node, name, nodeType){ Dep.target = this; this.vm = vm; this.node = node; this.name = name; this.nodeType = nodeType; this.update(); Dep.target = null; } Watcher.prototype = { update: function(){ this.get(); if (this.nodeType === 'text') { this.node.nodeValue = this.value; } if (this.nodeType === 'input') { this.node.value = this.value; } }, get: function(){ this.value = this.vm[this.name]; } } function Dep(){ this.subs = []; } Dep.prototype = { addSub: function(sub){ this.subs.push(sub); }, notify: function(){ this.subs.forEach(function(sub){ sub.update(); }); } } //第二部分 function defineProperty(vm, key, val){ var dep = new Dep(); Object.defineProperty(vm, key, { get: function (){ if(Dep.target){ dep.addSub(Dep.target); } return val; }, set: function (newValue){ if(newValue === val){ return; } val = newValue; dep.notify(); } }); } function observe(data, vm){ //Object.keys(data)返回data的key数组 Object.keys(data).forEach(function(key){ defineProperty(vm, key, data[key]); }); } //第一部分 function compile(node, vm){ if(node.nodeType === 1){ var attr = node.attributes; for(let i = 0; i<attr.length; i++){ if(attr[i].nodeName === 'v-model'){ let name = attr[i].nodeValue; node.addEventListener('keyup', function(e){ vm[name] = e.target.value; }); node.value = vm[name]; node.removeAttribute('v-model'); new Watcher(vm, node, name, "input"); } } if (child = node.firstChild) { compile(child, vm); } } if(node.nodeType === 3){ let reg = /\{\{(.*)\}\}/; if(reg.test(node.nodeValue)){ let name = RegExp.$1; name = name.trim(); // node.nodeValue = vm.data[name]; new Watcher(vm, node, name, "text"); } } } function nodeToFragment(node, vm){ var flag = document.createDocumentFragment(); var child; while(child = node.firstChild){ compile(child, vm); flag.appendChild(child); } return flag; } function Vue(options){ var id = options.el; var data = options.data; observe(data, this); var dom = nodeToFragment(document.getElementById(id), this); document.getElementById(id).appendChild(dom); } var vm = new Vue({ el: 'app', data: { input: 'hello' } });
大体逻辑表现为,首先定义观察者Watcher,并在编译函数compile()中对每个节点添加观察着Watcher,当接收到分发者指令时,调用update方法更新视图。接下来定义消息分发者Dep,Dep维护观察者数组,当值发生变化时,通知各观察者调用update方法。
image.png
四、附上源码
image.png
作者:破晓霜林
链接:https://www.jianshu.com/p/4cfbeddc5db6
共同学习,写下你的评论
评论加载中...
作者其他优质文章