JavaScript5

第 7 章“函数表达式”
一、
1.函数声明

function functionName(arg0, arg1, arg2){
	//函数体
}

关于函数声明,它的一个重要特征就是函数声明提升(function declaration hoisting),意思是在执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。

sayHi();
function sayHi(){
	alert("Hi!");
}
//这个例子不会抛出错误,因为在代码执行之前会先读取函数声明。

2.第二种创建函数的方式是使用函数表达式。
//创建一个函数并将它赋值给变量functionName。
这种情况下创建的函数叫做匿名函数(anonymous function),因为function 关键字后面没有标识符。(匿名函数有时候也叫拉姆达函数。)匿名函数的name 属性是空字符串。

var functionName = function(arg0, arg1, arg2){
	//函数体
};

在把函数当成值来使用的情况下,都可以使用匿名函数.

7.1 递归

//函数声明
function factorial(num){
	if (num <= 1){
		return 1;
	} else {
		return num * factorial(num-1);
	}
}

//以上没错
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //出错!

//但在接下来调用anotherFactorial()时,由于必须执行factorial(),而factorial 已经不再是函数,所以就会导致错误。在这种情况下,使用arguments.callee 可以解决这个问题。

我们知道,arguments.callee 是一个指向正在执行的函数的指针,因此可以用它来实现对函数
的递归调用,例如:

//函数声明
function factorial(num){
	if (num <= 1){
		return 1;
	} else {
		return num * arguments.callee(num-1);
	}
}

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //可以正常调用

但在严格模式下,不能通过脚本访问arguments.callee,访问这个属性会导致错误。

可以使用命名函数表达式来达成相同的结果。例如:

var factorial = (function f(num){
	if (num <= 1){
		return 1;
	} else {
		return num * f(num-1);
	}
});
//创建了一个名为f()的命名函数表达式,然后将它赋值给变量factorial。即便把函数赋值给了另一个变量,函数的名字f 仍然有效,所以递归调用照样能正确完成。这种方式在严格模式和非严格模式下都行得通。

7.2 闭包
闭包是指有权访问另一个函数作用域中的变量的函数。

在内部函数接收到propertyName 参数后,它会使用方括号表示法来取得给定属性的值
1.

//ECMAScript 中的参数在内部是用一个数组来表示的。函数接收到的始终都是这个数组,而不关心数组中包含哪些参数(如果有参数的话)

//在函数体内可以通过arguments 对象来访问这个参数数组,从而获取传递给函数的每一个参数。

//其实,arguments 对象只是与数组类似(它并不是Array 的实例),因为可以使用方括号语法访问它的每一个元素(即第一个元素是arguments[0],第二个元素是argumetns[1],以此类推),使用length 属性来确定传递进来多少个参数.

function createComparisonFunction(propertyName) {
	return function(object1, object2){
		var value1 = object1[propertyName];
		var value2 = object2[propertyName];
		if (value1 < value2){
			return -1;
		} else if (value1 > value2){
			return 1;
		} else {
			return 0;
		}
	};
}

在匿名函数从createComparisonFunction()中被返回后,它的作用域链被初始化为包含
createComparisonFunction()函数的活动对象和全局变量对象。这样,匿名函数就可以访问在
createComparisonFunction()中定义的所有变量。更为重要的是,createComparisonFunction()
函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换
句话说,当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活
动对象仍然会留在内存中;直到匿名函数被销毁后,createComparisonFunction()的活动对象才会
被销毁,例如:

//创建函数
var compareNames = createComparisonFunction("name");
//调用函数
var result = compareNames({ name: "Nicholas" }, { name: "Greg" });
//解除对匿名函数的引用(以便释放内存)
compareNames = null;

7.2.1 闭包与变量
即闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象,而不是某个特殊的变量。

function createFunctions(){
	var result = new Array();
	for(int i=0; i<10; i++)
	{
		result[i] = function(){
			return i;
		}
	}
	return result;
}

这个函数返回一个函数数组。实际上,每个闭包函数都返回10,因为每个函数的作用域链都保存着createFunctions() 函数的活动对象, 所以它们引用的都是同一个变量i 。

我们可以通过创建另一个匿名函数强制让闭包的行为符合预期,如下所示。

function createFunction(){
	var result = new Array();
	for(int i=; i<10; i++)
	{
		result[i] = function(num){
			return function(){
				return num;
			}
		}(i);//函数表达式,将i传递给num
		
		return result;
	}
}

我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并且将 立即执行匿名函数的结果赋值给数组。这里匿名函数中有一个参数num,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量i,所以就会将变量i的值赋值给num。而在这个匿名函数的内部,又创建了并且返回了一个访问num的闭包。这样一来,result数组中的每个函数都有自己num变量的一个副本。因此位置0 的函数返回0,位置1 的函数返回1,以此类推。

7.2.2 关于this对象
this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于windows,当函数作为某个对象的方法调用时,this等于那个对象。

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

//匿名函数的this 通过作用域链的查找先找到了全局作用域链的windows
// 因为this在闭包里面 
在getNameFunc : function(){ 函数内部定义的匿名函数的作用域链中,实际上将会包含外部
函数getNameFunc : function(){ 的活动对象。 外部函数的活动对象 中的this 是windows而不是object
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

//匿名函数没有取得其包含作用域(或外部作用域)的this 对象.,每个函数在被调用时都会自动取得两个特殊变量:this 和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量

必须要记住:闭包会引用 包含函数的整个活动对象。

===================================
每个函数在被调用时都会自动取得两个特殊变量:this 和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。不过,把外部作用域中的this 对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了,如下所示。

var name = "The Window";
var object = {
	name : "My Object",
	getNameFunc : function(){
		var that = this;
		return function(){
			return that.name;
		};
	}
};
alert(object.getNameFunc()()); //"My Object"

代码中突出的行展示了这个例子与前一个例子之间的不同之处。在定义匿名函数之前,我们把this
对象赋值给了一个名叫that 的变量。而在定义了闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声名的一个变量。即使在函数返回之后,that 也仍然引用着object,所以调用
object.getNameFunc()()就返回了"My Object"。

this 和arguments 也存在同样的问题。如果想访问作用域中的arguments 对
象,必须将对该对象的引用保存到另一个闭包能够访问的变量中。

7.3 模仿块级作用域
匿名函数可以用来模仿块级作用域并避免这个问题。
用作块级作用域(通常称为私有作用域)的匿名函数的语法如下所示。

(function(){
	//这里是块级作用域
})();
//将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。
//而紧随其后的另一对圆括号会立即调用这个函数。

//JavaScript 将function 关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号。
然而,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式,只要像上面这样给它加上一对圆括号即可。

7.4 私有变量

构造函数:这个构造函数的成员变量不是私有的,通过this对象可以访问到。
this对象是调用对象,也就是构造出来的 对象实例。
function SuperType(name){
	this.name = name;
	this.colors = ["red", "blue", "green"];
}
//构造函数MyObject里的  变量和函数  是私有的,只能通过特权方法来访问。
特权方法能够访问是因为特权方法 是一个函数表达式。 并且函数表达式放在了函数中(构造函数)形成了闭包。
闭包的作用域链延伸到包含它的函数,也就是作用域链延伸到MyObject,闭包函数就能够访问MyObiect环境下的所有变量了。
function MyObject(){
	//私有变量和私有函数
	var privateVariable = 10;
	function privateFunction(){
		return false;
	}
	//特权方法
	this.publicMethod = function (){
		privateVariable++;
		return privateFunction();
	};
}

构造函数模式的缺点是针对每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方
法就可以避免这个问题。

7.4.1 静态私有变量
通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法,其基本模式如下所示。

(function(){
	//私有变量和私有函数
	var privateVariable = 10;
	function privateFunction(){
		return false;
	}
	//构造函数
	MyObject = function(){
	};
	//公有/特权方法:公有方法是在原型上定义的,这一点体现了典型的原型模式。
	MyObject.prototype.publicMethod = function(){
		privateVariable++;
		return privateFunction();
	};
})();

这个模式在定义构造函数时并没有使用函数声明,而是使用了函数表达式。函数声明只能创建局部函数,但那并不是我们想要的。出于同样的原因,我们也没有在声明MyObject 时使用var 关键字。记住:初始化未经声明的变量,总是会创建一个全局变量。
因此,MyObject 就成了一个全局变量,能够在私有作用域之外被访问到。但也要知道,在严格模式下给未经声明的变量赋值会导致错误。

这个模式与在构造函数中定义特权方法的主要区别,就在于私有变量和函数是由实例共享的。由于
特权方法是在原型上定义的,因此所有实例都使用同一个函数。而这个特权方法,作为一个闭包,总是保存着对包含作用域的引用

7.4.2 模块模式(为单例创建私有变量和特权方法)
//所谓单例(singleton),指的就是只有一个实例的对象。
JavaScript 是以对象字面量的方式来创建单例对象的。

//错误! 不是实例对象, 而是一个匿名函数表达式
var singleton = function(){
	name:value;
	method:function(){
	}
};

//这个才是以字面量方式创建的实例(单例:只有一个实例的对象singleton)
var singleton = {
	name : value;
	method : function(){
		//这里面是方法的代码
	}
};

模块模式通过为单例 添加私有变量和特权方法能够使其得到增强,其语法形式如下:

这个模块模式使用了一个返回对象的匿名函数。在这个匿名函数内部,首先定义了私有变量和函数。然后,将一个对象字面量作为函数的值返回。返回的对象字面量中只包含可以公开的属性和方法。由于这个对象是在匿名函数内部定义的,因此它的公有方法有权访问私有变量和函数。从本质上来讲,这个对象字面量定义的是单例的公共接口。这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的。

var application = function(){
	//私有变量和函数
	var components = new Array();
	//初始化
	components.push(new BaseComponent());
	//公共
	return {
			getComponentCount : function(){
				return components.length;
			},
			registerComponent : function(component){
				if (typeof component == "object"){
				components.push(component);
				}
			}
		};
}();

7.4.3 增强的模块模式
这种增强的模块模式适合那些单例必须是某种类型的实例

var application = function(){
//私有变量和函数
var components = new Array();
//初始化
components.push(new BaseComponent());
//创建application 的一个局部副本
var app = new BaseComponent();
//公共接口
app.getComponentCount = function(){
return components.length;
};
app.registerComponent = function(component){
if (typeof component == “object”){
components.push(component);
}
};
//返回这个副本
return app;
}();

 函数表达式不同于函数声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表
达式也叫做匿名函数。
 在无法确定如何引用函数的情况下,递归函数就会变得比较复杂;
 递归函数应该始终使用arguments.callee 来递归地调用自身,不要使用函数名——函数名可
能会发生变化。
当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理
如下。
 在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。
 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。
 但是,当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。
使用闭包可以在JavaScript 中模仿块级作用域(JavaScript 本身没有块级作用域的概念),要点如下。
 创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。
 结果就是函数内部的所有变量都会被立即销毁——除非将某些变量赋值给了包含作用域(即外
部作用域)中的变量。
闭包还可以用于在对象中创建私有变量,相关概念和要点如下。
 即使JavaScript 中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公
有方法可以访问在包含作用域中定义的变量。
 有权访问私有变量的公有方法叫做特权方法。
 可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强
的模块模式来实现单例的特权方法。
JavaScript 中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。不过,因为
创建闭包必须维护额外的作用域,所以过度使用它们可能会占用大量内存。

猜你喜欢

转载自blog.csdn.net/baidu_19552787/article/details/84956052