函数做用域

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

一、定义

做用域(scope)指的是变量可访问的范围,在JavaScript中只有两种做用域:一个全局做用域,另外一个是函数做用域。全局做用域指变量在任何函数外声明(若是在函数内部声明的时候没有用var关键字,那就成了隐式全局声明了,也是全局变量,在严格模式下是禁止的),那么这样的话,在当前做用域下的任何地方均可以访问到这个全局变量;至于函数做用域,指的是变量在函数内部显式声明,这个变量只能在函数内部内被访问到,函数外部没法访问;函数

在全局声明的变量成为全局变量,在函数内部定义的变量成为局部变量,局部变量只能在函数内部读取;学习

var v = 1;

function f(){
  console.log(v);
}
f()// 1

在上面的代码中,变量v是全局中声明,为全局变量,在函数内就能读取到,全部输出结果为1spa

然鹅,在函数内部定义的变量为局部变量,在函数外没法读取哦,以下:code

function f(){
  var v = 1;
}
//输出变量v console.log(v)
// ReferenceError: v is not defined

在上面的代码中,因为变量v是在函数f内部定义的,因此在函数外部没法读取到,所以会报undefined哦;blog

若是在函数外部和内部同时出现同名变量呢?在函数内部读取的应该是全局的仍是局部的呢?答案是局部变量,函数内部的局部变量会覆盖同名全局变量:ip

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

在上面代码中,就是读取了一个函数内部的隐式全局变量,这个变量在哪里均可以访问到;io

二、变量提高

 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;

至于第三种,和第二种同样,也是变量赋值形式,普通的变量提高;

三、函数自己的做用域

函数自己也是一个值,也有本身的做用域。它的做用域与变量同样,就是其声明时所在的做用域,与其运行时所在的做用域无关。

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,这也是前面讲的,内部同名变量会覆盖全局变量,这也是理解闭包的基础哦!

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

相关文章
相关标签/搜索