一、递归
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=null,造成错误。
【改进】
function factorial(num){
if(num <= 1){
return 1;
}else{
return num * arguments.callee(num-1);
}
}
arguments.callee是一个指向正在执行的函数的指针。严格模式不可使用。
【严格模式下】
var factorial = ( function f(num){
if(num <= 1){
return 1;
}else{
return num * f(num-1);
}
});
二、闭包
1.基础
定义:有权访问另一个函数作用域中的变量的函数。
【关于作用域链】
每个执行环境都有一个表示变量的对象——变量对象。
全局环境的变量对象始终存在。
创建函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链,保存在内部的[[Scope]]属性中。
当,调用函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象,构建起执行环境的作用域链。
此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域链的前端。
作用域链通常包括几个变量对象:各个函数的活动对象和全局变量对象。
本质:一个指向变量对象的指针列表。
eg:
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;
}
};
}
//创建函数
var compareNames = createComparisonFunction("name");
//调用函数
var result = compareNames({ name: "Nicholas" }, { name: "Greg" });
//解除对匿名函数的引用(以便释放内存)
compareNames = null;
【注意】
当 createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中。
【闭包使用建议】
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。
建议只在绝对必要时,再考虑使用闭包。
2.闭包使用要注意的问题
(1)for循环的问题
作用域链机制的副作用:闭包只能取得包含函数中任何变量的最后一个值。
function createFunctions(){
var result = new Array();
for(var i = 0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
【结果】
var result = createFunctions();
result = [f,f,f,f,...,f]; //一个包含10个函数对象的数组,每个函数都是function(){ return i; }
【解析】
当createFunctions执行完毕,返回result时,createFunctions作用域中的变量i = 10。
由于闭包中的变量i来自外层函数createFunctions,所以,当遍历result数组,执行每个函数,返回的都是10。
【造成错误的原因】
数组中的10个函数的1级作用域,指向了同一个活动对象(createFunctions的活动对象)。
【改进,期望每个函数返回自己对应的索引值】
function createFunctions(){
var result = new Array();
for(var i = 0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
}
}(i);
}
}
【结果】
result = [f,f,f,f...,f]; //一个包含10个函数对象的数组,每个函数都是function(){ return num; }
【解析】
执行result中的每个函数,发现当前0级作用域不存在num变量,于是去1级作用域找。
每个函数的1级作用域,指向10个不同的活动对象,它们的num值分别为0 — 9(在for循环的时候被赋值)。
因此,最终result数组中每个函数的返回值分别为0 — 9。
【注意】
函数的参数变量,相当于在其作用域中声明的变量。
即:num相当于在外层匿名函数中,被var num = i;
(2)关于this
匿名函数中的this指向window。
(3)内存泄漏(闭包造成)
内存泄漏的概念(个人理解):使用过的变量在该被回收的时候,没有被回收,占用了本该被释放的内存,就叫内存泄漏。
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
闭包保存了对assignHandler活动对象的引用,造成DOM元素element无法销毁。
【解决】
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
【注意】
闭包会引用包含函数的整个活动对象,所以,引用了变量id,assignHandler的活动对象就不会被销毁。防止内存泄漏,要手动element = null,解除对DOM对象的引用,回收其占用的内存。
三、私有变量
【创建私有变量的两种方式】
1.类似构造函数模式的私有变量使用方法
function MyObject(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod = function (){
privateVariable++;
return privateFunction();
};
}
MyObject的实例,只能通过publicMethod访问其内部私有变量。
function Person(name){
this.getName = function(){
return name;
};
this.setName = function (value) {
name = value;
};
}
var person = new Person("Nicholas");
alert(person.getName()); //"Nicholas"
person.setName("Greg");
alert(person.getName()); //"Greg"
【缺点】
同构造函数模式,每个实例都会创建同样一组新方法,缺少复用性。
2.静态私有变量(类似于原型模式的私有变量使用方法)
(function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//构造函数(全局变量)
MyObject = function(){
};
//公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
})();
MyObject的实例可以通过原型方法publicMethod访问匿名函数的私有变量。
(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"
【缺点】
私有变量name,是静态的、由所有实例共享的属性。
因为所有实例的setName、getName方法,都有相同的1级作用域,即声明name的那个匿名函数。
【模块模式】
1.单例对象
var singleton = {
name : value,
method : function () {
//这里是方法的代码
}
};
2.模块模式通过为单例添加私有变量和特权方法能够使其得到增强
var singleton = function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权/公有方法和属性
return {
publicProperty: true,
publicMethod : function(){
privateVariable++;
return privateFunction();
}
};
}();
【适用场景】
需要对单例进行某些初始化,同时又需要维护其私有变量。
【增强的模块模式】
var singleton = function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//创建对象
var object = new CustomType();
//添加特权/公有属性和方法
object.publicProperty = true;
object.publicMethod = function(){
privateVariable++;
return privateFunction();
};
//返回这个对象
return object;
}();
【适用场景】
单例必须是某种类型的实例,同时还需要添加一些属性、方法对其加以增强的情况。