ES5函数作用域
函数作用域: 在 JavaScript函数中声明的变量,会成为函数的局部变量。在函数外部不能访问。
全局作用域: 函数之外声明的变量,会成为全局变量。在函数内部可以访问。当函数嵌套的时候,内部函数与外部函数的这个变量就组成了闭包。
作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突
// ES5中没有块级作用域
{
var a = 10
}
console.log(a); // 10
//全局作用域:global/window/本文件内
var v1 = 10; //全局作用域
v2 = 20; // 所有末定义直接赋值的变量自动声明为拥有全局作用域
function foo() {
//函数作用域、局部作用域
var a = 3;
console.log(v1, v2);
console.log(this);
}
foo()
console.log(a); // a is not defined
案例:
var a = 10;
function foo() {
// 当函数内部有变量a时,会产生局部作用域,外界全局作用域中的a不会对函数内部造成影响
// 如果把函数内部的变量a注释掉是,函数内部的a输出的就是全局作用域中的a
console.log(a); //undefined
var a = 100;
console.log(a); // 100
function fn() {
console.log(a); //undefined
var a = 200;
console.log(a); // 200
}
fn()
}
foo()
-
自由变量
首先认识一下什么叫做 自由变量 。如下代码中,
console.log(a)
要得到a变量,但是在当前的作用域中没有定义a(可对比一下b)。当前作用域没有定义的变量,这成为 自由变量 。自由变量的值如何得到 —— 要到创建这个函数的那个父级作用域寻找,如果没有就一直向上级祖先元素寻找(这就是所谓的"静态作用域",静态作用域是指函数的作用域在函数定义时就已经确定了)var a = 100 function fn() { var b = 200 console.log(a) // 这里的a在这里就是一个自由变量 // 100 console.log(b) } fn()
-
作用域链
如下代码,若是在本函数内找不到变量,便向父级(上一层)寻找,直到找到全局作用域还没有找到,就宣布放弃。这种一层一层的关系,就是作用域链 。
var a = 100 function F1() { var b = 200 function F2() { var c = 300 console.log(a) // 自由变量,顺作用域链向父作用域找 //100 console.log(b) // 自由变量,顺作用域链向父作用域找 //200 console.log(c) // 本作用域的变量 //300 } F2() } F1()
闭包
闭包: 是指有权访问另一个函数作用域中的变量的函数。它由两部分构成:函数,以及创建该函数的环境。
闭包的生成有三个必要条件
- 函数嵌套函数
- 内部函数引用了外部函数中的数据(属性、函数)
- 参数和变量不会被回收
创建闭包最常见方式,就是在一个函数内部创建另一个函数。 下面例子中的 example就是一个闭包:
function func() {
var a = 1, b = 2;
function example() {
return a + b;
}
return example;
}
console.log(func()()); // 3
在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的用途: 闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
function f1() {
var n = 999;
nAdd = function () { n += 1 }
function f2() {
console.log(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,可以在函数外部对函数内部的局部变量进行操作。
综合案例-作用域链-闭包-经典面试题
var a = 10
function fn() {
var b = 20
function bar() {
console.log(a + b) //30
// 父级作用域中有两个b可以选择,创建这个函数的父级作用域优先
}
return bar
}
var x = fn(), // 执行fn() 返回的是bar
b = 200
x() //执行x,就是执行bar函数