实例解析:对象/闭包函数/自调用 this指向与作用域问题
实例代码
<script>
var num = 20;
var obj = {
num: 30,
fn: (function(num){
this.num *= 3;
num += 15;
var num = 45;
return function(){
this.num *= 4;
num += 20;
}
})(num)
};
var fn = obj.fn;
fn();
obj.fn();
console.log(window.num,obj.num);//240 120
</script>
代码解析
在开始代码解析之前,需要先强调一个要点,即自调用函数是在它被定义的时候运行,而通常不是等待被调用,但是在本文实例中,自调用函数不仅被调用了,并且还运行了一部分,这来自于它有趣的结构——自调用函数与闭包的结合。
代码结构解析
对象的定义与调用
var num = 20;
var obj = {
num: 30,
fn: (function(num){...})(num)
}
var fn = obj.fn;
fn();
obj.fn();
隐藏部分代码后,可以看到,代码总体由全局变量num,对象obj组成,obj中含有属性num与方法fn。
obj.fn指向的是一个自调用函数1,有趣的是,自调用函数中为一个闭包2 ,这意味着在定义对象obj时,自调用函数将自动运行返回return3后的函数,定义结束后,obj.fn中将保存return后的值。
var fn = obj.fn;
该句指将obj.fn的值赋给全局量fn,上段指出,定义结束后,obj.fn存放的为return后的函数,故此句为将return后的函数赋给fn,运行后,全局fn为存放return后函数的函数。
注:此刻不能为obj.fn(),方法加括号是调用的含义,语句变为调用方法obj.fn,因return后的函数中无返回值设置,全局fn将被赋值underfind。
fn();
该句为调用全局函数fn(),全局函数fn的值来自于obj.fn,即return后的函数。
obj.fn();
该句为调用obj内部的方法fn,其实仍然是return后的函数。
综上,整段代码的结构为,定义一个带有自调用闭包函数的对象obj,对象定义后自调用函数运行完毕,返回内层函数给obj.fn,后将该方法赋给全局函数fn,在全局函数与obj环境下,分别调用fn。
代码内部解析
变量的作用域与this指代
文中所指代码均在普通模式下运行,严格模式下this无法指向window,而是指向undefined。
代码的难点在于对象的方法fn。
1 fn: (function(num){
2 this.num *= 3;
3 num += 15;
4 var num = 45;
5 return function(){
6 this.num *= 4;
7 num += 20;
8 }
9 })(num)//定义即运行
fn为自调用函数,即对象obj定义时,1-5行代码运行,运行后返回5-8行代码给obj.fn。
值得一提的是,由于自调用函数无调用对象,默认为全局对象window,故形参num指向全局变量num,传入值为20。
此后函数运行中用到了两个num,一个是this.num,此时由于调用者为全局对象window,this.num==window.num。
另一个是num,num是传入的形参,虽然实参值也来自于全局变量num,但是与window.num作用域不同,是完全不同的两个变量。
num经过了两次声明,首先是作为实参的隐式声明,其后显式的声明提前后无效化。
运行中先将全局变量num20乘3变为60,再将形参num20+15变为35,后将45赋给形参,执行结束后全局变量num变为60,内部变量num变为45。
定义后的obj.fn
1 fn: function(){
6 this.num *= 4;
7 num += 20;
8 }
fn与obj.fn
全局函数fn来自于obj.fn,函数代码相同,区别在于调用环境不同,代码运行时,this的指代也不同,fn中的this.num指代全局变量num,而obj.fn中指代obj.num的属性值。
全局fn与obj.fn中相同的为内部num的值,当fn中出现num时,根据作用域链,默认先从函数内部找,函数内部未声明,因此函数为闭包内层函数,故从外层父函数找,得到父函数中的变量num,值为65。
作用域链中专有一格指向外层父函数的变量,阻止变量释放,当obj.fn赋给fn时一同赋值,故两者中内部变量num指向的是同一个变量,即return外层函数中的num变量。
调用全局函数fn时,全局变量num60乘4变为240,内部变量指向外层变量num45+20=65。
调用obj.fn时,obj.num属性30乘4变为120,指向外层变量的num65+20变为85。
最终输出结果为:
console.log(window.num,obj.num);//240 120