Javascript模块化编程学习小结
2016-04-04 10:43
519 查看
拥抱模块化的javascript
随着越来越多的后端逻辑转移到前端,网页中的js代码量也是与日俱增,只是靠函数式的方式来组织代码已经渐渐变得力不从心,这时类的方式出现了,Prototype、YUI、Ext等框架渐渐流行开来,以满足大量javascript代码的开发。
而紧接着nodejs横空出世,这个服务端的javascript采用模块化的写法很快就在前端领域产生了巨大影响,大牛们纷纷效仿,各种模块化的规范也是浮出水面。CommonJS想统一前后端的模块写法,但AMD却被认为是最适合浏览器的,无论如何,模块化编程已经在javascript中流行了起来。
但是javascript本身并不是一种模块化编程语言,它不支持类(class),更别说模块(module)了,虽然ECMAScript第六版已经开始支持类和模块,但目前来说,了解javascript模块化编程依然是很有必要的。Javascript社区做了很多努力,在现有运行环境中,实现了模块的效果。
一、模块基本写法-信息隐藏
模块应设计的使其所包含的信息(过程和数据)对于那些不需要用到它的其他模块不可见。每个模块只完成一个独立工程,然后提供该功能的接口。模块间通过接口访问。Javascript中可以用函数去隐藏,封装,然后返回接口对象。比如:
二、模块的升级版写法
如果一个模块很大,必须分成很多部分,每个部分可能新增某些功能,或者模块之间需要有继承关系,这时就需要用这种“放大模式”的写法:
三、输入全局变量
独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。
为了在模块内部调用全局变量,必须显式地将其他变量输入模块。
四、模块规范之CommonJS
2009年,美国程序员Ryan Dahlchu创造了node.js项目,将javascript语言用于服务器端编程。
node.js的模块系统,就是参照CommonJS规范实现的。在CommonJS中,有一个全局性方法require(),用于加载模块。假设有一个模块math.js,就可以像下面这样加载并调用其方法。
但有一个重大局限,采用这种方式加载模块是同步完成的,也就是必须等某一个模块加载完成后才能运行下面的语句,如果加载时间很长,整个应用都会卡在那里等。
这对服务端不是问题,因为所有模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但这显然并不适合浏览器,因此,浏览器端的模块,不能采用“同步加载”,只能采用“异步加载”,于是AMD诞生了。
五、模块规范之AMD
AMD是“Asynchronous Module Definition”的缩写,意思是“异步模块定义”。所有以来这个模块的语句,都定义在一个回调函数中。
AMD也采用require()语句加载模块,但它要求两个参数:
若将前面的代码改成AMD形式,就是下面这样:
六、require.js
为什么要用require.js?
下面这段代码,相信很多人都见过。
于是,require.js诞生了。
(1)实现js文件的异步加载,避免网页失去响应;
(2)管理模块之间的依赖性,便于代码的编写和维护。
require.js的加载
先去官网下载最新版本,下载后,根据放置目录就可以加载了。
现在可以进一步加载自己的代码了,假设我们的代码文件是main,js,也在js目录下:
七、主模块的写法
主模块一般会依赖于其他模块,这时就要使用AMD规范定义的require()函数。
实际例子:
八、模块的加载
上节的写法是默认了moduleA、moduleB、moduleC与main.js在同一目录下,而实际情况当然要复杂一些,因此我们可以采用require.config()方法,对模块的加载行为进行自定义。
九、AMD模块的写法
具体来说,就是模块必须采用特定的额define()函数来定义。如果一个模块不依赖其他模块,则直接定义在define()函数中。
十、requireJS路劲解析问题
baseUrl:基础中的基础
首先需要说明,在requirejs的模块路劲解析里,baseUrl是非常基础的概念,所有的路劲都是requirejs基于baseUrl指定的路劲来寻找。
举个栗子,如下:
在demo.html里加载了require.js,同时在require.js所在的script上申明了data-main属性,那么,requirejs加载下来后,它会做两件事:
1、加载js/main.js
2、将baseUrl设置为data-main指定的文件所在的路劲,这里是js/
那么,下面依赖的lib模块实际路劲为js/lib.js
baseUrl + path:让依赖更简洁、灵活
原始代码:
paths:简单但需要记住的要点
看看下面三种情况:
关于define()内部依赖模块路劲解析问题
首先来个例子:
下面改个写法:
为什么会出现这个问题?这个对于初识requirejs并且没看过源码的人确实难懂,你只需要记住以下几点:
1、对于baseUrl的解析需要注意,当满足以下条件,将不会相对baseUrl
(1)以“/”开头
(2)以“.js”结尾
(3)包含各种协议
2、最终lib.js中解析的依赖模块路劲跟main.js中require()函数中的依赖模块路劲的parent有关。什么是parent?举个栗子:
关于其背后的深入原因,有兴趣的同学可以看看叶小钗大神写的这篇博客requireJS路劲加载。
随着越来越多的后端逻辑转移到前端,网页中的js代码量也是与日俱增,只是靠函数式的方式来组织代码已经渐渐变得力不从心,这时类的方式出现了,Prototype、YUI、Ext等框架渐渐流行开来,以满足大量javascript代码的开发。
而紧接着nodejs横空出世,这个服务端的javascript采用模块化的写法很快就在前端领域产生了巨大影响,大牛们纷纷效仿,各种模块化的规范也是浮出水面。CommonJS想统一前后端的模块写法,但AMD却被认为是最适合浏览器的,无论如何,模块化编程已经在javascript中流行了起来。
但是javascript本身并不是一种模块化编程语言,它不支持类(class),更别说模块(module)了,虽然ECMAScript第六版已经开始支持类和模块,但目前来说,了解javascript模块化编程依然是很有必要的。Javascript社区做了很多努力,在现有运行环境中,实现了模块的效果。
一、模块基本写法-信息隐藏
模块应设计的使其所包含的信息(过程和数据)对于那些不需要用到它的其他模块不可见。每个模块只完成一个独立工程,然后提供该功能的接口。模块间通过接口访问。Javascript中可以用函数去隐藏,封装,然后返回接口对象。比如:
var math = (function(){ var count = 0; var add = function(){ //... }; var sub = function(){ //... }; return { add: add; sub: sub }; })();这是javascript模块的基本写法,此时外部无法访问也无法修改count变量。
二、模块的升级版写法
如果一个模块很大,必须分成很多部分,每个部分可能新增某些功能,或者模块之间需要有继承关系,这时就需要用这种“放大模式”的写法:
var module = (function(mod){ mod.m1 = function(){ //... }; return mod; })(module);在浏览器环境中,模块的各个部分通常都是网上获取的,有时无法知道哪个部分先加载。如果采用上面的方法可能会导致加载一个不存在的空对象,这时就需要采用下面这种“宽放大模式”。
var module = (function(mod){ //... return mod; })(window.module || {});与“放大模式”相比,“宽放大模式”就是立即执行函数的参数可以是空对象。
三、输入全局变量
独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。
为了在模块内部调用全局变量,必须显式地将其他变量输入模块。
var module = (function($,YAHOO){ //... })(jQuery,YAHOO);
四、模块规范之CommonJS
2009年,美国程序员Ryan Dahlchu创造了node.js项目,将javascript语言用于服务器端编程。
node.js的模块系统,就是参照CommonJS规范实现的。在CommonJS中,有一个全局性方法require(),用于加载模块。假设有一个模块math.js,就可以像下面这样加载并调用其方法。
var math = require("math"); math.add(2,3); //5这个主要针对服务端编程,客户端怎么办呢,最好的当然是两者都能兼容,一个模块不用修改,在客户端和服务端都能运行。
但有一个重大局限,采用这种方式加载模块是同步完成的,也就是必须等某一个模块加载完成后才能运行下面的语句,如果加载时间很长,整个应用都会卡在那里等。
这对服务端不是问题,因为所有模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但这显然并不适合浏览器,因此,浏览器端的模块,不能采用“同步加载”,只能采用“异步加载”,于是AMD诞生了。
五、模块规范之AMD
AMD是“Asynchronous Module Definition”的缩写,意思是“异步模块定义”。所有以来这个模块的语句,都定义在一个回调函数中。
AMD也采用require()语句加载模块,但它要求两个参数:
若将前面的代码改成AMD形式,就是下面这样:
require(['math'], function(math){ math.add(2,3); });
六、require.js
为什么要用require.js?
下面这段代码,相信很多人都见过。
<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> <script src="7.js"></script>这段代码依次加载多个js文件,这样的写法有很大缺点。首先,加载的时候没浏览器会停止网页渲染,加载文件越多,网页失去响应的时间越长;其次,由于js文件之间往往存在依赖关系,因此必须严格保证加载顺序,当一来关系很复杂时候,代码的编写和维护都会变得困难。
于是,require.js诞生了。
(1)实现js文件的异步加载,避免网页失去响应;
(2)管理模块之间的依赖性,便于代码的编写和维护。
require.js的加载
先去官网下载最新版本,下载后,根据放置目录就可以加载了。
<script src="js/require-2.2.0.js" data-main="main"></script>你可能会觉得加载这个文件,也可能造成网页失去响应。解决办法有两个,一是将它放在网页底部加载,另一个是这样:
<script src="js/require-2.2.0.js" defer async="true"></script>async表明异步加载,而IE不支持这个属性,只支持defer,所以defer最好也加上。
现在可以进一步加载自己的代码了,假设我们的代码文件是main,js,也在js目录下:
<script src="js/require-2.2.0.js" data-main="js/main"></script>data-main属性的作用是指定网页程序的主模块。该文件会被require.js第一个加载。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。
七、主模块的写法
主模块一般会依赖于其他模块,这时就要使用AMD规范定义的require()函数。
//main.js require(['moduleA','moduleB','moduleC'], function(moduleA,moduleB,moduleC){ //... });第一个参数数组内的模块加载后会以参数的形式传入该函数。
实际例子:
//main.js require(['jquery','underscore','backbone'], function($, _, Backbone){ //... });
八、模块的加载
上节的写法是默认了moduleA、moduleB、moduleC与main.js在同一目录下,而实际情况当然要复杂一些,因此我们可以采用require.config()方法,对模块的加载行为进行自定义。
//main.js require.config({ paths:{ "jquery": "lib/jquery.min", "underscore": "lib/underscore.min", "backbone": "lib/backbone.min" } })或者直接更改 基目录(baseUrl)。
//main.js require.config({ baseUrl: "js/lib", paths:{ "jquery": "jquery.min", "underscore": "underscore.min", "backbone": "backbone.min" } })或者指定远程网址:
require.config({ paths:{ "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min" } })
九、AMD模块的写法
具体来说,就是模块必须采用特定的额define()函数来定义。如果一个模块不依赖其他模块,则直接定义在define()函数中。
// math.js define(function (){ var add = function ( b62d x,y){ return x+y; }; return { add: add }; });如果这个模块依赖其他模块,那么应该这样写:
// math.js define(['myLib'], function(myLib){ function foo(){ myLib.doSomething(); } return { foo : foo }; });当require()函数加载上面这个模块的时候,就会先加载myLib.js文件。
十、requireJS路劲解析问题
baseUrl:基础中的基础
首先需要说明,在requirejs的模块路劲解析里,baseUrl是非常基础的概念,所有的路劲都是requirejs基于baseUrl指定的路劲来寻找。
举个栗子,如下:
<script src="js/require.js" data-main="js/main.js"></script>
在demo.html里加载了require.js,同时在require.js所在的script上申明了data-main属性,那么,requirejs加载下来后,它会做两件事:
1、加载js/main.js
2、将baseUrl设置为data-main指定的文件所在的路劲,这里是js/
那么,下面依赖的lib模块实际路劲为js/lib.js
require(['lib'], function(Lib){ // do sth });如果没有通过data-main属性指定baseUrl,也米有通过config显式申明,那么baseUrl默认为加载requirejs的那个页面所在路劲
baseUrl + path:让依赖更简洁、灵活
原始代码:
//main.js requirejs.config({ baseUrl: 'js' }); // 加载一堆水果 require(['common/fruits/apple', 'common/fruits/orange', 'common/fruits/grape', 'common/fruits/pears'], function(Apple, Orange, Grape, Pears){ // do sth });改进后代码:
//main.js requirejs.config({ baseUrl: 'js', paths: { fruits: 'common/fruits' } }); // 加载一堆水果 require(['fruits/apple', 'fruits/orange', 'fruits/grape', 'fruits/pears'], function(Apple, Orange, Grape, Pears){ // do sth });对于改进后的好处不需要多说。
paths:简单但需要记住的要点
看看下面三种情况:
//main.js requirejs.config({ baseUrl: 'js', paths: { common: 'common/fruits' } }); <strong>// 从左到右,加载的路径依次为 js/apple.js、 js/common/fruits/apple.js、common/apple.js</strong> require(['apple', 'common/apple', '../common/apple'], function(){ // do something });
关于define()内部依赖模块路劲解析问题
首先来个例子:
//main.js requirejs.config({ baseUrl: 'js' }); // 依赖lib.js,实际加载的路径是 js/common/lib.js,而lib模块又依赖于util模块('./util'),解析后的实际路径为 js/common/util.js require(['common/lib'], function(Lib){ Lib.say('hello'); });
// lib.js define(['./util'], function(Util){ //... });解析后的实际路径为 js/common/util.js
下面改个写法:
//main.js requirejs.config({ baseUrl: 'js', paths: { lib: 'common/lib' } }); // 实际加载的路径是 js/common/lib.js require(['lib'], function(Lib){ Lib.say('hello'); });
// lib.js define(['./util'], function(Util){ //... });解析后的实际路径为 js/util.js
为什么会出现这个问题?这个对于初识requirejs并且没看过源码的人确实难懂,你只需要记住以下几点:
1、对于baseUrl的解析需要注意,当满足以下条件,将不会相对baseUrl
(1)以“/”开头
(2)以“.js”结尾
(3)包含各种协议
2、最终lib.js中解析的依赖模块路劲跟main.js中require()函数中的依赖模块路劲的parent有关。什么是parent?举个栗子:
// main.js
require(['common/lib'], function(Lib){
//...
});
// lib.js define(['./util'], function(Util){ //... });//解析路劲为common/util
// main.js
require(['lib'], function(Lib){
//...
});
// lib.js define(['./util'], function(Util){ //... });//解析路劲为util
// main.js
require(['a/c/d/lib'], function(Lib){
//...
});
// lib.js define(['./util'], function(Util){ //... });//解析路劲为a/c/d/util
关于其背后的深入原因,有兴趣的同学可以看看叶小钗大神写的这篇博客requireJS路劲加载。
相关文章推荐
- JQuery1——基础($对象,选择器,对象转换)
- Android学习笔记(二九):嵌入浏览器
- Android java 与 javascript互访(相互调用)的方法例子
- JavaScript演示排序算法
- javascript实现10进制转为N进制数
- 最后一次说说闭包
- Ajax
- 2019年开发人员应该学习的8个JavaScript框架
- HTML中的script标签研究
- 对一个分号引发的错误研究
- 异步流程控制:7 行代码学会 co 模块
- ES6 走马观花(ECMAScript2015 新特性)
- JavaScript拆分字符串时产生空字符的原因
- Canvas 在高清屏下绘制图片变模糊的解决方法
- Redux系列02:一个炒鸡简单的react+redux例子
- JavaScript 各种遍历方式详解
- call/apply/bind 的理解与实例分享