四时宝库

程序员的知识宝库

Js基础27:作用域(js作用域链的理解)

函数和变量的有效范围就是作用域

1、作用域的概念

 var a = 10;
 function f1(){
   console.log(a);
 }
 f1();// 变量a在函数外定义,可以在函数内使用
 
 function f2(){
   var b = 20;
 }
 console.log(b); // 变量b在函数内定义,在函数外无法访问,报错:b is not defined

这是因为js中存在作用域的概念。

作用域:

作用域就是指定一个变量或者一个函数的作用范围。

能在页面的任何位置都可以访问,称为 全局作用域

只能在局部(函数内)访问,称为为 局部作用域

在全局作用域下声明的变量,称为 全局变量

在局部作用域下声明的变量,称为 局部变量

上述代码中,a是全局变量,b是局部变量

ES5中只有函数才有作用域,所谓是局部作用域也可以叫函数作用域。

作用域的作用就是为了把数据进行保护,不让外部的数据对我们的数据进行污染

2、函数和变量声明的提升

在JS中存在一个很重要的特性,函数和变量声明的提升。怎么理解声明提升呢?我们先来看一道面试题:

以下①②③④打印什么?

 console.log(a);//①
 var a = 123;
 console.log(a);//②
 
 console.log(f);//③
 f();//④
 function f() {
     console.log("函数声明提升");
 }

①处的代码如果按照我们以前的理解,代码从上而下执行,那么在执行这行代码的时候,a还没有被声明,所以直接访问一个没有被声明的变量,程序应该报错。

但是结果却大出所料,这里得到的结果是undefined。

③处的结果也和我们最初的认识是不一样的,结果为f对应的函数对象。

造成这个结果是因为变量和函数的作用域提升的原因,什么意思呢?

JS是解释性语言,JS引擎对代码的处理分为两步:

  1. 预解析处理:在代码执行之前,对代码做全局扫描,对代码中出现的变量和函数提前声明处理;
  2. // 上面代码预解析后会变成这样:
    var a;//变量提前声明,但不初始化
    //函数提前声明
    function f() {
    console.log("函数声明提升");
    }
    console.log(a);//undefined
    a = 123;
    console.log(a);//123
    console.log(f);//函数对象
    f();//函数声明提升
  3. 调用执行:然后自上而下的执行代码

3、声明提升的规则

声明提升是将变量或者函数的声明提升到当前作用域的最顶端。在具体使用的过程中存在以下需要注意的细节。

  1. 变量和变量同名,解析之后只存在一个当前变量的声明。
  2. console.log(a);
    var a = 123;
    console.log(a);
    var a = 456;
    console.log(a);
  3. 解析之后:
  4. var a;
    console.log(a);//undefined
    a = 123;
    console.log(a);//123
    a = 456;
    console.log(a);//456
  5. 函数和函数同名,后面的声明将前面的覆盖。
  6. f();
    function f() {
    console.log("1");
    }
    f();
    function f() {
    console.log("2");
    }
    f();
    function f() {
    console.log("3");
    }
  7. 解析之后:
  8. function f() {
    console.log("3");
    }
    f();//3
    f();//3
    f();//3
  9. 函数和变量同名,只有函数声明提升,忽略变量的声明。
  10. console.log(a);
    var a = 123;
    console.log(a);
    function a() {}
    console.log(a);
    function a() {}
    console.log(a);
  11. 解析之后:
  12. function a() {}
    console.log(a);//函数a
    var a = 123;//将前面的函数覆盖,a的值变为123
    console.log(a);//123
    console.log(a);//123
    console.log(a);//123
  13. 如果是命名函数,则只将前面的变量声明提升,函数不动。
  14. console.log(fn1);
    function fn1() {
    }
    console.log(fn1);
    console.log(fn2);
    var fn2 = function () {
    }
    console.log(fn2);
  15. 解析之后:
  16. function fn1() {
    }
    var fn2;
    console.log(fn1);//fn1函数
    console.log(fn2);//undefined
    fn2 = function () {
    }
    console.log(fn2);//fn2函数
  17. 其他问题
  18. var a = b = c = 9;// 从右往左赋值

    // 不适用var 声明变量 不管在哪里都是全局变量
    function fn(){
    a = 10;
    }

4、作用域链和访问规则

在JavaScript里面,函数内部是可以包含另一个函数的

 function a(){  
   function b(){
     
  }  
 }

此时函数b就被函数a包含越来了,这样就形成了两层作用域。

如果有以下代码:三个同名变量放在三个作用域内

 var x = 10;
 console.log(x);
 function a(){
   var x = 20;
   console.log(x);
   function b(){
     var x = 30;
     console.log(x);
  }
   b();
 }
 a();

会依次输出:10,20,30

虽然多个变量x同名,但是不同作用域内优先使用自己内部作用域的变量x。

如果代码做一下修改:删除函数b的局部变量x

 var x = 10;
 console.log(x);
 function a(){
   var x = 20;
   console.log(x);
   function b(){
     console.log(x);
  }
   b();
 }
 a();

依次输出:10,20,20

函数b内部没有变量b,会向自己的外面的作用域查找x变量,函数a内的x变量离函数b最近,会优先得到函数a的变量x

代码再做修改:再删除a的局部变量x

 var x = 10;
 console.log(x);
 function a(){
   // 函数a内部的变量x也没有了
   console.log(x);
   function b(){
     // 函数b内部没有x变量了
     console.log(x);
  }
   b();
 }
 a();

会依次输出:10,10,10

函数b内部没有x变量,会向函数a的作用域查找,但是函数a内部也没有x变量,会向函数a的上一层作用域再查找,直到查找到了全局作用域。

代码再次变化:全局的变量x也删除

 // 全局的变量x也没有了
 function a(){
   // 函数a内部的变量x也没有了
   function b(){
     // 函数b内部没有x变量了
     console.log(x);
  }
   b();
 }
 a();

函数b内部没有变量x,会顺着上层作用域一层一层地查找,直到全局作用域也没有,就会报错。

总结:

  • 只有函数可以制造作用域链结构
  • 只要是代码,就至少有一个作用域,即全局作用域。
  • 凡是代码中有函数,那么这个函数就构成另一个作用域。
  • 如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域。
  • 将这样的所有的作用域列出来,可以有一个结构:函数内指向函数外的链式结构——作用域链。
  • 由内而外的访问规律,一直向外找,找不到,报错。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接