JS高级知识(作用域,作用域链,闭包)

JS高级知识(作用域,作用域链,闭包)

作用域

  • 概念:作用域就是变量和函数的可访问范围,控制着变量和函数的可见性与生命周期,在JavaScript中变量的作用域有全局作用域和局部作用域;在java中作用域就是一个{}花括符是块级作用域,而javaScript没有块级作用域,所以不一定{}包裹的就是局部作用域,,像if(){}这个还是全局作用域,只有函数级作用域,也就是函数包裹内的才是局部作用域。

      <script type="text/javascript">
      var m = 5;
      if(m == 5){
        var n = 10;
      }
      alert(n); //代码1 n=10
    script>
  • 全局作用域:最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的
   <script>
      var outerVar = "outer";
      function fn(){
         console.log(outerVar);
      }
      fn();//result:outer
   script>
  • 局部作用域:和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的,最常见的例如函数内部
<script>
  function fn(){
    var innerVar = "inner";
    console.log(innerVar) // inner
  }
 fn();
 console.log(innerVar);// ReferenceError: innerVar is not defined
script>

需要注意的是,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

   <script>
      function fn(){
         innerVar = "inner"; //没有var声明,相当于声明了全局变量
      }
      fn();
      console.log(innerVar);// result:inner
   script>
  • 全局变量的污染
 <script>
      var flag="外面";//全局变量
      function t(){
           flag="里面";  //在函数中污染全局变量
      }
      t();
     !function (){
         console.log(flag); //打印"里面"  //为什么是里面,而不是外面
      }();
  script>

因为t()函数将flag全局变量污染了,javascript中没有用var声明的变量都是全局变量,而且是顶层对象的。因为函数t()中没有加var 来定义局部变量flag导致全部变量flag就被”里面”赋值了。
解决办法:在方法中局部变量申明一定要记得加上var不然将会变为申请全局变量,导致变量污染
+ 变量提升问题

   <script>
      var scope = "global";
      function fn(){
         console.log(scope);//result:undefined  为什么是undefined
         var scope = "local";
         console.log(scope);//result:local;
      }
      fn();
   script>

第一个输出居然是undefined,原本以为它会访问外部的全局变量(scope=”global”),但是并没有。这可以算是javascript的一个特点,只要函数内定义了一个局部变量,函数在解析的时候都会将这个变量“提前声明”

   <script>
      var scope = "global";
      function fn(){
         var scope;//提前声明了局部变量
         console.log(scope);//result:undefined
         scope = "local";
         console.log(scope);//result:local;
      }
      fn();
   script>
  • 块极作用域:在Java/C/C++中有块极作用域,但是在JavaScript中没有,JavaScript中只有函数作用域
for(int i = 0; i < 10; i++){
//i的作用范围只在这个for循环
}
printf("%d",&i);//error
  • 函数作用域:
   <script>
      for(var i = 1; i < 10; i++){
            //coding
      }
      console.log(i); //10  
   script>

作用域链(作用域只会向上查找,从里到外)

  • 概念:根据在内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问。 JavaScript中所有的量都是存在于某一个作用域中的除了全局作用域, 每一个作用域都是存在於某个作用域中的在试图访问一个变量时JS引擎会从当前作用域开始向上查找直到Global全局作用域停止。((包括:自身的作用域, 以及外部函数的作用域, 以及全局作用域,形成一条作用域链))
var A;//全局作用域
function B()
{
    var C;//C位于B函数的作用域
    function D()
    // D的作用域链: D作用域 -> B作用域  -> 全局作用域
    {
        var E;//E位于D函数的作用域
        alert(A)
    }
}

当alert(A)时, JS引擎沿着D的作用域, B的作用域, 全局作用域的顺序进行查找。这三个作用域组成的有序集合就成为作用域链。

  • 执行环境:执行环境是JavaScript中最为重要的一个概念。执行函数定义了变量或函数有权访问的其它数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object)和一个作用域链(scope chain),环境中定义的所以变量和函数都保存在其变量对象中。执行环境分为两种,一种是全局执行环境,一种是函数执行环境。
    • 全局执行环境
      ​ 全局执行环境是最外围的一个执行环境,其变量对象就是全局活动对象(window activation object),全局执行环境直到应用程序退出——例如关闭网页或浏览器——时才会被销毁。
    • 函数执行环境
      ​ 每个函数都有自己的执行环境。当执行流进入一个函数时,函数环境就会被推入一个环境栈中。当函数执行完之后,栈将其环境弹出,把控制权返回给之前的执行环境。函数执行环境的变量对象是该函数的活动对象(activation object)。
  • 绘制作用域链的规则
    1》 将整个script标签的全局作用域,定义为0级作用域链。将全局作用域上的数据(变量、函数、对象等等)绘制在该链上。
    2》由于在词法作用域中,只有函数可以分割作用域。因此,只要遇到函数,就要引申出新的作用域链,级别为当前链的级别 + 1;
    3》 重复第二步骤,直到没有遇到函数为止。
  • 作用域链和原型链的区别:
    作用域链是针对变量的 全局作用域==>函数1作用域==>函数2作用域
    原型链是针对构造函数的 Object ==> 构造函数1 ==> 构造函数2


    function a(){};
    a.prototype.name = "追梦子";
    var b = new a();
    console.log(b.name); //追梦子

    原型链是针对构造函数的,比如我先创建了一个函数,然后通过一个变量new了这个函数,那么这个被new出来的函数就会继承创建出来的那个函数的属性,然后如果我访问new出来的这个函数的某个属性,但是我并没有在这个new出来的函数中定义这个变量,那么它就会往上(向创建出它的函数中)查找,这个查找的过程就叫做原型链

闭包

  • 闭包概念:一个函数有权去访问另一个函数的内部数据。(沿着作用域链寻找),让这些外部变量始终保存在内存中
  • 缺点: 会造成函数内部的数据常驻内存,从而引发内存泄漏的问题。
  • 解决的问题:解决在函数外部无法访问函数内部的数据
  • 为了防止闭包导致内存泄漏,在使用完闭包后,将其赋值为null即可。
  • 闭包怎么缓存数据?因为在函数内部有方法(函数)对其有引用,并且又将其返回到外部作用域上的一个变量接收。
  • 简述闭包流程

    1. 如果一个函数内部返回了一个函数或多个函数
    2. 而返回的函数中具有对自己的外层作用域中的成员的读取或修改
    3. 那么称这个函数叫做闭包函数
    4. 为什么在外部拿到该函数就可以访问内层函数的成员?
      • 原因就在于:函数的作用域链以函数定时时的位置为准
      • 这里就利用函数实现了对内部 foo 变量的读取和修改
    <script>
        function fn() {
          var foo = 'bar'
           var getFoo = function () {
            return foo
          }   
          var setFoo = function (val) {
            foo = val
          } 
          // 调用函数将setFoo和getFoo以对象的形式进行返回
          return {
            getFoo: getFoo,
            setFoo: setFoo
          }
        }
        var obj = fn()
        console.log(obj.getFoo()) //bar
        obj.setFoo('hello')
        console.log(obj.getFoo()) //hello
      script>
  • 关于this问题
  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());//result:The Window

this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象调用时,this等于那个对象。不过,匿名函数具有全局性,因此this对象同常指向window

  • 垃圾回收机制:GC(Garbage Collection)
    • 引用计数
      如果一个变量被另一个变量引用,那么该变量的应用计数+1;当这个变量不再引用该变量(该变量被回收掉了)时,此时这个变量的引用计数 - 1;
      GC会在一定时间间隔去查看每个变量的计数,如果为0, 就将其占用的内存回收。
      缺点: 循环引用:会造成无法回收变量的内存
      a{ val : b} , b{val:a} b = null;(a = null)
    • 标记清除
      从当前文档根部(window对象)找一条路径,如果能到达该变量,那么说明此变量不应该 被回收掉
      反之,应该被回收其所占用的内存。
      如果变量进入某个执行环境,那么给其标记为 “进入环境”;
      如果上述执行环境执行完成,被销毁,那么该环境内的所有变量都被标记为“已出环境”
      如果变量被标记为已出环境,就会被回收掉其占用的内存空间。
  • 闭包的应用
    1. 计数器
    2. 对象的私有属性
    3. 解决fib数列递归性能
    4. 沙箱模式: 好处: 不会污染全局变量(或者其他作用域的变量),并且能保证自己的代码安全执行。
      特性:a: 能分割作用域,不会污染全局 fn
      b: 在分割后的作用域内部的代码要能自执行。fn()
      结构:
      (function (){
      //代码块
      window.fn = fn;
      }());

你可能感兴趣的:(js)