学习笔记10--函数表达式
函数表达式的特征
函数表达式和其他表达式一样,在使用前必须先赋值。
递归
//递归阶乘函数
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
//虽然这个函数表面看来没什么问题,但下面的代码却可能导致它出错。
var anotherFactorial = factorial;
factorial = null;
// alert(anotherFactorial(4));//TypeError
//可以用命名函数表达式避免
var factorial = (function f(num) {
if (num < 1) {
return 1;
} else {
return num * f(num - 1);
}
});
alert(anotherFactorial(4));//24
闭包
闭包是指有权访问另一个函数作用域中变量的函数。
创建闭包的常用方式是在一个函数内部创建另一个函数。
function createComparisionFunction(propertyName) {
return function (object1, object2) {
//内部函数可以访问外部变量propertyName
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
}
var compare = createComparisionFunction("name1");
//在createComparisionFunction函数外部调用内部函数compare仍可访问变量propertyName
compare();
当函数被调用时,会创建一个执行环境和相应的作用域链。
function compare (value1, value2) {
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);
以上代码先定义compare()函数,然后在全局作用域中调用了它。
1. 创建函数时,会创建一个预先包含全局变量对象的作用域链,并被保存到函数内部的[[Scope]]属性中。
2. 调用函数时,从[[Scope]]属性中复制作用域链,此后又有一个活动对象被推到作用域的前端。
compare在被调用时的作用域链
变量对象
存储当前作用域中定义的变量和函数声明,每个执行环境中都有一个变量对象活动对象
在函数被调用时创建,包含形参和arguments
作用域链的前端始终是当前执行代码所在的环境的变量对象,如果这个环境是函数,则将其活动对象作为变量对象。
全局变量对象始终是作用域链中的最后一个对象。
在一个函数内部定义一个函数,内部函数将会把外部函数的活动对象加到其作用域链中。
/*在匿名函数从createComparisionFunction中返回后,
匿名函数的作用域链被初始化为包含createComparisionFunction活动对象和全局变量对象,
这样,匿名函数就可以访问createComparisionFunction中的变量,
createComparisionFunction()函数执行完毕后,createComparisionFunction()的活动对象不会被销毁,但是作用域链会被销毁。
*/
//创建函数
var compareNames = createComparisionFunction("name");
//调用函数
var result = compareNames({ name: "Jack" }, { name: "Tom" });
在被调用compareNames时的作用域链
匿名函数被销毁后,createComparisionFunction的活动对象才会被销毁。
//解除对匿名函数的引用
compareNames = null;
闭包与变量
闭包只能取得外部函数中的变量的最终值。
function createFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
};
}
//此时,i = 10
return result;
}
var functions = createFunctions();
for (var i = 0; i < 10; i++) {
var temp = functions[i]();
console.log(temp);
}
/**output
10
10
...
10
*/
通过一个创建一个匿名立即执行函数,返回一个可以访问匿名函数变量num的闭包。
function createFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function (num) {
return function () {
return num;
}
}(i);
}
return result;
}
var functions = createFunctions();
for (var i = 0; i < 10; i++) {
var temp = functions[i]();
console.log(temp);
}
/**output
0
1
...
9
*/
关于this对象
this是基于函数的执行环境绑定的,当函数被作为某个对象的方法调用时,this对象指向那个对象。
var name = "the window"
var object = {
name: "my object",
getNameFunc: function () {//此函数被object对象的方法调用,所以this指向object
console.log("getNameFunc " + this.name);
return function () {//此匿名函数没有绑定到任何一个对象上,所以this指向window
return this.name;
}
}
}
console.log(object.getNameFunc()());
/*output
getNameFunc my object
the window
*/
在外部函数中定义一个that变量保存外部函数(getNameFunc)的this对象,通过闭包访问that变量来访问外部函数的this对象。
var name = "the window"
var object = {
name: "my object",
getNameFunc: function () {
var that = this;
return function () {
return that.name;
}
}
}
console.log(object.getNameFunc()());//my object
模仿块级作用域
//可以通过这种方式,创建一个私有作用域,匿名函数中定义的任何变量都在执行后被销毁
//经常子在全局作用域中使用,从而限制向全局作用域中添加过多的变量和函数
(function () {
//do something
})();
私有变量
所有对象的属性都是公有的
任何在函数中定义的变量都可以被看做私有变量。
在函数内部创建的闭包可以通过作用域链访问函数的私有变量。私有变量和私有函数的公有方法称为特权方法
//在构造函数中定义特权方法
function MyObject() {
//私有变量和私有函数
var privateVariable = 10;
function privateFunction() {
return false;
}
//特权方法
this.publicMethod = function (){
privateVariable++;
return privateFunction();
}
}
静态私有变量
(function() {
var name = '';
//没有使用var声明,是全局变量
Person = function(value) {
name = value;
}
Person.prototype.getName = function() {
return name;
};
Person.prototype.setName = function(value) {
name = value;
};
})();
var person1 = new Person('Jack');
alert(person1.getName()); //Jack
person1.setName('Tom');
alert(person1.getName()); //Tom
var person2 = new Person('Jerry');
//因为setName(), getName()在原型上定义的,被所有实例共享,在一个实例上调用会影响所有实例
//所以私有变量可以看做是静态的。
alert(person1.getName()); //Jerry
alert(person2.getName()) //Jerry
模块模式
//返回对象的匿名函数
var singleton = function() {
var privateVariable = 10;
function privateFunction() {
return false;
}
// 特权 / 公有方法和属性
//返回包含可以访问私有变量和函数的对象字面量
return {
publicProperty: true,
publicMethhod: function() {
privateVariable++;
privateFunction();
}
}
}
如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。以这种模式创建的每个单例都是Object的实例,因为最终要通过一个对象字面量来表示它。
/**
* 在创建这个对象的过程中,首先声明了一个私有的components数组,并向数组中添加了一个BseComponent的新实例。
* 而返回对象的getComponentCount()和registerComponent()方法,都是有权访问数组components的特权方法。
*/
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);
}
}
};
}();
增强的模块模式
增强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。例子:
如果需要application对象必须是BaseComponent的实例
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;
}();