Chapter 7 函数表达式
基本概念
-
函数声明提升:执行代码前会先读取函数声明。
- 函数声明
function sayHi() { alert("Hi!"); }
- 函数表达式
var sayHi = function() { alert("Hi!"); }
匿名函数:function关键字后没有标识符。
不要在条件语句中声明函数,因为条件语句可能会被忽略。但是可以使用函数表达式。
递归
- 函数名递归(略)
- 使用arguments.callee指针实现递归
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);
}
});
闭包
- 定义:闭包是指有权访问另一个函数作用域中的变量的函数。
- 创建闭包的常见方式,是在一个函数内部创建另一个函数
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;
}
}
作用域链:当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象。但在作用域链中,使用arguments和其他命名参数的值来初始化函数的活动对象。但是在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位.....直到作为作用域终点的全局执行环境。
作用域链的本质是一个指向变量对象的指针列表,它只引用但不实际包含对象。
-
本地活动对象 & 全局变量对象
- 在另一个函数内部定义的函数会将包含函数的活动对象添加到它的作用域链中。包含函数执行完毕后,其活动对象不会被销毁,因为被包含的匿名函数的作用域链仍然在引用这个活动对象。直到匿名函数被销毁后,该函数的活动对象才会被销毁。(仔细看书)
由于闭包会携带包含它的函数作用域,因此会比其他函数占用更多的内存。
-
闭包与变量
- 闭包只能取得包含函数中任何变量的最后一个值
function createFunctions() { var result = new Array(); for(var i = 0; i < 10; i++) { result[i] = function() { return i; } } return result; }
- createFunctions()函数会返回一个函数数组,并且每个函数都返回10。因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是同一个变量i。当createFunctions()函数返回后,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是10。解决方法如下
function createFunctions() { var result = new Array(); for(var i = 0; i < 10; i++) { result[i] = function(num) { return function() { return num; }; }(i); } return result; }
-
关于this对象
- 匿名函数的执行环境具有全局性,因此其this通常指向window。
var name = 'Lawrence'; var object = { name: 'Qwerty', getName: function() { return function() { return this.name; } } }; alert(object.getName()); // 'Lawrence'
- 匿名函数没有取得其包含作用域的this对象:每个函数在被调用的时候都会自动取得两个特殊变量,this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。解决方法:把外部作用域中的this对象保存在一个闭包能够访问到的变量里。
var name = 'Lawrence'; var object = { name: 'Qwerty', getName: function() { var that = this; return function() { return that.name; } } }; alert(object.getName()); // 'Qwerty'
- arguments也存在同样的问题
-
内存泄漏
- IE9-的版本中,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。(具体见书)
模仿块级作用域
- Java没有块级作用域。在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。
- 可以通过匿名函数模仿块级作用域
(function() {
// 这里是块级作用域
})(); // 包裹function的括号是必需的。
- 这种做法可以减少闭包占用内存的问题,因为没有指向匿名函数的引用。
私有变量
JavaScript没有私有成员的概念,所有对象的属性都是公有的。但是有私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量。私有变量包括函数参数、局部变量和在函数内部定义的其他函数。、
-
有权访问私有变量和私有函数的公有方法称为特权方法。有两种在对象上创建特权方法的方式
- 在构造函数中定义特权方法
function MyObject() { var privateVariable = 10; function privateFunction() { return false; } // 特权方法 this.publicMethod = function() { privateVariable++; return privateFunction(); }; }
利用私有和特权成员,可以隐藏那些不应该被直接修改的数据。
-
静态私有变量
- 通过在私有作用域中定义私有变量或函数,同样可以创建特权方法,其基本模式如下
(function() {
var privateVariable = 10;
function privateFunction() {
return false;
}
MyObject = function() {
}; // 全局变量
// 特权方法
MyObject.prototype.publicMethod = function() {
privateVariable++;
return privateFunction();
};
})();
```
* 多查找作用域链中的一个层次,就会在一定程度上影响查找速度。
-
模块模式
- 为单例创建私有变量和特权方法。JavaScript以对象字面量创建单例对象。模块模式通过为单例添加私有变量和特权方法使其得到增强
var singleton = function() { var privateVariable = 10; function privateFunction() { return false; } //特权方法和属性 return { publicProperty: true; publicMethod: function() { privateVariable++; return privateFunction(); } }; }();
* 这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的。
```
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);
}
}
};
}();
```
-
增强的模块模式
- 在返回对象之前加入对其增强的代码。
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; }();