第七章 函数函数表达式

121 阅读3分钟

概述 :关于函数声明和函数表达式的区别

sayHi();//正确:函数声明提升
function sayHi(){
 	alert("Hi!");
}

sayHi(); //错误:函数还不存在
var sayHi = function(){
 	alert("Hi!");
}; 


//错误的做法实际上,这在 ECMAScript 中属于无效语法,JavaScript 引擎会尝试修正错误,将其转换为合理的状态。不同浏览器尝试修正错误的做法并不一致。大多数浏览器会返回第二个声明,忽略condition;Firefox 会在 condition 为 true 时返回第一个声明。因此这种使用方式很危险。
if(condition){
 	function sayHi(){
 		alert("Hi!");
 	}
} else {
    function sayHi(){
 		alert("Yo!");
 	}
}

//可以这样做
var sayHi;
if(condition){
 	sayHi = function(){
 		alert("Hi!");
 	};
} else {
 	sayHi = function(){
 		alert("Yo!");
 	};
} 

函数还可以作为其他函数的值返回,返回的函数可能会被赋值给一个变量, 或者以其他方式被调用;在把函数当成值来使用的情况下,都可以使用匿名函数。

1、 递归

在非严格模式下,可以使用arguments.callee (指向正在执行的函数的指针)来实现对函数的递归调用

function factorial(num){
 	if (num <= 1){
 		return 1;
 	} else {
 		return num * factorial(num-1);
 	}
} 
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //出错!

//以下两种方法即使设置factorial=null,都不会报错
function factorial(num){
 	if (num <= 1){
 		return 1;
 	} else {
 		return num * arguments.callee(num-1);
 	}
} 

var factorial = (function f(num){
 	if (num <= 1){
 		return 1;
 	} else {
 		return num * f(num-1);
 	}
}); 

2、闭包

关于this对象

匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。但有时候由于编写闭包的方式不同,这一点可能不会那么明显。

var name = "The Window";
var object = {
 	name : "My Object",
 	getNameFunc : function(){
 		return function(){
 			return this.name;
 		};
 	}
};
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

在几种特殊情况下,this 的值可能会意外地改变。

var name = "The Window";
var object = {
    name: "My Object",
    getName: function () {
        return this.name;
    }
};
object.getName(); //"My Object"
(object.getName)(); //"My Object"
(object.getName = object.getName)(); //"The Window",在非严格模式下

内存泄漏

如果闭包的作用域链中保存着一个 HTML 元素,那么就意味着该元素将无法被销毁。如下所示

function assignHandler(){
 	var element = document.getElementById("someElement");
 	element.onclick = function(){
 		alert(element.id);
 	};
} 

以上代码创建了一个作为 element 元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用。由于匿名函数保存了一个对 assignHandler() 的活动对象的引用,因此无法减少 element 的引用数。只要匿名函数存在,element 的引用数至少也是 1,因此它所占用的内存就永远不会被回收。

通过两步可以解决以上问题:(1)、把 element.id 保存在一个变量中,并且在闭包中引用该变量(消除循环引用)(闭包会引用包含函数的整个活动对象,而其中包含 element。即使闭包不直接引用 element,包含函数的活动对象中也仍然会保存一个引用,所以还必须进行下一步)(2)、把 element 变量设置为 null

function assignHandler(){
 	var element = document.getElementById("someElement");
 	var id = element.id;//第一步

 	element.onclick = function(){
 		alert(id);
 	};

 	element = null;//第二步
} 

3、私有变量

特权方法:有权访问私有变量和私有函数的公有方法

function MyObject(){
 	//私有变量和私有函数
 	var privateVariable = 10;
 	function privateFunction(){
 		return false;
 	}
 	//特权方法
 	this.publicMethod = function (){
 		privateVariable++;
 		return privateFunction();
 	};
}

静态私有变量

创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变量。

(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("Nicholas");
alert(person1.getName()); //"Nicholas"
person1.setName("Greg");
alert(person1.getName()); //"Greg"

var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael" 

模块模式

模块模式是为单例(只有一个实例的对象)创建私有变量和特权方法。如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。

 var singleton = function(){

 	//私有变量和私有函数
 	var privateVariable = 10;

 	function privateFunction(){
 		return false;
 	} 
	//特权/公有方法和属性
 	return {
 		publicProperty: true,
 		publicMethod : function(){
 			privateVariable++;
 			return privateFunction();
 		}
 	};
}();