前端发展史

什么是前端

前端工程师主要利用HMTL与CSS构建页面,用JavaScript完善交互以及用户体验。

诞生

1994年可以看做前端历史的起点,这一年10月13日网景推出了第一版Navigator;这一年,Tim Berners-Lee创建了W3C;这一年,Tim的基友发布了CSS(层叠样式表)。1995年,当时在网景公司就职的布兰登·艾克正为Netscape Navigator 2.0浏览器开发的一门名为LiveScript的脚本语言,后来网景公司与昇阳电脑公司组成的开发联盟为了让这门语言搭上java这个编程语言“热词”,将其临时改名为“JavaScript”,日后这成为大众对这门语言有诸多误解的原因之一。

万维网(WWW)是欧洲核子研究组织的一帮科学家为了方便看文档、传论文而创造的,这就是为什么Web网页都基于Document。Document就是用标记语言+超链接写成的由文字和图片构成的HTML页面,这样的功能已经完全满足学术交流的需要,所以网页的早期形态和Document一样,完全基于HTML页面,并且所有内容都是静态的。

1999年W3C发布第四代HTML标准,同年微软推出用于异步数据传输的ActiveX,随即各大浏览器厂商模仿实现了XMLHttpRequest。这标识着Ajax的诞生,但是Ajax这个词是在六年后问世的,特别是在谷歌使用Ajax技术打造了Gmail和谷歌地图之后,Ajax获得了巨大的关注。Ajax是Web网页迈向Web应用的关键技术,它标识着Web2.0时代的到来。

2006年,XMLHttpRequest被W3C正式纳入标准。

至此,早期的Document终于进化为了Web page,上述三个局限都得到了妥善的解决。

远古时代(web 1.0)

那时候的web开发还属于纯静态开发。开发者在web服务器的指定文件夹(/www)下,放置一下html文件,当浏览器请求的页面的时候返回相应的html文件。

但是,渐渐的纯静态的页面不足以满足产品的发展需求(例如:统计该网站的访问流量,提交用户的输入信息)。这时候,大约2005年左右,出现了Java Server Pages(JSP),微软的ASP,以及Ajax!你可以基于jsp和Ajax快速构建一个可伸缩并且安全的应用程序。

石器时代 (web 2.0)

因为当时浏览器厂商的混乱(例如:ie, chrome, firefox等),拥有不同内核的浏览器Trident(IE内核),Gecko(Firefox 内核),webkit(Safari 内涵), Chromium/Blink(Chrome内核),Presto(Opera内核)等。当时的浏览器厂商的标准都不一样。比如写一个点击事件需要做一些兼容性处理。

1
var e = e || window.event //处理ie浏览器和其他浏览器之间的事件兼容问题

这时候为了处理浏览器的兼容性,出现了YUI 和 jQuery, 主要解决了DOM元素选择,CSS操纵,事件系统处理。同时也可以兼顾一下动画效果的实现和一些常用插件的封装(表单验证,日历插件等)。

1
2
3
4
5
6
7
8
9
10
//jQuery
$("p.surprise").addClass("ohmy").show("slow");
$("p.surprise").on("click", function (e) {
console.log(e.target);
});
//YUI
YUI().use('node', 'event', function (Y) {
// The Node and Event modules are loaded and ready to use.
// Your code goes here!
});

模块化时代

最开始JavaScript承担的任务量并不多,表单验证基本上就是他的全部,最多就是简短的前端交互,这个时期JavaScript组织结构非常凌乱,大部分都是后端顺手代劳,那时候还没有“前端”这一职位。 一般都是写到一个文件或者直接写到jsp、asp的后端模板页面上就完事了。随着ajax的流行,前端能做的东西一夜之间暴涨,代码量飞速增加,单文件维护代码已经太沉重,多人开发全局变量泛滥,代码合并引起冲突覆盖等问题。最开始的解决的方式是用匿名函数包裹自己的代码,这样就不会把定义的变量暴露到全局作用域里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//立即执行函数写法 
var module1 = (function(){
var m1 = function(){
//业务代码
};
return {
m1 : m1
};
})();
//可以传入参数
var module1 = (function ($, undefined) {
  //业务代码
})(jQuery);
//命名空间方式
var namespace = {};
namespace.xxx = function () {...}

随着网站逐渐的发展,嵌入网页的Javascript代码越来越庞大,而网页越来越像桌面程序,需要一个团队去分工协作,进行管理和测试等等,为了更好的管理网页的业务逻辑,处理代码之间的依赖关系,产生了模块化编程的理念。

2008年9月2日,Google为旗下浏览器Chrome加入了V8 JavaScript处理引擎,可以更快速,更稳定,更安全的处理JavaScript代码。2009年,Ryan Dahl结合Google的 V8引擎、事件驱动模式、低级I\O接口开发了Node.js。

2010年1月,一款名为’npm‘的软件包管理系统诞生,随之而来的还有commonJS 规范

  1. 模块的标识应遵循的规则(书写规范)
  2. 定义全局函数require,通过传入模块标识来引入其他模块,执行的结果即为别的模块暴漏出来的API
  3. 如果被require函数引入的模块中也包含依赖,那么依次加载这些依赖
  4. 如果引入模块失败,那么require函数应该报一个异常
  5. 模块通过变量exports来向往暴漏API,exports只能是一个对象,暴漏的API须作为此对象的属性。
1
2
3
4
5
6
7
8
9
10
11
//math.js
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
//add.js
var add = require('math').add;
add(val, 1);

但是Node.js主要是用于服务器端,无法直接用在浏览器端,主要表现在服务端require一个模块,直接就从硬盘或者内存中读取了,消耗的时间可以忽略。而浏览器则不同,需要从服务端来下载这个文件,然后运行里面的代码才能得到API,需要花费一个http请求,也就是说,require后面的一行代码,需要资源请求完成才能执行。由于浏览器端是以插入<script>标签的形式来加载资源的(ajax方式不行,有跨域问题),没办法让代码同步执行,所以像commonjs那样的写法会直接报错。

所以,社区意识到,要想在浏览器环境中也能模块化,需要对规范进行升级。而就在社区讨论制定下一版规范的时候,内部发生了比较大的分歧,产生了不同的规范。

AMD/RequireJs的崛起与妥协
  1. 用全局函数define来定义模块,用法为:define(id?, dependencies?, factory);
  2. id为模块标识,遵从CommonJS Module Identifiers规范
  3. dependencies为依赖的模块数组,在factory中需传入形参与之一一对应
  4. 如果dependencies的值中有”require”、”exports”或”module”,则与commonjs中的实现保持一致
  5. 如果dependencies省略不写,则默认为[“require”, “exports”, “module”],factory中也会默认传入require,exports,module
  6. 如果factory为函数,模块对外暴漏API的方法有三种:return任意类型的数据、exports.xxx=xxx、module.exports=xxx
  7. 如果factory为对象,则该对象即为模块的返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//页面中需要引入require.js
//data-main 的作用是告诉 require.js 加载 main.js的代码.
// <script data-main="scripts/main" src="scripts/require.js"></script>
// math.js
define(function (){
var add = function (x,y){
return x+y;
};
return {
add: add
};
});
//main.js
require(['math'], function (math){
math.add(1,1);
});
兼容并包的CMD/seajs

seajs的作者是国内大牛, 淘宝前端玉伯。

  1. 一个文件一个模块,所以经常就用文件名作为模块id
  2. CMD推崇依赖就近,所以一般不在define的参数中写依赖,在factory中写
  3. factory是一个函数,有三个参数,function(require, exports, module)
    • require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口:require(id)
    • exports 是一个对象,用来向外提供模块接口
    • module 是一个对象,上面存储了与当前模块相关联的一些属性和方法
1
2
3
4
5
6
7
8
9
10
11
12
<script src="sea.js" data-main="main.js"></script>
//main.js
seajs.use(['./hello', 'jquery'], function(hello, $) {
$('#beautiful-sea').click(hello.sayHello);
});
//hello
define(function(require, exports, module) {
var $ = require('jquery');
exports.sayHello = function() {
$('#hello').toggle('slow');
};
});

AMD与CMD区别主要是:

  1. AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块
  2. CMD推崇就近依赖,只有在用到某个模块的时候再去require
面向未来的ES6模块标准

ECMAScript是由ECMA-262标准化的脚本语言的名称。JavaScript和JScript与ECMAScript兼容,但包含超出ECMAScript的功能。ECMA-262的第一个版本于1997年6月被Ecma组织采纳。

2011 年,ECMAScript 5.1(ES5) 版发布。也是前端快速发展的一段时间。上面大部分模块解决方案,jQuery等框架都是基于ECMAScript 5.1规范开发的。2015 年,负责制定 ECMAScript 规范草案的委员会决定将定义新标准的制度改为一年一次。ES6的第一个版本在2015 年 6 月发布,正式名称就是《ECMAScript 2015 标准》(简称 ES2015/ES6),2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016/ES7),2017年6月,《ECMAScript 2017 标准》(简称 ES2017/ES8)。我们统一把ECMAScript 2015标准之后的版本称为ES6.

模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

1
2
3
4
5
6
7
//math.js
export function add(x, y) {
return x+y;
}
//app.js
import {add} from './math';
add(1, 2)

但是由于ES6的标准太超前,浏览器的更新无法跟上,所以还不能直接应用到浏览器里面。通过Babel 语法转换器在本地编译成浏览器可识别的ES5代码。

未来

前端发展到模块化时代变得越来正规,团队之间的配合也越来越重,遇到的问题越来越多,通过借鉴传统的后端开发模式,衍生出了自己的JavaScript设计模式(单例模式,观察者模式,原型模式等),也有了自己的架构模式(MVC,MVVM,MVP)。

根据不同的设计模式有出现了不同的前端框架(Ext JS, jQuery UI, jQuery Mobile, Dojo, Prototype JS, Kissy, React, Vue, Angular等) 。

既然JavaScript出现了各种不同的模块解决方案,CSS也需要跟上JavaScript模块化进程(Sass, Less, Stylus)。

HTML也出现了各种模版系统(Handlebars,EJS等)。

各种编译工具(grunt, gulp, webpack等)。

最后, 我们期待无法预测的未来。

It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way- in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only.

那是最好的時代,那是最壞的時代;那是智慧的歲月,那是愚昧的歲月;那是信任的紀元,那是懷疑的紀元;那是光明的季節,那是黑暗的季節;那是希望的春天,那是失望的冬天;我們的前面曾應有盡有,我們的前面曾一無所有;我們都將走上天堂,我們都將走向另一端–簡言之,那段時期與現在的時期是那麼相似,那些最吵雜的權威人士都堅持,不論是善良或邪惡,用最高級形容詞來評論。