2017-02-12
This, Closure
学一门语言往往在洞晓了某个知识点后就能渐入佳境。
对于JavaScript, 闭包(Closure)this 这两个知识点毫无疑问将是上述所谓渐入佳境分界点的最佳候选者们

# this

this 取名很好,照英文意思它大概就是 面前的这个,事实上,多多少少的确是这个意思。
按阮哥的说法是:
有一个总的原则,那就是: this指的是,调用函数的那个对象。
---- 阮一峰 - Javascript的this用法
更简单地来说是:
this is who call it.
this 指的是谁调用了它 (this就是它)
看代码:
00var sayName = function(){  // 这是个函数 
01    console.log(this.name); 
02}
03
04name = 'global'; // 这个name 其实就是 window.name  (window是JS在浏览器的宿主对象) 
05
06var obj = { 
07    name: 'obj inner', 
08    sayName: sayName 
09}
10
11sayName(); // 'global'
12obj.sayName();  'obj inner'
以上结果表明, this 指的就是那个调用他的对象
更进一步的说, this 是根据它的调用栈来定自己的值的,它的值等于调用位置,而调用位置就是调用栈的第二个位置。
this 能节省很多代码量,有时候需要自己为函数指定 this 指向谁,因此也就有了 call bind apply 方法,讨论这些偏离了这篇博文的中心内容,请移步下一篇 this 和 call, apply, bind

# 闭包

考察以下代码:
00function a(){
01    var number = '9'; 
02
03    setTimeout(function(){
04        console.log(number); 
05    }, 1000); 
06
07    return 0; 
08}
09
10a(); // '9'
你可能已经注意到了,在一秒之后,a早已返回 0,按理来说函数到结尾return之后会销毁其作用域,作用域上面的变量都会被回收,然而一秒之后运行的 console.log(number) 却可以正确返回结果。
这种特性、亦或者说这种现象被称为 闭包(Closure) 或者说 函数a产生了一个闭包 又或者说 那个匿名函数是一个闭包
再举个例子:
00function getSize(){
01    var width = 3; 
02    var height = 4; 
03
04    return function(){
05        return width * height;
06    }
07}
08
09// 你可能已经猜到了 temp 是 sayNumber 产生的一个闭包函数 
10var temp = getSize();  
11
12// 很明显 答案是 3*4 = 12 
13var size = temp(); 
14console.log(size); // 12
上面的代码表明:闭包和 return 可以将外部变量暴露在外面,而且,如果我不执行 temp 也就不会得到 size ,也就是说*求值被延迟了*,这个特性被称为 惰性求值

# 匿名函数

匿名函数 (anonymous function) 和 闭包经常为初学者所混淆,很多人写教程写闭包结果写着写着就变成讨论如何用匿名函数生成闭包了,这就会让初学者认为: 匿名函数的执行结果是闭包
而实际上,匿名函数的执行结果可以是闭包才是正确的,而且用匿名函数产生闭包确实是太常见了
上面那个getSize的例子返回的就是匿名函数,而这个匿名函数体现了闭包,可以称它为匿名闭包函数

# 箭头函数

箭头函数是ES6里的一种区别于声明函数或者函数表达式的函数。
他跟他的姐妹们主要的区别在于对 this 的处理,他会继承当前所在执行函数的this。
来考虑实现一个名为小明的对象,让他能害羞地延迟一秒钟后说出他自己的名字:
00function sayName_shy(){
01    setTimeout(function(){
02        // 这样做会有很严重的问题,
03        // 因为这个this 指向的地方你不清楚
04        // 也就是说你不知道是谁调用这个匿名函数 (具体得看浏览器)
05        // 所以 this.name 很可能不会是你期望的那个对象
06        console.log(this.name);  
07    }); 
08}
09var obj = {
10    name: 'xiao ming',
11    sayName_shy: sayName_shy
12}
13
14// 在chrome中得到结果 'global' 
15// 这里的this指向 宿主对象window 
16obj.sayName_shy();
要怎么做才能让小明含羞地一秒后说出自己的名字?
在箭头函数出现以前主要用 var that = this; 来产生对 this 的闭包:
00function sayName_shy(){
01    var that = this; 
02
03    setTimeout(function(){
04        // 这个that指的是 sayName_shy 执行时的this 
05        console.log(that.name);  
06    }); 
07}
箭头函数出现后上面可以简化为
00function sayName_shy(){
01    setTimeout(() => {
02        // 这个 this 指的还是 sayName_shy 执行时的this 
03        console.log(this.name);  
04    }); 
05}
再来看一个箭头函数和他的兄弟之间的对比:
00// ES5
01var a = function (){
02    return 55 + 99;
03};
04a();  // 154 
05
06// ES6
07var b = () => console.log(55 + 99); 
08b();  // 154
09
10// 一眼看不太懂的... 
11var c = (i) => console.log(i); 
12c(5);  // console.log(5)
所以,箭头函数是匿名函数,ES5匿名函数的语法糖;但又增加了ES5所没有的一些优点
crper - ES6折腾记: 箭头函数【Arrow function】要点及脱坑点
就是这样、的确如此。

# this, Closure

this和闭包两个同时出现的时候,JS 就变得非常灵活多变了,复杂度也会大大提高…
在异步环境中会经常遇到的… 就不展开了…
(你可能已经发现了 上面的害羞的小明在延迟了一段时间后告诉你 this和闭包同时出现了)

# Links

  1. 阮一峰 - Javascript的this用法
  2. 山边小溪 - 理解作用域和作用域链
  3. crper - ES6折腾记: 箭头函数【Arrow function】要点及脱坑点
  4. 翔のblog - JavaScript 核心学习——闭包
  5. 翔のblog - JavaScript 核心学习—— this 关键字




回到顶部