[Vue系列] Vue功能的简单实现

文章目录

  • Vue功能的简单实现
    • 第一章 前言
    • 第二章 MVC和MVVM
      • 2.1 MVC
      • 2.2 前端渲染vs后端渲染
      • 2.3 前端MVC
      • 2.4 前端MVVM
    • 第三章 Vue功能 --- 数据绑定
    • 第四章 Vue功能 --- 双向数据绑定
    • 后记

Vue功能的简单实现

第一章 前言

最近在进行Vue的教学工作,为了能够更好的在小兄弟面前显摆显摆,我准备在教学中加入对Vue功能的原生实现环节,也将自己理解的过程写入博客,作为记录.

第二章 MVC和MVVM

在Vue中,数据是核心,Vue让我们更加关注数据本身而不用理会它渲染和实现的过程.
Vue是一个典型的MV-VM框架,是MVC的进一步发展.在解释MVVM框架之前,我们首先要了解MVC是什么

2.1 MVC

MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。

  • Model(模型) - 是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象在数据库中存取数据。
  • View(视图) - 视图代表模型包含的数据的可视化。
  • Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。

由于View层,也就是为我们的用户呈现的页面混合了HTML、CSS和JavaScript,而且页面众多,所以,代码的组织和维护难度其实很复杂.

最早的前端页面其实就是服务器上已经编写好的,内容完全静态的页面,用户在请求时,会直接返回.

但是随着业务的复杂和用户交互的需求,完全静态的页面不能够满足用户的需求.我们无法对成千上万的用户提供成千上万个不同的静态页面.

于是人们首先想到的是使用C/C++等语言,通过动态编译HTML的方式,为用户提供不同的编译完成的HTML页面.这种技术也叫做CGI(Common Gateway Interface)

CGI的效率比较低,因为我们需要在用户请求时,完整的编译一遍需要返回的页面,用户量多的时候也会不堪负重.

后来人们发现,对于用户返回的HTML,其实只有一部分数据是会改变的,而HTML的基本内容一般情况下不需要重新编译.

于是ASP,JSP,PHP应运而生,其中PHP以标记语言的特性,能够和HTML混写,服务器在接收请求之后,PHP解释器不会理会除了PHP标签内部以外的内容,而将各异性的内容编译输出即可

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		 echo $name?>
	</body>
</html>

请注意,这是一个php文件,混写了HTML

这就使得开发变得更为便利了起来.因此,直到现在,世界范围内排名前一千万的网站中,仍有近百分之八十的网站在使用PHP技术,这和PHP的灵活的特性是密不可分的.
[Vue系列] Vue功能的简单实现_第1张图片
关于ASP和JSP技术,笔者目前没有很深的了解,就不进一步介绍了.

随着时代的进一步发展,互联网应用的用户量越来越大,功能也越来越复杂,因而开发团队变得更为庞杂.这种PHP和HTML混写的方式不太利于团队分工协作,我到底是要写PHP呢?还是要写HTML?

首先出现的是"模板引擎"的概念,PHP程序员为了分离HTML结构,CSS样式,JS特效等与PHP业务逻辑,放弃了混写模式,采用了类似Smarty等模板进行开发,通过在HTML中预留{$name}等数据的占位符,专注页面的开发,其实在这一时代,前端程序员才正式的被分离了出来.
而模板引擎会读取这些HTML,通过并结合后端程序员的数据处理和业务逻辑,将具体的$name和原有的HTML一并的渲染出来,再返回给用户.

实际上 这和混写没有本质的区别,都是在后端进行页面的渲染

由此View(视图) 被分离了出来.

而后端程序员所做的操作一般的流程是

接受用户的请求-->数据库数据的CURD操作-->完成业务逻辑-->返回响应

在这个过程中,为了让程序员更加专注与业务逻辑的处理,而并不需要把精力放在具体的SQL语句的拼接,数据的处理上,Controller和Model也被分离了出来,

MVC其实不是一种设计模式,而是为了软件开发而诞生的一种开发模式,MVC允许在不改变视图的情况下改变视图对用户输入的响应方式,用户对View的操作交给了Controller处理,在Controller中响应View的事件调用Model的接口对数据进行操作,一旦Model发生变化便通知相关视图进行更新.这也实现了程序员的业务分工.

[Vue系列] Vue功能的简单实现_第2张图片

数据的通信是单向的

关于MVC,我计划书写使用PHP实现MVC框架的系列文章.

2.2 前端渲染vs后端渲染

我们讲的是Vue,并不是后端MVC框架.那么,既然后端MVC已经将视图,模型和控制器进行了分层开发,我们为什么还要把传统的View层,也就是我们的前端实现再次分层呢?

这其实就涉及到了前端渲染和后端渲染的比较.

传统意义上的后端开发,我们上文中也提起过,是服务器在后端处理数据,拼接HTML,渲染模板,并将页面返回给客户端.
即便我们做好了任务分层,也仅仅是明确了开发职责,我们无法避免用户访问时,页面在后端服务器的渲染.虽然有一些缓冲技术或者页面静态化技术,但随着用户量的增加和请求的变化,服务器承受的压力会迅速增加.

为了减缓服务器的压力,人们开始使用前端技术,也就是使用javascript渲染HTML.

前端渲染,其实就是指浏览器会从后端得到一些信息,这些信息或许是Vue的模板/组件,亦或是JSON等各种数据交换格式所包装的数据,甚至是直接的合法的HTML字符串.这些形式都不重要,重要的是,将这些信息组织排列形成最终可读的HTML字符串是由浏览器来完成的,在形成了HTML字符串之后,再进行显示。

浏览器在完成第一次请求之后,将模板/应用程序输出,剩下的数据处理都交由前端来完成,我们的服务器只负责通过接口提供数据,而浏览器则会完成剩下的工作.Web不再是一张张页面,而是一个整体的应用,一个由路由系统、数据系统、页面(组件)系统…组成的应用程序,这样的组织形式会减轻服务器的压力,程序的开发也会变得更为敏捷,

前端渲染和后端渲染互有优劣

前端渲染
优势
页面渲染在浏览器完成,减轻了服务器的压力
端口分离,更易于开发
跨平台开发和Native应用开发变得更为简单
访问和响应速度更快
劣势
不利于SEO
不兼容老旧Pc端浏览器
业务逻辑位于客户端,安全性降低
首次加载缓慢
后端渲染
优势
开发速度快 不涉及复杂的两端交互
SEO友好度高
每次基本上只打开一个页面,首次访问稍快
对于内容固定的页面,静态化的优势明显
内容隐藏于后端,安全性高
劣势
数据变化频繁的页面,静态化困难
服务器压力变大
可移植性差

技术都是为人服务的.没有优劣之分.我们应该知道在哪些场景使用哪些技术.

2.3 前端MVC

有了前端渲染的相关知识,我们首先来接触一下前端的MVC概念.

如果前端没有框架,只使用原生的html+js,MVC模式可以这样理解:
将HTML看成View,负责页面的呈现,js看成Controller,负责处理用户与应用的交互,响应对View的操作(事件监听), 将js的Ajax当做Model,也就是数据层,通过Ajax从服务器获取数据。调用Model对数据进行操作,完成Model与View的同步(根据Model的改变,通过选择器对View进行操作)

在原有的"行为-样式-结构"分离的基础之上,我们通过原生js或者jQuery技术,能够很轻易的完成对页面的前端渲染.

<style>
	#div{
		width:400px;
		height:100px;
		background-color:lightblue
	}
style>

<div id="oBox">
	点击查看新闻
div>
<script>
	//controller
	var oBox = document.getElementById('oBox');
	oBox.addEventListener('click',function(){
		var xhr = new XMLHttpRequest();
		xhr.onreadystatechange = function(){
			if(this.readyState == 4 && this.status == 200){
			//model
				var data = this.responseText;
				oBox.innerHTML == data;
			}
		}
		xhr.open("someurl");
		xhr.send(null);
	});
script>

如果想要完成前端路由,我们可以使用hash或者history(pjax)轻易的实现.

虽然这样的前端开发,可以看做"MVC",但是对于职责的划分,并不是那么明确,并且看起来有些强行的MVC,Model层被弱化了很多.采用jQuery开发,代码书写和上述代码没有本质的区别,因为jQuery本身更加注重的是"Query".这样的划分,并不是真正的MVC.

实际开发过程中 MVC往往会更为灵活,我们来看一个MVC框架backbone的例子.

var M = Backbone.Model.extend({  
  defaults:{name:"lisi"} ,
  initialize : function(){   //new M时,会执行这个初始化函数。
    this.on("change",function(){   //监听change事件
      alert(1);
    })
  }
})
var model = new M();
//这里只要改变模型,就会触发,从而改变视图。
model.set("name","wangwu");    

这里Model的对象不只包含数据,也有对属性(name)的监听事件.所以backbone里的Model也不是纯Model,它有一部分Controller的功能。

而backbone里的View层也往往比较厚

var M = Backbone.Model.extend({  
  defaults:{name:"lisi"}  
});
 
var V = Backbone.View.extend({ 
	//new V时,触发初始化函数,进行对视图的监听 
  initialize:function(){
  	  //视图监听模型变化,一旦改变,触发回调函数
    this.listenTo(this.model, "change", this.modify);   
  }, 
  //回调函数 修改视图
  modify:funtion(model){
    $("#oBox").append(this.model.name);
  }
});
var m = new M();
// 视图中绑定模型
var v = new V({model:m});
//改变模型的name值时,就会触发change事件,执行回调函数modify,改变视图中的模型设置值。
m.set("name","wangwu");

这里view即包含了视图显示也包含了事件监听,注册了Model,属于View+Model,Controller被弱化了,更加注重了数据.

上述两个例子,我们可以看出,在backbone中,View和Model是可以互相直接影响的,而Controller的功能被分布在了View和Model中.
[Vue系列] Vue功能的简单实现_第3张图片
总结一下,backbone中的MVC有以下特性:

  1. 用户可以向 View 发送指令,再由 View 直接要求 Model 改变状态
  2. 用户也可以直接通过Controller(例子中未列出的路由Router)发送指令,触发hashChange事件,从而触发Model改变,影响View
  3. View和Model之间是互相影响的
  4. Controller被弱化,backbone中,Controller的体现更多的是在路由Router中

2.4 前端MVVM

MVVM(Model-View-ViewModel)最早由微软提出.ViewModel指 “Model of View”——视图的模型.这个概念曾在一段时间内被前端圈热炒,以至于很多初学者拿jQuery和Vue做对比…
其实两者之间没有什么可比性,完全是两个世界的东西.
MVVM更加注重的是数据,并且弱化了View层,我们不需要也不应该在视图层上做更多的DOM操作.
而jQuery更加注重的是DOM的操作.

下面,我们以Vue为例,简单介绍一下MV-VM.


<div id="oBox">{{name}}div>
<script>
	//viewmodel
	let vm = new Vue({
		el:"#oBox",
		//model
		data:{
			name:"lisi"
		}
	});	
script>

在上面的例子中 Vue 通过VM层实现了Model和View的数据绑定,当我从任何位置,哪怕是控制台更改name的值,View当中的name也会随之发生变化,我们无需关注DOM是如何操作的,只需要关注数据本身就可以了,VM会帮助我们自动完成数据的同步.

Model的变化会影响到View,同样的,View当中的数据变化也能够影响Model.


<div id="oBox">
	输入购买个数 : <input name="nums" v-model="num"> <br>
	总价: {{price()}} 元
div>
<script>
	//viewmodel
	let vm = new Vue({
		el:"#oBox",
		//model
		data:{
			num:0
		},
		methods:{
			price(){
				return parseInt(this.num) * 10;
			}
		}
	});	
script>

上例中的体现即为双向数据绑定,

我们简单总结一下MVVM
[Vue系列] Vue功能的简单实现_第4张图片

  1. 各部分之间的通信,都是双向的.
  2. View 与 Model 不发生联系,都通过 ViewModel 传递.
  3. View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性.
  4. ViewModel比较厚,业务逻辑主要部署在VM层
  5. 数据的绑定是双向的(绑定≠通信)

下面 我们步入正题,用原生JavaScript实现数据的绑定/双向数据绑定.

第三章 Vue功能 — 数据绑定

为了完成Vue中的数据绑定功能,我们使用了ES6中的Proxy.
Proxy可以理解为,在目标对象之前假设一层"拦截",外界对该对象的访问,都必须通过这层拦截.它可以对外界的访问进行过滤和改写.Proxy的意思是代理,我们可以把它称作"代理器";

ES6原生提供Proxy构造函数,用来生成Proxy对象

var proxy = new Proxy(target,handler);

target是用于代理的对象,而handler参数也是一个对象,用于定制拦截行为;

具体代理的行为可以分为

  • get 拦截某个属性的读取操作
  • set 拦截某个属性的赋值操作
  • apply 拦截函数的调用,call和apply操作

当然,除了Proxy以外,我们也可以使用Object.defineProperty()定义属性权限

书归正传,我们简单实现一下Vue的数据绑定/同步功能;

我们期待

  1. Model的变化和View是绑定的,Model发生了变化,View也会发生变化
  2. Model和View不直接通信,开发者可以更加关注数据,而不是View的变化

首先,我们用Proxy实现数据代理,其实也就是我们的VM层,直接跟View/Model进行通信;

<div id="oBox">
	姓名: {{name}}<br>
	年龄: {{age}}
div>
<script type="text/javascript">
	let el = document.getElementById('oBox');
	// 保留原始状态 以便多次编译
	let template = el.innerHTML;
	
	// Vue 使用 Proxy 完成数据代理 在数据变的时候更新html
	
	// 真正的数据
	let _data = {
		name:"whitsats",
		age:18
	}
	let data = new Proxy(_data,{
		// 监控数据修改
		set(obj,name,value){
			alert(`有人试图设置 ${name}=>${$value}`)
		}
	});
	
script>

在命令行修改data.name,会弹出相应的窗体;

[Vue系列] Vue功能的简单实现_第5张图片
那么剩下的工作其实就是在数据发生改变时,重新渲染模板了;
我们定义一个render函数,用于渲染html,它会在初始化和数据变化时对页面进行重新的渲染


<div id="oBox">
	姓名: {{name}}<br>
	年龄: {{age}}
div>

<script type="text/javascript">
	let el = document.getElementById('oBox');
	// 保留原始状态 以便多次编译
	let template = el.innerHTML;
	
	// Vue 使用 Proxy 完成数据代理 在数据变的时候更新html
	
	// 真正的数据
	//Model
	let _data = {
		name:"whitsats",
		age:18
	}
	// ViewModel
	let data = new Proxy(_data,{
		// 监控数据修改 obj即为_data 
		// 代理数据data修改,会影响到原始数据_data
		set(obj,name,value){
			obj[name] = value;
			render();
		}
	});
	
	render();
	function render(){
		// replace 正则替换的第一个参数为匹配到的字符串
		// 初次渲染 会将原始数据_data渲染到模板上
		// 代理数据data修改,会影响_data的值,我们仍然返回_data中的值
		el.innerHTML = template.replace(/\{\{\w+\}\}/g,str=>{
			str = str.substring(2,str.length-2);
			return _data[str];
		});
	}
script>

[Vue系列] Vue功能的简单实现_第6张图片
相信读者能够很清楚的知晓 M-V-VM各层的所在

第四章 Vue功能 — 双向数据绑定

我们上一步中的数据绑定是 Model发生变化 影响View变化,
我们期待的是通过View可以直接映射到Model变化,而Model变化同样可以导致View的更改.

首先做好模板

<div id="oBox">
	<input type="text" v-model="name" />
	姓名: {{name}}<br>
	年龄: {{age}}
div>

我们期望

  1. input框中的value在页面初始化时是_data原始数据中的name
  2. _data原始数据中的name发生变化,inputvalue值也同步了变化
  3. input中输入内容,同样也能影响到_data

看似是View和Model的互相影响,其实通过前面关于MVVM的描述中,VM才是实现双向数据绑定的关键,View和Model不应该有直接的关联.
为了实现上述功能,我们需要将VM功能完善一下

首先实现input的初始化工作
我们寻找带有v-model属性的input标签,并根据绑定的属性名称进行赋值

<body>

<div id="oBox">
	<input type="text" v-model="name" />
	姓名: {{name}}<br>
	年龄: {{age}}
div>

<script type="text/javascript">
	let el = document.getElementById('oBox');

	let template = el.innerHTML;
	// Model
	let _data = {
		name:"whitsats",
		age:18
	}
	// ViewModel
	let data = new Proxy(_data,{

		set(obj,name,value){
			obj[name] = value;
			render();
		}
	});
	
	render();
	function render(){
		el.innerHTML = template.replace(/\{\{\w+\}\}/g,str=>{
			str = str.substring(2,str.length-2);
			return _data[str];
		});
		// 寻找v-model
		// 1. 格式化元素为数组
		// 2. 筛选数组中含有v-model属性的input标签
		// 3. 循环v-model属性标签,并赋予相应的值
		Array.from(el.getElementsByTagName('input'))
			.filter(ele=>ele.getAttribute('v-model'))
			.forEach(input=>{
				let name = input.getAttribute('v-model');
				input.value = _data[name];
			});
	}
script>

[Vue系列] Vue功能的简单实现_第7张图片
成功渲染之后,我们需要完成View到Model的绑定,同样需要VM来完成,这里肯定会使用到事件监听;


<div id="oBox">
	<input type="text" v-model="name" />
	姓名: {{name}}<br>
	年龄: {{age}}
div>
	
<script type="text/javascript">
	let el = document.getElementById('oBox');

	let template = el.innerHTML;
	//Model
	let _data = {
		name:"whitsats",
		age:18
	}
	//ViewModel
	let data = new Proxy(_data,{

		set(obj,name,value){
			obj[name] = value;
			render();
		}
	});
	
	render();
	function render(){
		el.innerHTML = template.replace(/\{\{\w+\}\}/g,str=>{
			str = str.substring(2,str.length-2);
			return _data[str];
		});
		// 寻找v-model
		Array.from(el.getElementsByTagName('input'))
			.filter(ele=>ele.getAttribute('v-model'))
			.forEach(input=>{
				let name = input.getAttribute('v-model');
				input.value = _data[name];
				input.oninput = function(){
					//此处只是通过事件通知代理数据进行变化
					//代理数据一旦变化, 触发set,重新渲染
					data[name] = this.value;
				}
			});
		
	}
script>

[Vue系列] Vue功能的简单实现_第8张图片

后记

坑算是开了XD!
我比较懒,系列文章可能更新的慢一些
在不同平台上开的博客也都长了草了
emmm…尽快更新吧

你可能感兴趣的:(前端)