currentFrontEnd

Front-end evolution

简单介绍一下我理解的前端技术演进…

第一阶段:刀耕火种

基本的事件响应机制

  • 获取view上的dom
  • 对dom进行事件绑定
  • 根据事件响应改变视图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!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>
<label>用户输入<label for="userInput"></label><input type="text" id="userInput">
<button id="submitButton">提交</button>
<p id="userComment"></p>
</body>
<script>
// 页面dom加载完毕
window.onload = function () {
var userInput = bindDom('userInput');
var submitButton = bindDom('submitButton');
var userComment = bindDom('userComment');
submitButton.addEventListener('click',function(){
userComment.innerHTML = userInput.value;
});
};

// bind dom
function bindDom(domID){
return document.getElementById(domID)
}
</script>
</html>

demo1

频繁的操作dom,性能差,视图view和业务逻辑的js文件揉在一起,维护性差,可读性差,无法复用

第二阶段:前端模块化

  • 什么问题导致我们需要前端页面进行组件化?
  • 组件化究竟时要解决什么问题?
    答:为了解决结构服用,避免产生重复冗余的代码

模块化第一阶段:原始的组建化写法(在全局对象上定义模块的属性和方法)

模块就是实现特定功能的一组方法-所以把不同的函数和变量简单的放在一起就算是一个模块

1
2
3
4
5
6
7
8
9
var name = 'pis';
var age = 18;
function getName(){
return this.name
}

function getAge(){
return this.age
}

getName getAge方法组成了一个模块,使用的时候直接调用就可以了。

缺点:

污染了全局变量
不能保证在引入其他模块的时候不会发生变量名冲突和方法被overwrite
模块成员之间看不出直接关系

模块化第二阶段:对象的写法

为了解决上面的问题,可以把模块的方法放到一个对象里面,这样就解决了上面的问题

1
2
3
4
5
6
7
8
9
10
var module = {
name:'unclepis',
age:18,
getName:function(){
return this.name
},
getAge:function(){
return this.age
}
}

这样所有的模块方法都封装在了对象里面,使用的时候就是调用这个对象的方法就可以了

module.getName() // unclepis

缺点:

暴漏了所有的模块成员
内部的状态可以被外部改写 module.name = ‘eric’

模块化第三阶段:自执行函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

var module = (function(){
var name = 'cindy';
var age = 18;
var getName = function(){
return name;
}
var getAge = function(){
return age;
}
return{
getName:getName,
getAge:getAge
}
})();

这样的写法使得外部不能获取内部变量,又对外暴漏了模块的接口,所以这就是在es6模块化之前比较主流的模块化写法

自执行函数模块化扩展1:放大模式+自执行函数

如果一个模块很大或者一个模块需要依赖另一个模块,这时候就需要使用放大模式

1
2
3
4
5
6
7

var getMoreInfo = (function(mod){
mod.getJobInfo = function(){
// 引入前面封装好的module,已经有了getName和getAge的方法,又引入了新的方法getjobinfo
}
return mod;// 返回新的模块
})(module);

自执行函数模块化扩展2:

由于浏览器环境中,模块的各个部分通常都是从网上获取的。无法控制资源的加载顺序(requirejs这种amd的方式先不考虑),所以上面引入的module可能在执行的时候还是一个空对象

1
2
3
4
5
6
7

var getMoreInfo = (function(mod){
mod.getJobInfo = function(){
// 引入前面封装好的module,已经有了getName和getAge的方法,又引入了新的方法getjobinfo
}
return mod;// 返回新的模块
})(module||{}); // 所以需要给模块设置默认的空对象

第三阶段:模块化规范

通过上面的介绍,我们大概知道了,如何使用自执行函数来封装一组方法集合的模块,这样在模块中对外暴漏了接口方法,但是又不污染全局变量,而且不能在外部随意更改模块的内部变量。
虽然这样我们有了模块,可以很方便的服用别人的代码,想要什么功能就加载什么功能,但是每个人的写法可能不一样,所以模块化的规范在社区中慢慢衍生出来…

目前主流的js模块化规范主要包括三个:

  • CMD(common module defination)通用模块化定义
  • AMD(Asynchronous module defination) 异步模块化定义

CMD

  • 09年nodejs项目使得js可以运行在服务器端。这也就标志着js模块化变成正式诞生
  • 浏览器环境下网页的复杂性有限,没有模块化问题不是很大,但是运行在服务器端,与操作系统和其他应用程序相互互动,模块化势在必行,否则没发编程
  • nodejs的模块系统就是参考commonjs规范实现的,在commonjs中有一个全局性的方法require用于加载模块
1
2
var jquery = require('./jquery.js');
jquery.Ajax(); //这样就可以调用了

在服务器端运行的时候,所有的模块都存放在服务器本地的磁盘上,等待的事件也就是硬盘读取的时间
但是如果运行在浏览器上的化,网络的加载可能会使得模块的加载出现⌛️,也就是出现我们常说的页面假死…因此浏览器端不能使用commonjs这种同步加载的方法,
只能采用异步加载的方式,这也就诞生了AMD规范…

AMD

AMD是”Asynchronous Module Definition”的缩写,意思就是”异步模块定义”。
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。
所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

1
2
3
4
//AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
require(['math',function(math){
math.add(2,3);
}])

math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。

为什么引入amd?

最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了。后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载。下面的网页代码,相信很多人都见过。

1
2
3
4
5
6
<script src="1.js"></script>
  <script src="2.js"></script>
  <script src="3.js"></script>
  <script src="4.js"></script>
  <script src="5.js"></script>
  <script src="6.js"></script>

这段代码依次加载多个js文件。

这样的写法有很大的缺点。

  • 首先,加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长;
  • 其次,由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js要在2.js的前面),依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。

所以amd的规范就是为了解决这个问题:
(1)实现js文件的异步加载,避免网页失去响应;
(2)管理模块之间的依赖性,便于代码的编写和维护。

第四阶段:前端mvc架构

构架模式

如何设计一个程序的结构,就是一个架构模式,属于编程的方法论…(演示需要将demo用http-server跑起来)

mvc构架模式

MVC就是一种常用的软件构架模式,这个模式任务,任何程序,无论简单与否,从结构上都分为三层:分别代表:
mvc构架

  • Modal模型 (数据存储)
  • view视图 (用户界面)
  • controller控制 (业务逻辑)

step1: View 传送指令到 Controller
step2: Controller 完成业务逻辑后,要求 Model 改变状态
step3: Model 将新的数据发送到 View,用户得到反馈

  • 基本的MVC构架模式,所有通信都是单项的

实际的业务构架

mvc实际构架
基本上就可以覆盖我们常见的大多数场景

视图层上的用户交互,通过控制器,对数据模型进行操作,然后数据模型的变化也会动态的更新到视图上…

mvp/mvvm构架模式(model view Presenter)/(model view viewModel)

在MVC的基础上,进而演进出了视图view和数据模型model的双向绑定
mvvm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!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>
<label>用户输入<label for="userInput"></label><input type="text" id="userInput">
<p id="userComment"></p>
</body>
<script>
// 页面dom加载完毕
window.onload = function () {
// view视图上dom元素
var userInput = bindDom('userInput');
var userComment = bindDom('userComment');

// model数据
window.data = {
userInput: 'uncle pis'
};

userInput.value = data.userInput; // 使用model的数据初始化视图的显示
userComment.innerHTML = data.userInput; // 使用model的数据初始化视图的显示

// 数据双向绑定是通过Object.definePropery的get和set对数据进行劫持,监听数据的变化
Object.defineProperty(window.data,'userInput', {
set: function (val) {
userComment.innerText = val;
userInput.value = val;
},
get: function () {
console.log('get User input ');
}
})

userInput.addEventListener('keyup', function (e) {
data.userInput = e.target.value;
});
};

// bind dom
function bindDom(domID) {
return document.getElementById(domID)
}


</script>

</html>

demo2

观察者模式和发布订阅模式的有什么区别?

在软件设计中是一个对象,维护一个依赖列表,当任何状态发生改变自动通知它们

subjectObserver
简单的说就好比找工作:
目标公司(Subject)
找工作的人 (Observers)
职位空缺(消息事件)
所以 当职位空缺的时候,目标公司就会通知找工作的人

发布订阅
在发布-订阅模式,消息的发送方,叫做发布者(publishers),消息不会直接发送给特定的接收者(订阅者)。发布者和订阅者不知道对方的存在。需要一个第三方组件,叫做信息中介,它将订阅者和发布者串联起来,它过滤和分配所有输入的消息

subscribe

简单的说就好比买卖房子
房主(publisher)就是发布者
房产中介(信息中介)
买房子的人(订阅者)

房主和买房子的人不知道对方的存在,消息通过中介进行传输

区别
在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。
然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
diff

数据劫持和观察者模式

上面的例子,我们通过object.definePropery方法,通过get和set方法对data数据进行了劫持;
然后在数据发生变化的时候,通知给视图上的观察者,来完成视图和数据的关联。

第五阶段:前后端分离+Ajax+restful

标量-序列-映射

从yaml文档中说,从结构上看,所有的数据data最终都可以分解成三种类型
1)第一种是标量(scalar),也就是一个单独的字符串string或者数组numbers
2)第二种是序列(sequence),也就是若干个有序的数据并列在一起,又叫做数组array或是列表list
3)第三种是映射(mapping),也就是一个健值对(key/value),即数据又一个名字,还有一个对应的值,着又称作散列(hash)或是字典(dictionary)

JSON

而21世纪初期,就是一个叫做json的数据格式代替了xml在服务器间进行数据交换传输。什么格式的数据是json?

  • 并列数据的集合(数组)用方括号([])表示
  • 并列的数据之间用逗号(“,”)分开
  • 映射用冒号(“:”)表示
  • 映射的集合(对象)用话括号({})表示

上面四条规则,就是Json格式的所有内容。
例如:宁波和利时应用开发部有20人,java开发15人,前端开发4人,测试1人就可以用json表示成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
company:'宁波和利时'
total_people:20,
department:'应用开发部门'
role:[{
number:15,
name:'java'
},{
number:4,
name:'js'
},{
number:1,
name:'测试'
}]
}

注:js中数组和对象的区别
学习javascript的时候,我曾经一度搞不清楚”数组”(array)和”对象”(object)的根本区别在哪里,两者都可以用来表示数据的集合。

比如有一个数组a=[1,2,3,4],还有一个对象a={0:1,1:2,2:3,3:4},然后你运行alert(a[1]),两种情况下的运行结果是相同的!这就是说,数据集合既可以用数组表示,也可以用对象表示,那么我到底该用哪一种呢?

我后来才知道,数组表示有序数据的集合,而对象表示无序数据的集合。如果数据的顺序很重要,就用数组,否则就用对象。

初到贵宝地,有钱的给个钱场,没钱的挤一挤给个钱场