JavaScript学习专题之--私有变量和ES5中的模块模式

Posted by liveipool on December 24, 2016

私有变量和ES5中的模块模式

私有变量相关概念

严格来讲,JavaScript中并没有私有成员的概念,所有对象属性都是公有的.不过,倒是有私有变量的概念.任何在函数中定义的变量,都可以认为是私有变量.

有权访问私有变量和私有函数的公有方法被称为特权方法.
有两种在对向上创建特权方法的方式:第一种是在构造函数中定义特权方法

	function myObject() {
		var privateVariable = 10;     //私有变量
		function privateFunction() {  //私有函数
			return false;
		}

		this.publicMethod = function() {    //特权方法
			privateVariable++;
			console.log(privateVariable);
			console.log(privateFunction());
		}
	}

	var object = new myObject();     
	object.publicMethod();           //11  false

第二种是在原型上定义特权方法.这种方式和在构造函数中定义特权方法的主要区别在于,私有变量是由实例共享的,下面看一下例子:


	(function() {
		var name = "";

		Person = function(value) {       //这样写在严格模式下会报错
			name = value;
		}

		Person.prototype.getName = function() {
			return name;
		}

		Person.prototype.setName = function(value) {
			name = value;
		}
	})();

	var person1 = new Person("Daniel");
	console.log(person1.getName());      //Daniel
	person1.setName("Nick");
	console.log(person1.getName());      //Nick

	var person2 = new Person("Michael");
	console.log(person2.getName());      //Micheal
	console.log(person1.getName());      //Micheal

注意理解这两种创建特权方法即它们之间的区别.

ES5中的模块模式

在上文中我们了解了与私有变量相关的一些概念,前面的博客中我们也已经详细了解了闭包.接下来在这里要提到的代码模式:模块模式就运用了闭包的强大威力.
随着ES6的出现,JavaScript终于有了一个完善的模块体系,但是在系统的学习ES6的模块之前,我们还是先了解一下截至到ES5, JavaScript是怎样来实现模块的.

考虑以下的代码:

function coolModule() {
	var something = "cool";      //私有变量
	var another = [1, 2, 3];

	function doSomething() {     //私有函数
		console.log(something);
	}

	function doAnother() {
		console.log(another.join(","));
	}

	return {
		doSomething: doSomething,
		doAnother: doAnother
	}
}

var foo = coolModule();

foo.doSomething();     //cool
foo.doAnother();       //1,2,3

这个模式在JavaScript中被称为模块.最常见的模块实现的方法通常被称为模块暴露,这里展示的是其变体.
我们分析一下上面的代码:首先,coolModule()只是一个全局函数,必须要通过调用它来创建一个模块实例.如果不调用它,内部作用域和doSomething(),doAnother()这两个闭包都无法被创建.
其次,coolModule()返回一个用对象字面量语法{key: value, …}来表示的对象.这个返回的对象中含有对私有函数而不是私有变量的引用.我们保持私有变量是隐藏且私有的状态.可以将这个对象类型的返回值看作本质上是模块的公共API.
这个对象类型的返回值最终被赋值给外部的变量foo,然后就可以通过它来访问API中的属性方法,比如foo.doSomething().

如果需要更简单的描述,模块模式需要具备两个必要条件:
1.必须有外部的封闭函数(如coolModule),该函数必须至少被调用一次(每次调用都会创建一个新的模块实例).
2.封闭函数必须返回至少一个私有函数,这样私有函数才能在私有作用域上形成闭包,并且可以访问或者修改私有的状态.

上一个实例代码中有一个叫做coolModule()的独立模块创建器,可以被调用任意多次,每次调用都会创建一个新的模块实例.当只需要一个实例时,可以对这个模块进行简单的改进来实现单例模式.

	var foo = (function coolModule() {
		var something = "cool";
		var another = [1, 2, 3];

		function doSomething() {
			console.log(something);
		}

		function doAnother() {
			console.log(another.join(","));
		}

		return {
			doSomething: doSomething,
			doAnother: doAnother
		}
	})();

	foo.doSomething();     //cool
	foo.doAnother();       //1,2,3

模块模式另一个简单但强大的用法是命名将要作为公共API返回的对象:

	var foo = (function coolModule(id) {
		function change() {
			publicAPI.identify = identify2;
		}

		function identify1() {
			console.log(id);
		}

		function identify2() {
			console.log(id.toUpperCase());
		}

		return publicAPI = {
			identify: identify1,
			change: change
		}
	})("foo module");

	foo.identify();     //foo module
	foo.change();
	foo.identify();     //FOO MODULE

通过在模块实例的内部保留对公共API对象的内部引用,可以从内部对模块实例进行修改,包扩添加或删除方法和属性,以及修改它们的值.

“现代”的模块机制

这个”现代”其实不那么准确,只是代表截止到ES5,体现和ES6模块机制的区别(本文中不讨论ES6的模块机制).
现有的大多数模块依赖加载器/管理器本质上都是将这种模块定义封装进一个友好的API. 这里并不会研究某个具体的库,而是通过一段代码来宏观地体现一下现代模块机制的核心思想:

	var MyModules = (function Manager() {
		var modules = {};

		function define(name, dependencies, fn) {
			for (var i  = 0; i < dependencies.length; i++) {
				dependencies[i] = modules[dependencies[i]];   //把这个dependence的name对应的值取出来.
			}
			modules[name] = fn.apply(fn, dependencies);
		}

		function get(name) {
			return modules[name];
		}

		return {
			define: define,
			get: get
		}
	})();

	MyModules.define("bar", [], function() {
		function hello(who) {
			console.log("let me introduce: " + who);
		}

		return {
			hello: hello
		}
	});

	MyModules.define("foo", ["bar"], function(bar) {
		var anotherPerson = "Nick";

		function anotherHello() {
			return bar.hello(anotherPerson);
		}

		return {
			anotherHello: anotherHello
		}
	});

	var bar = MyModules.get('bar');
	var foo = MyModules.get('foo');

	bar.hello("Liveipool");
	foo.anotherHello();

这部分看得较为仓促,感觉有些地方也还没完全理解,配合着书和资料,以后再多看看,多总结一下,这部分一定要完全弄懂才行.

相关文章链接