函数表达式 及 闭包
函数表达式
函数又两种定义方式:
①.函数声明
它有个特性叫 函数声明提升.可以把这个声明放到最后面,前面调用不会报错.
var ret = abc();
function abc(){return "abc";}
②.就是本页重点说的 函数表达式
它不具有所谓的"提升",所以一定先定义好,再在下面调用才行.
函数表达式有几种方式: 赋给变量 , 作为返回值 , 作为函数参数
创建了一个匿名函数(也叫lambda函数,拉姆达)
var func = function(){} //赋给变量.
function outer(){
return function( arg ){ // 作为返回值
console.log(arg);
}
}
以下写法时极其不好的 :
if(condition){function func_a(){}}
else{function func_b(){}}
但可以用函数表达式改写
var func ;
if(condition){func = function(){}}
else{func = function(){}}
函数表达式 在递归中的写法
经典阶乘函数 提到过一种 使用 arguments.callee 的用法,用于把实现和函数名称解耦.
但是在严格模式下,会报错.因此可以用下面的形式来折衷处理.
var func = (function factorial(num){
if(num <= 1) return 1 ;
else return num * factorial(num -1);
});
func(3); //display 6
var another = func ;
another(2); //display 2
这样即使有其他变量接收这个函数(another),也不会导致another()执行时,找不到factorial的情况.
闭包
所谓闭包,就是在函数中创建了一个函数.这个内部的函数,拥有者访问外部函数所有变量的权利.
关于函数/作用域,有个活动对象的概念.即当前作用域中所有的数据(无论是变量还是方法),都会挂载到这个活动对象上.
比如:
function outer(age){
var result = "result";
return function(index){
var inner_var = "variable";
// do something
}
}
这里内部函数的活跃对象 可以看做是
{
index: undefined,
inner_var: "variable",
arguments: [{index:xxxx}]
}
外部函数活动对象 , 可以理解为:
{
arguments:[{age:xxxx}],
age:xxx,
result : "result"
}
那它们还有个 全局变量对象 ,就不列举了.
而 内部函数的作用域链 就是 [ {内部函数_活动对象},{外部函数_活动对象},{全局对象} ]
了解了 活动对象 , 就明白下面的函数为什么出现全都是返回10的函数
function createFuncs(){
var result = new Array();
for(var i = 0 ; i < 10 ; i++){
result[i] = function(){
return i ;
};
}
return result ;
}
createFuncs()[0](); // display 10
其实把 活动对象的概念加上 , 就可以把上面代码解读成 下面的代码:
function createFuncs(){
var _outer = this ;
_outer.result = new Array();
for(_outer.i = 0 ; _outer.i < 10 ; _outer.i ++){
result[_outer.i] = function(){
return _outer.i ; // 这里可是使用的引用哦 , 而不是复制了i的值.是通过引用去找值.
}
}
return _outer.result ;
}
createFuncs()[0](); // display 10
也就是说 , 变量i 实际是外部函数的 活动对象 的属性i ,
那么所有引用了这个 活动对象的属性i的实例,都会随着这个值变化(无论循环多少次,它们都是指向同一个对象属性)
那么当i++后,外部函数 的这个活动对象的属性i的值会增加,所有使用了这个对象属性的内容都会变化成属性最终的值.
想要 这段程序符合预期 , 可以改成如下模样 :
function createFuncs(){
var result = new Array();
for(var i = 0 ; i< 10 ; i++){
result[i] = function(num){
console.log(num);
return function(){
return num ;
}
}(i);
}
}
其实上面代码,就是利用了"基本类型数据 传递参数是按值传递" 的特性,
当i或者说_out.i 传递给 num参数时 , 做了一次值的copy, 变成了内部函数的变量值
所以最后结果就可以是复合预期的函数数组了.
闭包中 this 的问题
var name = "window name";
var person = {
name : "person name",
getName : function(){
return function(){
return this.name ;
};
}
}
person.getName()(); // display 'window name'
为什么会如此呢?
内部函数的作用域链中,this是指内部函数的活动对象
它会把外部函数的活动对象--也叫this给屏蔽掉,这是作用域链搜索机制导致的,本身有,就不会向上找
那么person.getName() 返回了一个函数 , 再调用这个函数,这个调用环境已经变成了全局环境中调用
在浏览器中,this自然指向window对象.
想要按预期工作,可改为:
var name = "window name";
var person = {
name : "person name",
getName : function(){
var _this = this ;
return function(){
return _this.name ;
};
}
}
person.getName()(); // display 'person name'
这是由于,_this是作为外部函数活动对象的属性存在了,也就是变量嘛
内部函数可以访问到这个外部变量,并持有它.
闭包引起的内存泄露
function addEvent(){
var element = document.getElementById("el");
element.onclick = function(){
console.log(element.id);
};
}
由于绑定的事件函数 是个闭包函数(也是匿名的),它保有了外部活动对象的一个引用.
只要闭包函数在外界使用了,那么对于"利用引用计数法"来回收垃圾的浏览器,比如万恶的IE(早期),
那么,引用数至少为1,造成无法回收.
可以对上面代码做以下优化: (这个方法,本人没有细细考虑,转述作者的说法而已)
function addEvent(){
var element = document.getElementById("el");
var eleId = element.id;
element.onclick = function(){
console.log(eleId);
};
element = null ;
}
把内部函数使用的引用,放到外部函数的变量中使用.
将element设置为null,切断内部函数对它的持有.
模拟块级作用域
(function(){
// 这里是 块级作用域
})();
讲一下这个结构,实际上,第一个括号内是一个 函数表达式,只不过是个匿名的.
var func = function(){}; // 看到了没, 在这里 (function(){}) 其实就是 func啊
func();
之所以不能直接 function(){}(); --会报错
是由于 function a(){} 不可以直接调用一样,解析器认为你在声明函数,说白了就是解析器不认它.
那么加了括号了呢 (function(){}()) , 解析器会认为它是个 表达式,因为里面是函数,自然就是函数表达式了
这种用法一般用在全局作用域中,防止命名冲突,而且执行完了也方便回收.
不过其实什么地方都可以使用它
function test(){
(function(){
for(var i = 0 ; i<10;i++){}
})();
console.log(i); // 报错,i is not defined
}
私有变量
当前作用域中的所有变量,内部函数,以及 当前函数的参数 都可以看做是私有变量.
而有权访问所有私有变量以及私有函数的方法 称为 特权方法.
特权方法有两种定义方式 :
利用构造函数 以及 利用静态私有变量
示例:
function MyObject(){
var count = 0;
function inner(){}
// 特权方法 -- 绑定this了
this.privilegeMethod = function(){
count ++;
return inner();
}
}
好处是,可以隐藏那些不应该直接被修改的数据:
function Person(name){
this.getName = function(){
return name ;
};
this.setName = function(value){
name = value ;
};
}
var person = new Person("origin");
person.getName();
person.setName("new name");
坏处就是,所有实例没有共享这些方法,没有抽象出去.
下面看看利用静态私有变量是如何解决这个问题的.
(function(){
//创建 共享变量 -- 所有对象实例共享.
var name = "共享变量";
//构造函数 , 使用函数表达式,而不是函数声明 -- 不想创建局部的.同样的原因,所以没有用var来声明,为了添加到全局.在严格模式下会报错.
Person = function(value){
name = value ;
};
// 公有/特权 方法
Person.prototype.getName = function(){
return name ;
};
Person.prototype.setName = function(value){
name = value ;
}
})();
var p = new Person("p");
p.getName();
这个模式的问题是,全都是静态变量..实例之间会互相影响.
模块模式
一般使用这个技巧是为了建立一个全局的,单例的实例来管理程序应用级的信息.
所以它虽然用instanceof检查是Object类型的,也没所谓.不会用于传参给函数,也就无需检查类型.
示例:
var application = function(){
// 私有变量 及 函数
var components = new Array();
// 初始化
components.push(new BaseComponent());//不要在意BaseComponent是什么
// 公共方法 -- 特权方法
return {
getComponents : function(){
return components ;
},
registerComponent:function(component){
if(typeof component === 'object'){
components.push(component);
}
}
};
}();
增强的模块模式:
主要是为了使其是特定的对象,而不仅仅是Object类型.
var application = function(){
var name = '';
var components = new Array();
components.push(new BaseComponent());
var app = new BaseComponent(); // 不再直接return 字面量对象,而是让它称为具体的类型
app.getComponentCnt = function(){
return components.length;
};
app.registerComponent = function(component){
if(typeof component === 'object'){
components.push(component);
}
};
return app ;
};