模块化:
一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。模块开发需要遵循一定的规范,各行其是就都乱套了。
1、AMD (用于浏览器):即Asynchronous Module Definition,中文名是异步模块定义的意思。它是一个在浏览器端模块化开发的规范。
由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎RequireJS,实际上AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出。
require(['Service'],function (Service){ });
requireJS主要解决两个问题
(1)、多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器;
(2)、js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长;
2、CMD:即Common Module Definition通用模块定义,CMD规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同。
//可以兼容 commonJS 的写法, 可以不写
define(function (require, exports, module) {
var React = require('react');
});
CMD推崇:
(1)、一个文件一个模块,所以经常就用文件名作为模块id;
(2)、CMD推崇依赖就近,所以一般不在define的参数中写依赖;
3、CommonJS (用于服务器) node应用由模块组成,采用的commonjs模块规范。每一个文件就是一个模块,拥有自己独立的作用域,变量,以及方法等,对其他的模块都不可见。CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。require方法用于加载模块。
CommonJS模块的特点如下:
(1)、所有代码都运行在模块作用域,不会污染全局作用域;
(2)、模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存;
(3)、模块加载的顺序,按照其在代码中出现的顺序;
module对象:
1.module.exports属性
module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。
2.exports变量
node为每一个模块提供了一个exports变量(可以说是一个对象),指向 module.exports。这相当于每个模块中都有一句这样的命令 var exports = module.exports;
这样,在对外输出时,可以在这个变量上添加方法。例如 exports.add = function (r){return Math.PI * r *r};注意:不能把exports直接指向一个值,这样就相当于切断了 exports 和module.exports 的关系。例如 exports=function(x){console.log(x)};
一个模块的对外接口,就是一个单一的值,不能使用exports输出,必须使用 module.exports输出。module.exports=function(x){console.log(x);};
AMD规范和commonJS规范:
1.相同点:都是为了模块化。
2.不同点:AMD规范则是非同步加载模块,允许指定回调函数。CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。
基本用法:
example.js:
exports.name = 'tom';
exports.age = 50;
var example = require('./example.js');
4、ES6 Modules ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
ES6 的模块化分为导出(export)与导入(import)两个模块
特点:
(1)、ES6 的模块自动开启严格模式,不管你有没有在模块头部加上 use strict;
(2)、模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等;
(3)、每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域;
(4)、每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取;
用法:
ES6 modules使用export关键字对外提供接口:
export default {}
export {}
引入模块:
用import表示引入一个模块
import test from './test'
5、二者的区别:
最明显的区别就是在模块定义时对依赖的处理不同
(1)、AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块
(2)、CMD推崇就近依赖,只有在用到某个模块的时候再去require
这种区别各有优劣,只是语法上的差距,而且requireJS和SeaJS都支持对方的写法 AMD和CMD最大的区别是对依赖模块的执行时机处理不同,注意不是加载的时机或者方式不同
组件化:
为什么要做组件化?无论前端也好,后端也好,都是整个软件体系的一部分。软件产品也是产品,它的研发过程也必然是有其目的。绝大多数软件产品是追逐利润的,在产品目标确定的情况下,成本有两个途径来优化:减少部署成本,提高开发效率。
减少部署成本的方面,业界研究得非常多,比如近几年很流行的“去IOE”,就是很典型的,从一些费用较高的高性能产品迁移到开源的易替换的产品集群,又比如使用Linux + Mono来部署.net应用,避开Windows Server的费用。
说到组件化,最常见的莫属MVVM框架了,比如React、Angular、Vue。
React的编程模式其实不必特别考虑Web标准,它的迁移成本并不算高,甚至由于其实现机制,屏蔽了UI层实现方式,所以大家能看到在native上的使用,canvas上的使用,这都是与基于DOM的编程方式大为不同的,所以对它来说,处理Web Components的兼容问题要在封装标签的时候解决,反正之前也是要封装。React使用virtual dom,所以性能很好,而且一切都是component,所以代码更加模块化,重用代码更容易。Angular 1.x的版本,可以说是跟同时代的多数框架/库一样,对未来标准的兼容基本没有考虑,但是重新规划之后的2.0版本对此有了很多权衡,变成了激进变更,突然就变成一个未来的东西了。
复用性:
所谓组件化,核心意义莫过于提取真正有复用价值的东西。那怎样的东西有复用价值呢? 1、控件 2、基础逻辑功能 3、公共样式 4、稳定的业务逻辑 5、对于控件的可复用性,基本上是没有争议的,因为这是实实在在的通用功能,并且比较独立。
基础逻辑功能主要指的是一些与界面无关的东西,比如underscore这样的辅助库,或者一些校验等等纯逻辑功能。
公共样式的复用性也是比较容易认可的,因此也会有bootstrap,foundation,semantic这些东西的流行,不过它们也不是纯粹的样式库了,也带有一些小的逻辑封装。
最后一块,也就是业务逻辑。这一块的复用是存在很多争议的,一方面是,很多人不认同业务逻辑也需要组件化,另一方面,这块东西究竟怎样去组件化,也很需要思考。
除了上面列出的这些之外,还有大量的业务界面,这块东西很显然复用价值很低,基本不存在复用性,但仍然有很多方案中把它们“组件化”了,使得它们成为了“不具有复用性的组件”。
组件化的本质目的并不一定是要为了可复用,而是提升可维护性。这一点正如面向对象语言,Java要比C++纯粹,因为它不允许例外情况的出现,连main函数都必须写到某个类里,所以Java是纯面向对象语言,而C++不是。
对于一个有一定规模的Web应用来说,把所有东西都“组件化”,在管理上会有较大的便利性。同样是编写代码,短代码明显比长代码的可读性更高,所以很多语言里会建议“一个方法一般不要超过多少行,一个类最好不要超过多少行”之类。在Web前端这个体系里,javascript这块是做得相对较好的,现在入门水平的人,也已经很少会有把一堆js都写在一起的了。CSS这块,最近在SASS,LESS等框架的引领下,也逐步往模块化方面发展,否则直接编写bootstrap那种css,会非常痛苦。
Promise与异步 前端一般都习惯于用事件的方式处理异步,但很多时候纯逻辑的“串行化”场景下,这种方式会让逻辑很难阅读。在新的ES规范里,也有yield为代表的各种原生异步处理方案,但是这些方案仍然有很大的理解障碍,流行度有限,很大程度上会一直停留在基础较好的开发人员手中。尤其是在浏览器端,它的受众应该会比node里面还要狭窄。
前端里面,处理连续异步消息的最能被广泛接受的方案是promise,我这里并不讨论它的原理,也不讨论它在业务中的使用,而是要提一下它在组件化框架内部所能起到的作用。
现在已经没有哪个前端组件化框架可以不考虑异步加载问题了,因为,在前端这个领域,加载就是一个绕不过去的坎,必须有了加载,才能有执行过程。每个组件化框架都不能阻止自己的使用者规模膨胀,因此也应当在框架层面提出解决方案。
我们可能会动态配置路由,也可能在动态加载的路由中又引入新的组件,如何控制这些东西的生命周期,值得仔细斟酌,如果在框架层面全异步化,对于编程体验的一致性是有好处的。将各类接口都promise化,能够在可维护性和可扩展性上提供较多便利。
前端路由 前端的路由和后端的路由在实现技术上不一样,但是原理都是一样的。在 HTML5 的 history API 出现之前,前端的路由都是通过 hash 来实现的,hash 能兼容低版本的浏览器。它的 URI 规则中需要带上 #。
Web 服务并不会解析 hash,也就是说 # 后的内容 Web 服务都会自动忽略,但是 JavaScript 是可以通过window.location.hash 读取到的,读取到路径加以解析之后就可以响应不同路径的逻辑处理。
history 是 Html5 才有的新 API,可以用来操作浏览器的 session history (会话历史)。基于 history 来实现的路由可以和最初的例子中提到的路径规则一样。
从性能和用户体验的层面来比较的话,后端路由每次访问一个新页面的时候都要向服务器发送请求,然后服务器再响应请求,这个过程肯定会有延迟。而前端路由在访问一个新页面的时候仅仅是变换了一下路径而已,没有了网络延迟,对于用户体验来说会有相当大的提升。