【学习记录】闭包与this

145 阅读6分钟

一、闭包    

     要理解闭包,需要先理解作用域与词法作用域。

(一)作用域

1、作用域

     作用域就是一套规则,用来决定在何处以及如何查找变量(标识符)的规则。换一种说法即——作用域是变量与函数的可访问范围,它控制着变量与函数的可见性和生命周期。最直白的说法就是——作用域是所写代码的那一个区块,他决定之后怎么查找变量。

2、全局作用域与函数(局部)作用域

     作用域分为全局作用域函数(局部)作用域。当然,在es6中还有块级作用域(此处不过多介绍)。

     可以通过以下例子来理解上述两种作用域:比如广州羊城通可以在广州市以及广东其他地方通用,但是在其他省市羊城通便失效了,所以羊城通是限制在广东这个作用域内的(局部)。又如某些信用卡在世界各地是通用的,所以它的作用域是全世界(全局)。

    具体可看如下的例子:

var a;//全局作用域
function b(){
    var c;
    //c位于b函数的作用域
function d(){
    var e;
//e位于d函数的作用域
    alert(a);
    }
}

3、作用域链

     可以发现,在查找某变量(如上述的a变量)的时候,会先在(d函数)函数作用域中查找,若没有查找到,继续往外层(b函数作用域)查找,直到查找到全局作用域,然后停止查找。这是一个从里层往外层(从函数作用域到全局作用域)查找的过程,这就像是在顺着一条链条从下往上查找变量,而这条“链条”就称之为作用域链,就比如查找a变量时所依据的“链条”——d函数作用域->b函数作用域->全局作用域。

(二)词法作用域

         作用域有两种工作模式——词法作用域和动态作用域

  • 词法作用域: 函数的作用域在函数定义的时候就决定了。(静态作用域)
  • 动态作用域: 函数的作用域是在函数调用的时候才决定的。

如下述所示例子:

var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar(); // 1

由于函数foo()的作用域是在它定义的时候就决定了,那么在foo()执行时它所定义的作用域中是没有value值的,所以就在全局作用域中查找,可找到value值为1,故上述程序的输出为1。

(三)闭包

关于闭包的含义,有以下几种描述

闭包是指有权访问另一个函数作用域中的变量的函数;——《JavaScript高级程序设计》

从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。——《JavaScript权威指南》

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。——《你不知道的JavaScript》

       这样看来,其实第三种说法会让人更好地理解。

具体可看以下代码:

function fn1() {
	var name = 'AA';
	function fn2() {
		console.log(name);
	}
	return fn2;
}
var fn3 = fn1();//实际上就是将fn2的引用赋值给fn3
fn3();

以上代码可以清晰地展示闭包:

  • fn2的词法作用域能访问fn1的作用域

  • 将fn2当做一个值返回

  • fn1执行后,将fn2的引用赋值给fn3

  • 执行fn3,输出了变量name

如上,fn3引用了fn2,所以fn3也就是fn2函数本身。执行fn3能够正常输出name——这也说明了fn2能够记住它所在的词法作用域,并且还可在当前词法作用域之外运行,fn2就产生一个闭包。

【注意】正常来说,在上述的fn1执行完之后,其作用域会被销毁并由垃圾回收器释放占用的内存空间。但是,闭包可使fn1的作用域存活下来,这样fn2就依旧持有该作用域的引用,这个对fn1作用域的引用即为“闭包”。

总而言之:某个函数在定义时的词法作用域之外的地方被调用,闭包可以使该函数极限访问定义时的词法作用域。

二、JavaScript中的this

      this——JavaScript关键字,它是函数运行时,在函数体内部自动生成的一个对象只能在函数体内部使用。

      相关的书籍解释如下:

**      this对象是在运行时基于函数的执行环境**绑定的。——《JavaScript高级程序设计》

对于以上这句关于this的解释,可分三点来看:

(1)、this是一个对象。

(2)、this的产生与函数有关。

(3)、this与执行环境绑定。

        那么,函数有不同的使用场合,因而this也有不同的值,具体几种情况如下所示:

**       1、直接在函数中调用**

        可以知道,这是函数最通常用法,属于全局性调用,此时this代表的是全局对象——window对象

var x = 1;

function test() {
    console.log(this.x);
}

test();  //1

        js中有一个全局对象window,直接调用函数test时,就相当于调用window下的test方法,包括直接声明的变量也都是挂载在window对象下的。

**       2、作为对象方法的调用**

       具体可看如下代码

var person = {
    "name": "xxx",
    "showName": function () {
        console.log(this.name);
    } 
};

person.showName(); // 'xxx'

在showName方法中this指向的是对象person。因为调用showName的是person对象,所以showName方法中的 this.name 其实就是 person.name。

也就是说:函数作为某个对象的方法调用,这时**this**就指这个上级对象。

【注意】方法写在哪个对象下其实不重要,重要的是这个方法最后被谁调用了,this指向的永远是最终调用它的那个对象

【附】匿名函数不管写在哪里,只要是被直接调用,它的this都是指向window,因为匿名函数的执行环境具有全局性。

**       3、作为构造函数调用**

所谓构造函数(使用new操作符来构造一个对象),就是通过这个函数,可以生成一个新对象。这时,**this**就指这个新对象

function Person (name) {
    this.name = name;
}
var global = Peson('global'),//直接调用Person函数,故this指向window
    xiaoming = new Person('xiaoming');//构造函数生成的对象,this指向生成的对象xiaoming

console.log(window.name); // 'global'
console.log(xiaoming.name); // 'xiaoming'

**      4、apply调用**

**         apply()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后调用这个函数的对象**。因此,这时this指的就是这第一个参数

var person = {
    "name": "shenfq"
};
function changeJob(company, work) {
    this.company = company;
    this.work    = work;
};

changeJob.call(person, 'NASA', 'spaceman');
console.log(person.work); // 'spaceman'

changeJob.apply(person, ['Temple', 'monk']);
console.log(person.work); // 'monk'

【附】以上拙见有待修改补充。

参考:

  1. Javascript的this用法