函数声明和函数表达式
- 理解函数提升的关键,就是理解函数声明与函数表达式之间的区别
- 函数声明会提升,函数表达式则不会
//不要这样做!
if (condition) {
function sayHi() {
alert("Hi!");
}
} else {
function sayHi() {
alert("Yo!");
}
}
表面上看,以上代码表示在 condition 为 true 时,使用一个 sayHi()的定义;否则,就使用另 一个定义。实际上,这在 ECMAScript中属于无效语法,JavaScript引擎会尝试修正错误,将其转换为合 理的状态。但问题是浏览器尝试修正错误的做法并不一致。大多数浏览器会返回第二个声明,忽略 condition;Firefox会在 condition 为 true 时返回第一个声明。
如果需要这种不同的定义方式,可以使用函数表达式
递归
- 在严格模式下,不能通过脚本访问 arguments.callee,访问这个属性会导致错误。不过,可以使用命名函数表达式来达成相同的结果
//严格模式会报错
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);
}
});
- 即便把函数 赋值给了另一个变量,函数的名字 f 仍然有效,所以递归调用照样能正确完成
闭包
- 当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链。 然后,使用 arguments 和其他命名参数的值来初始化函数的活动对象(activation object)。但在作用域 链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作为作用域链终点的全局执行环境。
- 在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量
- 后台的每个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,而函数这样的局部环境的变量对象,则只在函数执行的过程中存在。
在创建函数 时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。 当调用函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对 象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域链的前端。显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲, 当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。
在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。因此,在函数内部定义的子函数的作用域链中,实际上将会包含外部函数的活动对象。
- 函数表达式的后面可以跟圆括号,表示函数调用,将返回值赋值给变量
- 匿名函数如果需要自调用,必须用()包裹
私有变量
-
任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。 私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
-
在函数内部可以访问这些变量,但在 函数外部则不能访问它们。
-
第一种是在构造函数中定义特权方法
在构造函数内部,直接使用函数声明或者变量声明来创建私有变量和函数
公有变量和函数通过this创建
- 通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法
(function () {
//私有变量和私有函数
var privateVariable = 10;
function privateFunction() {
return false;
}
//构造函数
MyObject = function () {
};
//公有/特权方法
MyObject.prototype.publicMethod = function () {
privateVariable++;
return privateFunction();
};
})();
这段代码的重点是构造函数的声明,不使用var或者函数声明的方式来声明构造函数,使得构造函数声明在全局作用域中,同时构造函数的定义又存在与局部作用域内,所以new这个构造函数的实例时,就会将这个构造函数的私有变量带入实例对象,从而可以让每一个实例都能够访问这个构造函数的私有对象
- 初始化未经声明的变量,总是会创建一个全局变量。
- 这个模式与在构造函数中定义特权方法的主要区别,就在于私有变量和函数是由实例共享的。由于 特权方法是在原型上定义的,因此所有实例都使用同一个函数。
-多查找作用域链中的一个层次,就会在一定程度上影响查找速度。而这正是使用 闭包和私有变量的一个显明的不足之处。
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;
}();