本文分为(chang)几(pian)个(da)部(lun)分
- 作用域链(Scope chain)
- 闭包(Closure)的定义
- 闭包(Closure)的使用
最开始学习前端的时候,总是听到一个很新奇的词叫闭包。总觉得这个闭包听起来很高大上,当时也看了很多,还是觉得模糊不清,直到当时看到了一句话,豁然开朗。
“JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。”
接下来就从这点出发,来看下怎么理解闭包。
作用域链(Scope chain)
首先:
要理解 作用域链 (Scope chain) 首先要理解 作用域 (Scope)是什么;
-
作用域:在JavaScript中,作用域是执行代码的上下文。
-
作用域有三种类型:
全局作用域 :在代码中任何地方 都能访问到。
局部作用域(函数作用域) :只在固定的代码片段中可访问到。
eval作用域 : 与调用eval的方式相关联。 -
关于JavaScript中的块级作用域
JavaScript中没有块级作用域,逻辑语句 ( if(){} ) 和 ( for(){} ) 无法创建作用域。所以变量可以相互覆盖。这里可以看一个例子
var a = 1; //定义一个全局变量a 并且初始化为1
if(true){
a = 3;//在if语句中 给 变量 a 赋值,因为if无法创建块级作用域 ,所以这里访问到的是全局变量a
for(var i = 0 ; i < 3 ; i++ ){
a = i;//在for语句中给a赋值 因为for无法创建块级作用域,所以这里访问到的也是全局变量a
}
}
- 关于JavaScript中声明变量时使用的var
//在a函数中 我们声明了一个变量var
var a = function(){
s = 2;//如果s的作用域是函数作用域,那么我们在函数外打印它,结果是undefined 因为找不到
}
console.log(s);//在控制台打印出2
JavaScript 会将 缺少var的变量声明的变量 声明在全局中。
上面的例子如果我们在s前面加一个var 那么 就会打印出undefined 因为此时s在用var声明时,它的作用域变成了函数a的作用域。
很好理解对吧。
那么当一个函数被执行时,JavaScript会去查找与变量关联的值,这个查找的过程会遵循一个规则:
这个规则,就是作用域链。
可以通过一个例子来理解什么是作用域链。
var sthToAlert = 'peace peace'//首先定义了一个变量sthToAlert 并初始化
var func_1 = function(){//定义第一层函数func_1
var func_2 = function(){//定义第二层函数func_2
console.log(sthToAlert);//在func_2中打印变量sthToAlert 结果是 'peace peace'
}
}
代码很简单,但是能说明很多问题。
首先,JavaScript在执行这段代码的时候,它执行到console.log(sthToAlert); 时,会发现,嗯?这个值哪里冒出来的。
然后它会 由内而外 的去寻找sthToAlert的定义。
顺序也就是 检查func_2 中是否有sthToAlert的定义 →
检查func_2的父函数func_1 中是否有sthToAlert的定义 →
检查func_1的父函数中是否有sthToAlert的定义
···
检查全局作用域内中是否有sthToAlert的定义(如果没找到 就会返回undefined)
那么 如果我们同时在全局和func_2中 同时定义了 sthToAlert 会怎么样呢?
JavaScript会找到就近的定义(func_2中sthToAlert的定义),一旦找到了关于sthToAlert的定义,就不再找下去了。
闭包(Closure)的定义
现在我们知道了作用域链的概念,那我们回头再来看最开始的这句话:
“JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。”
换句话说,函数的作用域链 是根据 函数定义时的位置 决定的
而不是在调用时确定的。这叫做 "词法作用域"
那么这和闭包又有什么关系呢?
可以结合一个场景来看:
//我们定义了一个person 函数
var person = function(){
//在函数的内部 我们定义了这个person 的birth
//但是我们不想让别人一下就知道这个人的 birth的信息
//所以我们用了一个 var 来定义这个变量
var birth = "June/18";
}
//我们很想打印这个birth
//但是又因为birth在定义时 是person函数作用域,所以在外面访问不到
//只能返回undefined
console.log(birth);
那么这时候我们怎么解决这个问题呢?
答案就是用闭包来解决。
怎么做呢?修改下代码
//我们定义了一个person 函数
var person = function(){
//在函数的内部 我们定义了这个person 的birth
//但是我们不想让别人一下就知道这个人的 birth的信息
//所以我们用了一个 var 来定义这个变量
var birth = "June/18";
//为了能在外部访问到birth的值,我们返回一个匿名函数来做这件事情。
return function(){
console.log(birth);
}
}
//我们只需要调用person返回的匿名函数就可以达到效果
person()();
现在已经简单的说明了闭包的概念。
闭包(Closure)的使用
在说到闭包的使用之前,可以先看一个例子。
//定义了一个函数Q 这个函数接收一个string类型的参数
var Q = function (string){
//把传入的参数赋值给当前对象的status属性
this.status = string;
};
//注意这里没有通过new关键字调用
console.log((Q('111')).status);
//这里返回的是:Uncaught TypeError: Cannot read property 'status' of undefined
//注意这里是通过new关键字调用
console.log((new Q('111')).status);
//这里返回的是:111
这个例子很有意思,在函数调用的方式不一样会直接导致结果的不一样。
因为在JavaScript中我们直接调用一个函数时,这个 this 会绑定到 全局对象 。
然后就很好理解为什么提示 'status' 属性的 undefined 因为这里的this指向全局对象this。
在通过new调用时,那么实际上JavaScript做的事情是,它会再创建一个链接到该函数的prototype成员的新对象,并且把this指向这个新对象。同时在函数调用结束,会把这个对象返回。
用new关键来调用函数的这种行为称为:构造器调用模式
说了这么多,这和闭包有什么关系呢?
如果说,我并不想让其他调用者知道,这个函数里面的属性是怎么样定义的,而是提供一个公用的getter和setter 让其他人来读取或者修改这些私有属性。这时候闭包就派上了大用场。看一个例子:
//创建一个myObject对象,对象内定义了一个匿名函数。
var myObject = (function(){
//在函数内部定义一个属性 name 因为函数作用域的原因,这个属性为私有。
var name = 'dendi';
//返回一个对象,对象本身包含了两个函数,一个getName() 一个setName()
return{
getName:function(){
return name;
},
setName:function(_name){
name = _name;
}
}
});
// 调用myObject来创建一个实例。
var temObj = myObject();
console.log(temObj.getName()); //输出dendi
temObj.setName('dendoink');
console.log(temObj.getName());//输出dendoink
(结束啦。)
网友评论