//函数表达式:
	//使用函数声明创建函数:

function functionName(arg0,arg1,arg2){
	console.log();
}
console.log(functionName.name);						//函数名.name:返回函数名。functionName

sayHi();								//函数声明提升:在执行代码之前会先读取函数声明。所以可以把函数声明放在调用它的语句后面。
function sayHi() {
	console.log("hi!");
}


	//使用函数表达式创建函数:
		//函数表达式不同于声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表达式也叫匿名函数(本章重点)。
sayHi();								//Error:函数还不存在。因为用了匿名函数,没有进行函数声明提升。
sayHi = function() {
	console.log("hi!");
};

if(true) {								//不可以使用这样的条件表达式。
	function sayHi() {
		console.log("hi!");
	}
} else {
	function sayHi() {
		console.log("no!");
	}
}
sayHi();								//"no!"本质上是错误的,每种浏览器处理方式不一样。


if(true) {								//但可以使用这样的条件表达式。
	sayHi = function() {
		console.log("hi!");
	}
} else {
	sayHi = function() {
		console.log("no!")
	};
}
sayHi();								//"hi!"
									//这样不会发生意外,不同的函数根据条件被赋给sayHi

//匿名函数实例:创建比较器的函数。
function createComparisonFunction(propertyName) {
	return function(object1,object2) {
		var val1 = object1[propertyName];
		var val2 = object2[propertyName];
		
		if(val1<val2) {
			return 1;
		} else if(val1>val2) {
			return -1;
		} else {
			return 0;
		}
	};
}

function functionName () {
	return arguments.callee.name;
}
console.log(functionName());						//functionName:使用函数的.name属性获取了函数名。

//以前写过的例子:递归函数应该始终使用arguments.callee来递归地调用自身,降低耦合性。
function factorial(num) {
	if(num==1)
		return 1;
	else
		return num*arguments.callee(num-1);		//降低耦合性。
}
console.log(factorial(6));

	//注意:严格模式下不能使用arguments.callee函数,会发生错误。
	//解决方法:使用命名函数表达式(给匿名函数命名)
	
var factorial1 = function f(num) {		//用f表示匿名函数,即使将函数名改变,函数也不会出错。
	if(num==1)
		return 1;
	else
		return num*f(num-1);
}
var factorial2 = factorial1;
console.log(factorial1(6));			//720
console.log(factorial2(6));			//720,改了名仍然有效。

//闭包:有权访问另一个函数作用域中变量的函数(通过外部函数中的变量创建内部函数)。
//例:
function createComparisonFunction(propertyName) {
	return function(object1,object2) {
		var val1 = object1[propertyName];
		var val2 = object2[propertyName];				//这两行代码访问了外部函数中的变量。
		
		if(val1<val2) {
			return 1;
		} else if(val1>val2) {
			return -1;
		} else {
			return 0;
		}
	};
}
var fun = createComparisonFunction("name");
console.log(fun.propertyName);
*/

	/*
	 * 对闭包的理解:
	 * 	一般函数在被调用时,会创建一个执行环境,执行环境指向了一组指针:作用域链
	 * 		作用域链中优先级为	函数活动对象→全局对象。
	 * 	而闭包被调用时,有闭包的作用域链为	闭包活动对象→外部函数活动对象→全局变量;
	 * 		外部函数的作用域链为	外部函数活动对象→全局对象。
	 * 	由于外部函数的活动对象一直存在于闭包的作用域链中。
	 * 	所以即使外部函数执行完毕以后,其活动对象也不会被销毁(因为一直在被闭包的作用域链引用)。
	 * 	而是一直被匿名函数的作用域链所指向从而留在内存中,直到匿名对象被销毁。
	 * 解决:少用闭包,在绝对必要时才用。用完以后手动解除其和外部函数活动对象之间的关联(理解:《JavaScript高级程序设计》 P180 图7-2)
	 */
	//手动解除关联:
	
var compareNames = createComparisonFunction("name");			//将函数创建的闭包函数记录以调用
var result = compareNames({ name: 'Steve'},{ name: 'Erison'});		//调用函数
	//使用完毕后:
compareNames = null;							//解除对匿名函数的引用,释放内存
									//以便垃圾收集器释放闭包活动对象
									//然后其作用域链中的其他活动对象(除了全局作用域)也被安全地销毁。

//闭包的副作用:闭包只能取的外部函数中任何变量的最后一个值
function createFunctions() {		//目的:通过闭包创建十个函数,十个函数的返回值为从0到9。
	var result = new Array();
	for(var i=0;i<10;i++) {
		result[i] = function() {
			return i;
		}
	}
	return result;
}

var result = createFunctions();

for(var i=0;i<result.length;i++) {
	console.log(result[i]());			//返回了10个10!
}
	//原因:闭包的作用域链中包含外部函数,所以闭包中的i是通过作用域链在外部函数中搜索来的。
	//在最后return result时,i是10,故10个函数的返回值都是10.

//解决:
function createFunctions() {
	var result = new Array();
	for(var i=0;i<10;i++) {
		result[i] = function(num) {
			return function() {
				return num;
			}
		}(i);			//用定义匿名函数的方式给数组赋予函数。最后传入一个参数i。由于函数参数是按值传递的,所以i会被赋值给参数num。
	}
	return result;
}
var result = createFunctions();
for(var i=0;i<result.length;i++) {
	console.log(result[i]())			//返回了0到9
}

//闭包与this对象

var name = "The Window";

var object = {
	name: "My Object",
	get: function() {					//get:返回一个匿名函数
		return function() {
			return this.name;
		};
	}
};

console.log(object.get()());			//The Window:返回的匿名函数中的this指向外部作用域。
							//!*这里我没搞懂。书上的解释是:内部函数在搜索this和argument变量时,
							//只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。

	//让闭包访问外部函数对象:
var name = "The Window"
var object = {
	name: "My Object",
	get: function() {
		var that = this;
		return function() {
			return that.name;
		}
	}
};

console.log(object.get()());				//My Object:在函数内定义了that指向object,起到过渡作用,使内部函数访问到object。

//细微区别导致this改变:
var name = "The Window";

var object = {
	name: "My Object",
	getName: function() {
		return this.name;
	}
}

console.log(object.getName());							//My Object:因为this.name就是object.name。
console.log((object.getName)());						//My Object:将object中的函数当作一个独立的函数运行,但this的值得到了维持。
														//因为object.getName()和(object.getName)()的定义是相同的。
console.log((object.getName = object.getName)());		//The Window:
													//!*这里也没太看懂。书上的解释:因为这个赋值表达式的值是函数本身。														//所以this的值不能得到维持。
													//(因为将object中的函数重新赋予,this没有维持,所以改变了作用域?)

//IE中的BUG:如果闭包的作用域链中保存着一个HTML元素,该元素无法被销毁。
	//因为IE中队JS对象和COM对象使用的不同的垃圾收集例程(JS:标记清除/COM:引用计数)
function assignHandler() {
	var element = document.getElementById("someElement");
	
	element.onclick = function() {
		alert(element.id);
	};
}
		//由于匿名函数一直保存着对assignHandler活动对象的引用,导致活动对象一直存在,element的引用数至少是1,所占用的内存永远不会被回收。
		//解决:不让闭包直接访问HTML属性,而是创建一个副本让闭包访问。
		
function assignHandler() {
	var element = document.getElementById("someElement");
	
	var id = element.id;					//创建闭包要访问的属性的副本。
	
	element.onclick = function() {
		alert(id);					//将内部函数解除对element的引用(变为引用所需要的element对象属性的副本)。
	};
	
	element = null;						//解除对element的引用。
}
		//由于被闭包访问的整个活动对象会一直存在,所以即使闭包引用的是id,包含函数中还是会存在一个对element的引用。所以要手动解除对element的引用。

//模仿块级作用域:js中没有块级作用域,语句块中的变量实际上在函数中,所以可以用匿名函数来模仿块级作用域。
function output() {
	for(var i=0;i<10;i++) {
		
	}
	var i;							//js中,后续声明一个已存在的变量会被无视,i还是10。
	console.log(i);						//后面的输出结果说明了,这里可以访问到i。
}

output();							//10:由于没有块级作用域,导致所有变量都在函数中。所以在函数内的for语句之外也可以访问到i。
(function(){
	//块级作用域:创建一个匿名函数并立即执行。
	for(var i=0;i<10;i++) {
		
	}
})();

console.log(i);						//错误:因为i在匿名函数模仿的块级作用域中,所以外部访问不到。
		//用途:经常会用在全局作用域中的函数外部,为了不向全局作用域中添加过多的数据,增强封装型,满足程序员们的强迫症。


//私有变量

//构造函数方法
function Person(name) {					//通过在构造函数内部定义两个特权方法,定义了私有变量和函数
	this.getName = function() {			//由于没有显式地定义name属性,除了通过这两个方法以外,没有其他方法可以访问name属性。
		return name;
	};
	
	this.setName = function(value) {
		name = value;
	};
}

var person = new Person("Steve");
console.log(person.getName());

		//通过构造函数定义特权变量缺点:像创建对象的构造函数模式一样,无法实现方法的复用。

//静态私有变量
		//通过在私有作用域(通过匿名函数实现)中定义私有变量或函数,创建特权方法。

(function {
	//私有变量和私有函数
	var privateVariable = 10;
	
	function privateFunction() {
		return false;
	}
	
	//构造函数
	MyObject = function() {};
	
	//公有/特权方法
	MyObject.prototype.publicMethod = function() {
		privateVariable++;
		return privateFunction;
	};
})();
		//与构造函数中定义特权方法的区别:将方法定义在原型上,原型上的方法是共享的。因为不管是哪个实例中的此方法都包含着对作用域的引用。

(function() {
	var name = "";
	
	Person = function(value) {				//不使用var关键字:确保Person为全局函数
		name = value;
	}
	
	Person.prototype.getName = function(){
		return name;
	}
	
	Person.prototype.setName = function(value) {
		name = value;
	}
})();

var p1 = new Person("Steve");
console.log(p1.getName());

var p2 = new Person("Erison");
console.log(p1.getName());					//Erison
console.log(p2.getName());					//Erison		由于变量的共享,都被改成了Erison

//模块模式:为单例添加私有变量和特权方法。
var application = function(){					//创建了一个含有指定方法的实例:在私有域中定义方法和属性,并添加方法,然后立刻返回一个单例。
	//私有变量和函数。
	var components = new Array();
	
	//初始化
	component.push(new BaseComponent());
	
	//公共方法
	return {						//用字面量定义法返回一个单例
		getComponentCount: function(){
			return component.length;
		},
		
		registerComponent: function(component){
			if(typeof conponent == "object") {
				components.push(conponent);
			}
		}
}();								//这种单例以Object形式存在(因为由对象字面量方法定义)。

//增强的模块模式。
	//在返回对象之前加入对其增强的代码。适合单例必须是某种类型的实例。
	//使用构造函数而不是字面量方法来返回对象
	//修改后的上述代码:

var application = function(){
	//私有变量函数
	var components = new Array();
	
	//初始化
	components.push(new BaseComponent());
	
	//创建局部副本:相比模块模式加强的地方,因为返回的对象必须是BaseComponent的实例!
	var app = new BaseComponent();
	
	//再为局部副本添加能够访问私有变量的公共方法
	app.getComponentCount = function() {
		return components.length;
	};
	
	app.registerComponent = function(component) {
		if(typeof component == "object"){
			components.push(component);
		}
	}
	
	//返回局部副本,此时返回以后便是指定类型的单例了。
	return app;
}();