一、作用域
作用域是可访问变量的集合
。
在 JavaScript 中, 对象和函数同样也是变量。
在 JavaScript 中, 作用域为可访问变量,对象,函数的集合。
JavaScript 函数作用域: 作用域在函数内修改。
常见的作用域分为:
全局作用域(window、global)
函数作用域(function)
块级作用域({})
词法作用域(this)
函数作用域
变量在函数内声明,变量为局部作用域,也称为函数作用域。
局部变量:只能在函数内部访问。
举个例子:
function fn() {
let a = 1
}
fn()
console.log(a) // 报错 a not defined
由此可看出,在全局访问这个变量a时,会报错,说明全局是无法获取到(闭包除外)函数内部的变量。
因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量。
局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。
如果想读取函数内的变量,必须借助 return 或者闭包
function fn() {
var b = 2
return b
}
fn()
console.log(b)
VM879:6 2
全局作用域
变量在函数外定义,即为全局变量。
全局变量有 全局作用域: 网页中所有脚本和函数均可使用。
let a = 1
function fn() {
console.log(a)
}
fn()
console.log(a) // 打印1 1
// 函数内修改变量a,因为变量a是全局的,因此都是打印2 2
let a = 1
function fn() {
a = 2
console.log(a)
}
fn()
console.log(a) // 2 2
如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量。
function fn() {
a = 2
console.log(a)
}
fn()
console.log(a) // 2 2
*注意:在函数内部没有声明的变量是作为window、global存在的,拥有全局作用域,但是这个变量是可以被delete的,而全局作用域不可以
块级作用域
ES6引入了let和const关键字,和var关键字不同,在大括号中使用let和const声明的变量存在于块级作用域中,在大括号之外不能访问这些变量。
{
let t1 = 1
const t2 = 2
var t3 = 3
console.log(t1, t2, t3)
}
console.log(t1, t2, t3) // t1 t2 报错 t3打印3
词法作用域
词法作用域,又叫静态作用域,变量被创建时就确定好了,而非执行阶段确定的。
只能在执行阶段才能决定变量的作用域,那就是动态作用域
var a = 12;
function foo(){
console.log(a) // 12
}
function bar(){
var a = 13;
foo();
}
bar()
由于JavaScript遵循词法(静态)作用域,相同层级的 foo 和 bar 就没有办法访问到彼此块作用域中的变量,所以foo执行时没有找到变量a,会向上级寻找,foo的外层是window,因此找到了a = 12,因此输出12。
二、作用域链
js当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域,如果全局作用域还查不到改变量,则在全局范围内隐式声明该变量(非严格模式下)或是直接报错。
就像上面那个例子,稍微修改一下变量,取值就不一样
var a = 12;
function foo(){
console.log(this.a) // 13
}
function bar(){
this.a = 13;
foo();
}
bar()
具体分析:
- foo函数在内部找不到变量a,this的指向又是在b1调用的时候确定的,向上一层作用域bar函数内部找
- 函数foo中的this指向是函数bar,在函数bar找到了变量a,因此this.a就是13
- 如果函数bar中找不到变量a,向上一层作用域找,即全局作用域,还是找不到则报错
三、闭包
介绍
闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁
。
作用
一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
可以看看阮一峰的文章例子
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
使用注意点
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
思考题
可以想想这两段代码分别输出什么值。
代码一:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
console.log(object.getNameFunc()());
代码二:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var self = this
return function(){
return self.name;
};
}
};
console.log(object.getNameFunc()());
分析:
由上图可看出,函数getNameFunc内部打印的this指向object对象,但是在函数getNameFunc中又return一个匿名函数,且该函数的this指向为window,因此直接调用函数getNameFunc会直接打印全局变量 "The Window"。但是如果使用self.name打印时,会输出"My Object"。
当使用object.getNameFunc的方式不同时,输出的内容也不一样
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
console.log(this)
var self = this
return function(){
console.log(this)
return self.name;
};
}
};
console.log(object.getNameFunc()()) // 'My Object'
var jj = object.getNameFunc()()
jj // 'My Object'
var kk = object.getNameFunc()
kk() // 'My Object'
var ll = object.getNameFunc
ll()() // 'The Window'
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。