函数作用域
函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取。
var v = 1;
function f() {
console.log(v);
}
f()// 1
上面的代码表明,函数f内部可以读取全局变量v。
在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)。
function f(){
var v = 1;
}
v // ReferenceError: v is not defined
上面代码中,变量v在函数内部定义,所以是一个局部变量,函数之外就无法读取。
函数内部定义的变量,会在该作用域内覆盖同名全局变量。
var v = 1;
function f(){
var v = 2;
console.log(v);
}
f() // 2
v // 1
上面代码中,变量v同时在函数的外部和内部有定义。结果,在函数内部定义,局部变量v覆盖了全局变量v。
注意,对于var来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。
if (true) {
var x = 5;
}console.log(x); // 5
上面代码中,变量x在条件判断区块之中声明,结果就是一个全局变量,可以在区块之外读取。
var a =1;
function f1(){
a=3
}
声明全局变量情况:
a = 3 优先会认为它是一个赋值。但是问题在于它会沿着这个作用范围的树去找,首先找当前所在 f1 的范围有不有 3 的声明,结果发现没有就去父作用域,发现全局范围里面有一个 a ,于是就是在给全局范围的 a 赋值。
如果全局范围也没有 ,这时候写 a = 3 就是在声明并赋值。沿着作用域的树,树里一直都找不到声明,就认为这棵树里从来没有 a ,那就是在给全局的window 赋值了一个 window.a = 3 ,只有这一种情况是在声明全局变量,大部分情况下 a 都是在沿着这棵树找,找到变量就给它赋值。
只要有一个函数就有一个作用域
函数作用域最要注意的就是变量提升,拿到一个函数只要里面有变量声明,就把声明提到当前作用域的最上边去:
当运行上面的代码的时候,函数f1里面的console.log(a)这句的时候打印出来的a是多少,我们要先把所有的变量声明都提升一下,变成下面的代码
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2。
总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
例题1:
var x = function () {
console.log(a);
};
function y(f) {
var a = 2;
f();
}
y(x)// ReferenceError: a is not defined
上面代码将函数x作为参数,传入函数y。但是,函数x是在函数y体外声明的,作用域绑定外层,因此找不到函数y的内部变量a,导致报错。
同样的,函数体内部声明的函数,作用域绑定函数体内部。
例题2:
function foo() {
var x = 1;
function bar() {
console.log(x);
}
return bar;
}
var x = 2;var f = foo();
f() // 1
上面代码中,函数foo内部声明了一个函数bar,bar的作用域绑定foo。当我们在foo外部取出bar执行时,变量x指向的是foo内部的x,而不是foo外部的x。
例题3:
var a = 1
function f1(){
var a = 2
f2.call()
}
function f2(){
console.log(a) // 是多少
}
f1.call()
按照前面说的,因为函数执行时所在的作用域是函数定义时所在的作用域,而不是调用时的作用域,那么f2的作用域为全局,所以a等于1
例题4:
同样的对于对象里有回调函数的也是一个道理
var obj = {
fn(){
var a = 1
this.fn1(function(){console.log(a)})
},
fn1(f){
var a = 2
f()
}
}
obj.fn() //?
上面的代码this.fn1()括号里面的就是函数声明也就是function(){console.log(a)},而函数的作用域只与函数声明的时候有关所以这时候回调函数的作用域就在fn里,所以执行的时候打印的是1,而不是回调函数调用时fn1的作用域
词法作用域(静态作用域)
var global1 = 1
function fn1(param1){
var local1 = 'local1'
var local2 = 'local2')
function fn2(param2){
var local2 = 'inner local2'
console.log(local1)
console.log(local2)
}
function fn3(){
var local2 = 'fn3 local2'
fn2(local2)
}
}
上面的词法树,全局window下有global1变量和fn1变量,fn1下又有fn3、fn2、param2、local1和local2变量,fn3下面有local2,fn2下有local2变量;
比如:我要在fn2里面打印local1,根据右边的词法树,fn2里只有local2,所以它就会去fn2所在的那一级树里面找,然后就找到了local1
总之,就是先在你当前的作用域里找,如果当前的作用域找不到再去它的上一级找,直到找到为止,另外要注意词法树只是用来分析这个变量是不是当前对应层级的变量,不是用来分析这个变量的值是不是对应层级的变量的值,也就是说只分析语义,不分析值
问题:
var a = 1;
function b(){
console.log(a)
}
b这个函数里的a肯定是全局里的a吗?
答:是,因为函数内没有声明a这个变量,所以他这个a肯定是全局的a
- b函数里的a一定是1吗?
答: 不是。
比如:
var a = 1;
function b(){
console.log(a)
}
a=2;
b()
上面的代码当调用函数b的时候才会去执行函数里面的内容,也就是才回去打印出a,而这个时候a是2,这也说明了词法作用域只能确定这个a是不是全局里的a,但不能确定这个a的值是不是全局里的a的值
闭包
- 如果一个函数,使用了它范围外的变量,那么(这个函数+这个变量)就叫闭包
var a = 1;
function f4(){
console.log(a)
}
上面的代码函数f4使用了他外面的a变量所以是闭包
在另一个函数里使用当前函数的变量的场景
- 在当前函数里调用另一个函数,将当前函数的变量作为参数直接传给调用的函数
function a(){
var z = 1
b(z)
}
function b(z){
console.log(z)
}
a()//1
上面的代码在b函数里需要使用a里面的变量z,只需要在a里面调用b的时候把z变量作为形参传入即可
- 在当前函数中不调用另外一个函数,只需将另外一个函数中需要用到的变量,在当前函数中return 一下就可以,然后通过另一个函数调用这个函数将返回值赋值给一个变量即可
function a(){
var z = 1
return z
}
function b(){
//这里一定要将调用后的值赋值给一个新变量
var z = a()
console.log(z)
}
b()//1
上面的代码将a中的z变量作为返回值,然后b函数中调用这个a拿到了z的值,然后将z的值赋值给一个新的变量,因为一个变量只在一个函数里有效,所以函数的返回值得在另一个函数中通过一个变量接收