函数作用域
作用域的概念对于初学者来说可能比较难,它涉及到变量,函数等基础知识,理解作用域对于理解作用域链和闭包是非常重要的,今天闲来一起复习下作用域:
1、定义
作用域(scope)指的是变量可访问的范围,在JavaScript中只有两种作用域:一个全局作用域,另一个是函数作用域。全局作用域指变量在任何函数外声明(如果在函数内部声明的时候没有用var关键字,那就成了隐式全局声明了,也是全局变量,在严格模式下是禁止的),那么这样的话,在当前作用域下的任何地方都可以访问到这个全局变量;至于函数作用域,指的是变量在函数内部显式声明,这个变量只能在函数内部内被访问到,函数外部无法访问;
在全局声明的变量成为全局变量,在函数内部定义的变量成为局部变量,局部变量只能在函数内部读取;
var v = 1; function f(){ console.log(v); } f()// 1
在上面的代码中,变量v是全局中声明,为全局变量,在函数内就能读取到,所有输出结果为1
然鹅,在函数内部定义的变量为局部变量,在函数外无法读取哦,如下:
function f(){ var v = 1; }
//输出变量v console.log(v) // ReferenceError: v is not defined
在上面的代码中,由于变量v是在函数f内部定义的,所以在函数外部无法读取到,因此会报undefined哦;
如果在函数外部和内部同时出现同名变量呢?在函数内部读取的应该是全局的还是局部的呢?答案是局部变量,函数内部的局部变量会覆盖同名全局变量:
var v = 1;//全局变量 function f(){ var v = 2;//局部变量 console.log(v); } f() // 2 console.log(v); // 1
在上面的代码中,在全局和函数f中都有定义变量v,但是由于两个变量同名了,而且在f内部读取的变量v,所有局部变量v会覆盖掉全局变量v;
这里有个点需要注意下,前面我一直说的是显式声明变量,如果在函数内部隐式声明了变量,即没有用var命令去声明变量,这个时候这个变量就是全局变量,在开发中,一定要避免隐式声明变量,解决办法是在采取严格模式,严格模式下禁止隐式声明变量,会报错;
function fn(){
x = 5;
}; console.log(x);// 5
在上面代码中,就是读取了一个函数内部的隐式全局变量,这个变量在哪里都可以访问到;
2、变量提升
JavaScript引擎的工作方式是,在代码正式执行之前,会有预解析的过程,先解析(通读)代码,获取所有被声明的变量和函数,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句和所有的函数在其作用域内,都会被提升到代码的头部,这就叫做变量提升(hoisting),看看栗子:
console.log(a);//undefined var a = 1;
上面的代码输出结果为undefined,为什么?因为在预解析阶段,变量a声明被提升到了顶部,到执行console.时,a只是声明了,并没有到赋值(js代码从上到下依次执行),此时不会报错,因为存在变量提升,真正是这样运行的:
var a; console.log(a);//undefined a = 1;
最后的结果是显示undefined
,表示变量a
已声明,但还未赋值。
请注意,变量提升只对var
命令声明的变量有效,如果一个变量不是用var
命令声明的,就不会发生变量提升,因为没有用var声明,这个变量就是隐式全局变量,在全局都可以访问到,不会提升声明:
console.log(b);//1
b = 1;
好,接下来,我们看看函数的声明提升,先看看函数有常见的几种声明方式:
- 使用function命令声明,如:function f1(){};
- 使用变量赋值方式声明,也叫函数表达式,如var f1 = funtion(){};
- 使用Function函数创建实例声明,如var f1 = new Function();(此方法不常用)
看第一个,function命名声明函数,f1为函数名,这种方法声明的函数在预解析阶段也会发现声明提升,注意和变量不同的是,函数声明提升的时候,会同时赋值函数体:
f1();
function f1(){ //some code here };
以上代码,虽然函数f1在声明之前就调用了,但正常执行,没毛病老铁,其实这么写就相当于下面这样:
function f1(){ //some code here }; f1();
这是和变量声明不一样的地方;
看第二种,第二种采用变量赋值的方式,这个时候就当函数体是一个值,而且函数在JavaScript中就是一个值,可以被任意赋值,那么就和普通变量声明提前一样了,在声明提升的时候不会发生赋值行为:
f1();//Uncaught TypeError: f1 is not a function var f1 = function(){
console.log("啊哈");
};
结果是f1报错undefined;
至于第三种,和第二种一样,也是变量赋值形式,普通的变量提升;
3、函数本身的作用域
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
var a = 1; var x = function () { console.log(a); }; function f() { var a = 2; x(); } f(); // 1
上面的代码中,由于x函数是定义在f函数外部的,所以x函数作用域绑定在外层,内部变量a
不会到函数f
体内取值,所以输出1
,而不是2
。
总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
很容易犯错的一点是,如果函数A
调用函数B
,却没考虑到函数B
不会引用函数A
的内部变量。
再来看个具体栗子:
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
,导致报错。
同样的,函数体内部声明的函数,作用域绑定函数体内部。
function foo() { var x = 1; function bar() { console.log(x); } return bar; } var x = 2; var f = foo(); f() // 1
上面代码中,函数foo中定义了一个函数bar,在取出函数bar并调用的时候,bar内部的x变量访问的是外层函数foo中的变量x,而不是同名全局变量x,这也是前面讲的,内部同名变量会覆盖全局变量,这也是理解闭包的基础哦!
今天到这里了,学习不停。。。。