开源中国

我们不支持 IE 10 及以下版本浏览器

It appears you’re using an unsupported browser

为了获得更好的浏览体验,我们强烈建议您使用较新版本的 Chrome、 Firefox、 Safari 等,或者升级到最新版本的IE浏览器。 如果您使用的是 IE 11 或以上版本,请关闭“兼容性视图”。
深入理解JavaScript 模块模式 - 技术翻译 - 开源中国社区

深入理解JavaScript 模块模式 【已翻译100%】

唯一 推荐于 5年前 (共 11 段, 翻译完成于 02-03) 评论 34
收藏  
195
推荐标签: JavaScript 待读
模块模式是JavaScript一种常用的编码模式。这是一般的理解,但也有一些高级应用没有得到很多关注。在本文中,我将回顾基础知识,浏览一些不错的高级技巧,甚至我认为是原生基础的。
傅小黑
 翻译得不错哦!

基础知识

首先我们开始简单概述模型模式。三年前Eric Miraglia(YUI)的博文使模型模式众所周知。如果你已经很熟悉模型模式,可以直接阅读“高级模式”。

匿名闭包

这是一切成为可能的基础,也是JavaScript最好的特性。我们将简单的创建匿名函数,并立即执行。所有函数内部代码都在闭包(closure)内。它提供了整个应用生命周期的私有状态

(function () {
	// ... all vars and functions are in this scope only
	// still maintains access to all globals
}());

注意匿名函数周围的()。这是语言的要求。关键字function一般认为是函数声明,包括()就是函数表达式

傅小黑
 翻译得不错哦!

引入全局

JavaScript有个特性,称为隐性全局。使用变量名称时,解释器会从作用域向后寻找变量声明。如果没找到,变量会被假定入全局(以后可以全局调用)。如果会被分配使用,在还不存在时全局创建它。这意味着在匿名函数里使用全局变量很简单。不幸的是,这会导致代码难以管理,文件中不容易区分(对人而言)哪个变量是全局的。

幸好,匿名函数还有一个不错的选择。全局变量作为参数传递给匿名函数。将它们引入我们的代码中,既更清晰,又比使用隐性全局更快。下面是一个例子:

(function ($, YAHOO) {
	// 当前域有权限访问全局jQuery($)和YAHOO
}(jQuery, YAHOO));
傅小黑
 翻译得不错哦!

模块出口

有时你不只想用全局变量,但你需要先声明他们(模块的全局调用)。我们用匿名函数的返回值,很容易输出他们。这样做就完成了基本的模块模式。以下是一个完整例子:

var MODULE = (function () {
	var my = {},
		privateVariable = 1;
	
	function privateMethod() {
		// ...
	}
	
	my.moduleProperty = 1;
	my.moduleMethod = function () {
		// ...
	};
	
	return my;
}());

注意,我们声明了一个全局模块MODULE,有两个公开属性:方法MODULE.moduleMethod和属性MODULE.moduleProperty。而且,匿名函数的闭包还维持了私有内部状态。同时学会之上的内容,我们就很容易引入需要的全局变量,和输出到全局变量。

傅小黑
 翻译得不错哦!

高级模式

对许多用户而言以上的还不足,我们可以采用以下的模式创造强大的,可扩展的结构。让我们使用MODULE模块,一个一个继续。

扩充

模块模式的一个限制是整个模块必须在一个文件里。任何人都了解长代码分割到不同文件的必要。还好,我们有很好的办法扩充模块。(在扩充文件)首先我们引入模块(从全局),给他添加属性,再输出他。下面是一个例子扩充模块:

var MODULE = (function (my) {
	my.anotherMethod = function () {
		// 此前的MODULE返回my对象作为全局输出,因此这个匿名函数的参数MODULE就是上面MODULE匿名函数里的my
	};

	return my;
}(MODULE));

我们再次使用var关键字以保持一致性,虽然其实没必要。代码执行后,模块获得一个新公开方法MODULE.anotherMethod。扩充文件没有影响模块的私有内部状态。

傅小黑
 翻译得不错哦!

松耦合扩充

上面的例子需要我们首先创建模块,然后扩充它,这并不总是必要的。提升JavaScript应用性能最好的操作就是异步加载脚本。因而我们可以创建灵活多部分的模块,可以将他们无顺序加载,以松耦合扩充。每个文件应有如下的结构:

var MODULE = (function (my) {
	// add capabilities...
	
	return my;
}(MODULE || {}));

这个模式里,var语句是必须的,以标记引入时不存在会创建。这意味着你可以像LABjs一样同时加载所有模块文件而不被阻塞。

傅小黑
 翻译得不错哦!

紧耦合扩充

虽然松耦合很不错,但模块上也有些限制。最重要的,你不能安全的覆写模块属性(因为没有加载顺序)。初始化时也无法使用其他文件定义的模块属性(但你可以在初始化后运行)。紧耦合扩充意味着一组加载顺序,但是允许覆写。下面是一个例子(扩充最初定义的MODULE):

var MODULE = (function (my) {
	var old_moduleMethod = my.moduleMethod;
	
	my.moduleMethod = function () {
		// method override, has access to old through old_moduleMethod...
	};
	
	return my;
}(MODULE));

我们覆写的MODULE.moduleMethod,但依旧保持着私有内部状态。

傅小黑
 翻译得不错哦!

克隆和继承

var MODULE_TWO = (function (old) {
	var my = {},
		key;
	
	for (key in old) {
		if (old.hasOwnProperty(key)) {
			my[key] = old[key];
		}
	}
	
	var super_moduleMethod = old.moduleMethod;
	my.moduleMethod = function () {
		// override method on the clone, access to super through super_moduleMethod
	};
	
	return my;
}(MODULE));

这种方式也许最不灵活。他可以实现巧妙的组合,但是牺牲了灵活性。正如我写的,对象的属性或方法不是拷贝,而是一个对象的两个引用。修改一个会影响其他。这可能可以保持递归克隆对象的属性固定,但无法固定方法,除了带eval的方法。不过,我已经完整的包含了模块。(其实就是做了一次浅拷贝)。

傅小黑
 翻译得不错哦!

跨文件私有状态

一个模块分割成几个文件有一个严重缺陷。每个文件都有自身的私有状态,且无权访问别的文件的私有状态。这可以修复的。下面是一个松耦合扩充的例子,不同扩充文件之间保持了私有状态

var MODULE = (function (my) {
	var _private = my._private = my._private || {},
		_seal = my._seal = my._seal || function () {
			delete my._private;
			delete my._seal;
			delete my._unseal;
		},//模块加载后,调用以移除对_private的访问权限
		_unseal = my._unseal = my._unseal || function () {
			my._private = _private;
			my._seal = _seal;
			my._unseal = _unseal;
		};//模块加载前,开启对_private的访问,以实现扩充部分对私有内容的操作
	
	// permanent access to _private, _seal, and _unseal
	
	return my;
}(MODULE || {}));

任何文件都可以在本地的变量_private中设置属性,他会对别的扩充立即生效(即初始化时所有扩充的私有状态都保存在_private变量,并被my._private输出)。模块完全加载了,应用调用MODULE._seal()方法阻止对私有属性的读取(干掉my._private输出)。如果此后模块又需要扩充,带有一个私有方法。加载扩充文件前调用MODULE._unseal()方法(恢复my._private,外部恢复操作权限)。加载后调用再seal()。

这个模式一直随我工作至今,我还没看到别的地方这样做的。我觉得这个模式很有用,值得写上。

傅小黑
 翻译得不错哦!

子模块

最后的高级模式实际上最简单。有很多好方法创建子模块。和创建父模块是一样的:

MODULE.sub = (function () {
	var my = {};
	// 就是多一级命名空间
	
	return my;
}());

虽然很简单,但我还是提一下。子模块有所有正常模块的功能,包括扩充和私有状态。

傅小黑
 翻译得不错哦!
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们
评论(34)
Ctrl/CMD+Enter

不错。
现在的模块模式确实很多,这么一点一点讲述模块的还真不多,受益匪浅,值得推荐
提供一个模型模式的补充,http://www.ituring.com.cn/article/1091
第一个也可写作这样,什么区别?
(function () {
  // ... all vars and functions are in this scope only
  // still maintains access to all globals
})();
有什么用啊,感觉好像给自己找罪受

引用来自“iTsai”的评论

第一个也可写作这样,什么区别?
(function () {
  // ... all vars and functions are in this scope only
  // still maintains access to all globals
})();

没啥区别。。外加一个括号不是必须的。。只是为了跟容易分辨

引用来自“HelloChina”的评论

有什么用啊,感觉好像给自己找罪受

现在的JavaScript复杂应用基本都是模块化的
先收下

引用来自“傅小黑”的评论

引用来自“HelloChina”的评论

有什么用啊,感觉好像给自己找罪受

现在的JavaScript复杂应用基本都是模块化的

我是菜鸟,感觉是有用 可惜没有入门头绪 头疼
资深前端
CommonJS 比较屌
var MODULE = (function (my) {
  var old_moduleMethod = my.moduleMethod;
  
  my.moduleMethod = function () {
    // method override, has access to old through old_moduleMethod...
  };
  
  return my;
}(MODULE));

不太清楚为什么这里要进行var old_moduleMethod = my.moduleMethod,直接对my.moduleMethod进行重写不就好了。

引用来自“ErosVshare”的评论

var MODULE = (function (my) {
  var old_moduleMethod = my.moduleMethod;
  
  my.moduleMethod = function () {
    // method override, has access to old through old_moduleMethod...
  };
  
  return my;
}(MODULE));

不太清楚为什么这里要进行var old_moduleMethod = my.moduleMethod,直接对my.moduleMethod进行重写不就好了。

只是为了表示一下有权限访问到

引用来自“傅小黑”的评论

引用来自“iTsai”的评论

第一个也可写作这样,什么区别?
(function () {
  // ... all vars and functions are in this scope only
  // still maintains access to all globals
})();

没啥区别。。外加一个括号不是必须的。。只是为了跟容易分辨

上面那是一个自动执行函数表达式,
不加最后那个括号是不会执行的,只是一个匿名函数而已。
话说模块化很好,非常优美,简洁,很喜欢,但是实际开发还是得看团队的。

引用来自“ErosVshare”的评论

var MODULE = (function (my) {
  var old_moduleMethod = my.moduleMethod;
  
  my.moduleMethod = function () {
    // method override, has access to old through old_moduleMethod...
  };
  
  return my;
}(MODULE));

不太清楚为什么这里要进行var old_moduleMethod = my.moduleMethod,直接对my.moduleMethod进行重写不就好了。

如果你重写的方法里需要调用原版的方法,那保存旧方法就很有必要了,这跟集成抽象类,重写父类是一样的,只不过javascript没base/super这样的关键字而已。
嘛认可

引用来自“傅小黑”的评论

引用来自“iTsai”的评论

第一个也可写作这样,什么区别?
(function () {
  // ... all vars and functions are in this scope only
  // still maintains access to all globals
})();

没啥区别。。外加一个括号不是必须的。。只是为了跟容易分辨

()内部的对于解释器来说就是表达式,函数后加()就是执行。还是有区别的吧!

引用来自“Tobyee”的评论

引用来自“ErosVshare”的评论

var MODULE = (function (my) {
  var old_moduleMethod = my.moduleMethod;
  
  my.moduleMethod = function () {
    // method override, has access to old through old_moduleMethod...
  };
  
  return my;
}(MODULE));

不太清楚为什么这里要进行var old_moduleMethod = my.moduleMethod,直接对my.moduleMethod进行重写不就好了。

如果你重写的方法里需要调用原版的方法,那保存旧方法就很有必要了,这跟集成抽象类,重写父类是一样的,只不过javascript没base/super这样的关键字而已。

正解哈

引用来自“大奶牛”的评论

引用来自“傅小黑”的评论

引用来自“iTsai”的评论

第一个也可写作这样,什么区别?
(function () {
  // ... all vars and functions are in this scope only
  // still maintains access to all globals
})();

没啥区别。。外加一个括号不是必须的。。只是为了跟容易分辨

上面那是一个自动执行函数表达式,
不加最后那个括号是不会执行的,只是一个匿名函数而已。
话说模块化很好,非常优美,简洁,很喜欢,但是实际开发还是得看团队的。

首先function ()是 一个匿名函数就表示不会被重用,();即是马上执行这个匿名函数。外面加的那对括号形成了一个闭包,闭包能减少全局变量和全局函数的污染,也更便于阅读。你要说闭包有用没用,那就要看自己写js时站在什么样的高度了。
话说写js都比较随意,高手都是这么干的,js模块化,尽量减少全局变量的污染。
顶部