第七章 函数表达式
7.1 函数表达式
-
-
定义函数的方式有两种
-
-
函数声明 function functionName(){}
-
- 大部分浏览器支持 functionName.name 返回 functionName;
- 函数声明提升,再执行代码前会先读取函数声明;
-
函数表达式 var functionName = function(){};
-
- 创建一个函数赋值给变量,该函数称为匿名函数,function 后面没有标识符;
-
-
-
- 调用匿名函数时,需要加上(),如 functionName(),匿名函数最后直接加入()则可以自动执行,()中可以填入参数,调用函数内部返回的匿名函数,则或许要 functionName()()// 两个括号;
- 递归 调用函数自身
7.2 闭包
-
- 匿名函数是指没有标识符的函数,闭包是指有权访问到外部变量(另一个作用域中的变量)的函数;
2.
-
- 当某个函数被调用时,会创建一个执行环境及对应的作用域链;
- 然后,使用 arguments 和其它命名参数来初始化函数的活动对象;
- 在作用域链中,外部函数的活动对象始终处于第二位,第三位于以此类推,终点位全局执行环境;
-
闭包会占用更多的内存,建议只在绝对必要时使用;
过程
-
创建 compare()函数时,先创建预先包含全局变量对象的作用域链,这个作用域链保存在内部的[[scope]]属性中;
-
调用 compare()函数时,会创建一个执行环境,复制函数[[scope]]属性中的作用域链;
-
此后,又有一个活动对象(自身变量对象)被推入作用域链的前端;
-
作用域链本身是指向变量对象的指针列表,只引用不实际包含;
-
访问函数中的变量时,就会从作用域链中搜索,一般函数执行完毕后,局部活动对象会被销毁,内存仅保留全局作用域(全局执行环境的变量对象);
过程
- 在一个函数内部定义的函数,会将它的外部函数的活动对象添加到作用域链中;
- 当外部函数执行完后,它的作用域链会被销毁,但它的活动对象并不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象,所以该活动对象仍然会留在内存中,直到匿名函数被销毁(匿名函数=null);
7.2.1 闭包与变量
-
- 闭包只能取得外部函数中任何变量的最后一个值;
function createFunctions() {
var result = new Array()
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i
}
}
return result
}
var funcs = createFunctions()
//every function outputs 10
for (var i = 0; i < funcs.length; i++) {
document.write(funcs[i]() + '<br />')
}
该例子中,最后取得数组值都是 10,最后执行时,此时的 i 已经累加到 10,引用的是同一个 i;
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 funcs = createFunctions()
//every function outputs 10
for (var i = 0; i < funcs.length; i++) {
document.write(funcs[i]() + '<br />')
}
该例子中,因为循环时,最后传入了参数 i(num),并且传入参数属于值传递,所以每一个的值的副本都不相同,最后数组值也均不相同;
7.2.2 this 对象
-
- 闭包中使用 this 可能会导致某些问题,如匿名函数的执行环境具有全局性,当它在全局环境下执行时,this=window
- 防止该情况可以在外部函数中将外部函数的 this 赋值给某个变量,然后在匿名函数中使用该变量;
-
e.g var that = this ; return that.name;
7.2.3 内存泄漏
7.3 模仿块级作用域
-
- js 中没有块级作用域(for 之类),但函数是有作用域的,可以模仿块级作用域。
function(){
//作用域
}();
这样写会报错,因为 js 将 function 关键字当作函数声明的开始,而函数声明后不能跟圆括号,而函数表达式可以,所以要将其转换为函数表达式;
;(function () {
//作用域
})()
7.4 私有变量
-
-
严格而言,js 没有私有变量,所有对象属性都是共有的,但函数中定义的变量,可以认为是私有变量,因为外部不能访问;
-
有权访问私有变量和私有函数的共有方法称作特权方法
-
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"
通过构造函数中定义特权方法
getName()和setName()是特权方法(闭包)可以访问外部变量(name),这样的设置可以避免数据被直接修改,只有通过特权方法才能更改私有变量;
与其它构造函数模式相同,每一次生成新的实例时,都会重新创建这两个方法,而且必须通过构造函数模式来使用;
7.4.1 静态私有变量
-
- 通过在私有作用域中定义私有变量或函数,也可以创建特权方法;
- 通过原型定义公有(特权)方法;
- 私有作用域中的构造函数,并没有使用函数声明(函数声明只能创建局部函数),为了创建全局的构造函数,函数表达式中也没有使用 var;
;(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"
和原型模式一样,一个实例上调用 setName()会影响所有实例;
7.4.2 模块模式
-
-
模块模式用于为单例创建私有变量的特权方法,单例指的是只有一个实例的对象;
-
匿名函数返回了一个字面量对象;
-
function BaseComponent() {}
function OtherComponent() {}
var application = (function () {
//private variables and functions
var components = new Array()
//initialization
components.push(new BaseComponent())
//public interface
return {
getComponentCount: function () {
return components.length
},
registerComponent: function (component) {
if (typeof component == 'object') {
components.push(component)
}
},
}
})()
application.registerComponent(new OtherComponent())
alert(application.getComponentCount()) //2
7.4.3 增强的模块模式
-
- 返回了 BaseComponet 的实例对象,对于某些要求单例必须是某种类型的实例需求有用;
function BaseComponent() {}
function OtherComponent() {}
var application = (function () {
//private variables and functions
var components = new Array()
//initialization
components.push(new BaseComponent())
//create a local copy of application
var app = new BaseComponent()
//public interface
app.getComponentCount = function () {
return components.length
}
app.registerComponent = function (component) {
if (typeof component == 'object') {
components.push(component)
}
}
//return it
return app
})()
alert(application instanceof BaseComponent)
application.registerComponent(new OtherComponent())
alert(application.getComponentCount()) //2