一、闭包
要理解闭包,需要先理解作用域与词法作用域。
(一)作用域
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'
【附】以上拙见有待修改补充。
参考: