函数作用域

作用域的概念对于初学者来说可能比较难,它涉及到变量,函数等基础知识,理解作用域对于理解作用域链和闭包是非常重要的,今天闲来一起复习下作用域:

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;

好,接下来,我们看看函数的声明提升,先看看函数有常见的几种声明方式:

  1. 使用function命令声明,如:function f1(){};
  2. 使用变量赋值方式声明,也叫函数表达式,如var f1 = funtion(){};
  3. 使用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,这也是前面讲的,内部同名变量会覆盖全局变量,这也是理解闭包的基础哦!

今天到这里了,学习不停。。。。

 

posted @ 2017-09-09 14:46  全堆栈溢出攻城狮  阅读(364)  评论(0编辑  收藏  举报