JavaScript中作用域和预解析

作用域以及预解析

 

        在javascript中作用域是非常重要的,本文章将会说明作用域以及我们在工作,以及面试中的一些面试题,如果有不足的地方希望大家可以评论指出来,自己一定会及时的改正错误,避免大家走入一些误区。

 

        谈及作用域先就必须要说明预解析和词法作用域。

 

        下面我们先说明一下:

 

        预解析

 

代码在正常执行操作之前会对文档进行一次解析,这个操作就是将声明提升,

 

声明包括全局范围内 1.带有var的变量, 2.函数  

 

文档预解析后会把文档中在全局函数中的内容储存起来,将全局中带有var的变量(var和变量名,注意:变量体不会随着提升,加载var只是告诉我们在文档中有一个全局变量是fn,并不会有其他的作用)提升到最前面

 

预解析后会正常的读取代码(由上至下由左到右)

 

下面举例说明一下预解析:

 

var fn=456;

function fn(){

}

        在上述的代码中我们预解析后会变成: 

 

var fn;

function fn(){

}

fn=456;

        所以不管function是在前面还是后面我们打印fn出来的结果都是456。

 

        词法作用域

 

词法作用域在书写代码的时候就已经决定了,与运行无关

 

可以分割词法作用域的只是函数,别的不可以分割

 

        下面举例说明一下词法作用域     

 

  if(true){

      function fn(){

          alert("true");

      }

  }else{

      function fn(){

          alert("false");

      }

  }

fn();

            语句在预解析后,会得到以下结果

 

function fn(){

    alert("true");

    }

function fn(){

    alert("false");

    }

if(true){

    }

else{

    }

fn();

        再逐行进行解析;所以上面的函数会被下面的函数覆盖(函数内部的内容会被保存);

 

       在函数执行时会进入判断语句为true的语句中,但是这个时候fn这个函数在预解析后已经变成了false,所以这个时候打印出来的应该是false。

 

        注意:

 

全局中的函数在预解析之后内容会被保存,在执行时不会被二次解析,会被直接拿来用

 

上述例子我们写代码中不可能出现,因为函数不能被语句进行包裹,上面只是为了给大家进行演示而做的例子

 

正式进入主题介绍作用域

 

不被函数包裹的带有var的变量他们的存在于作用域链的零级链中

被函数包裹的带有var变量他们是存在于作用域中的一级链中

构造函数原型属性(prototype)等是存在于作用域中的二级链中 ,

以此类推,函数一直包裹那么,作用域链也会一直递增下去

在此中要特别注意的是隐式全局变量,隐式全局变量如果在函数内部只有在函数在执行时才会被调用​

             下面通过一个复杂的面试题来为大家讲解作用域链:

 

        

 

function Foo(){

   getName = function(){

      console.log(1);

   };

   return this;

}

Foo.getName = function(){ console.log(2); };

Foo.prototype.getName = function(){ console.log(3); };

var getName = function(){ console.log(4); };

function getName(){ console.log(5); }

 

Foo.getName();             

getName();                 

Foo().getName();           

getName();                 

new Foo.getName();         

new Foo().getName();       

new new Foo().getName();   

         第一步当文档加载的时候会进行预解析,将声明和带有var的全局变量名提升,

        解析完之后零级链上回出现两个函数foo和getname

 

              第二步会由上往下加载给foo函数添加一个名为getname的函数给foo的原型中添加一个getname;在刚才的预解析中,零级链上有一个getname,加载之后出现另一个getname会把之前加载的getname覆盖掉

 

        第三步进入函数的执行,

 

       函数在执行时如果在原来的作用域链中有这个函数时,不用从新二次加载,如果出现同名会直接拿来用,

 

foo.getname在刚才已经定义了,所以第一个直接得到的是2;

getname在刚才的作用域零级链中已经被从新定义覆盖,所以第二个直接得到的是4;

会先执行foo函数,但是在foo的函数中存在了一个隐式的全局变量(getmame函数);所以当foo执行时会将这个全局变量函数释放出来,getname覆盖原来零级链上的getname;最后执行return this,这个时候this指向的是window,所以会这句话翻译过来也就是window.getname;全局变量中的getname得到的是刚才foo释放出来的隐式全局变量。所以第三个得到的是1;

getname和刚才第三个翻译过来的结果是一样的,只不过是将window省略; 所以第四个的结果同样是1;

new foo.getname, foo不是一个函数,所以不能被new,会先执行后面的Foo.getname;foo.getname和第一个一样;第五个所以得到的是2;最后执行new随后foo.getname变成了一个空对象

new foo().getname() foo是一个函数;new之后就是指被构造函数实例化的对象,对象.getname,但是现在foo这个函数中没有this.getname;所以直接沿着Foo的原型链忘上找;得到了存在于原型链中foo.prototype.getname所以第五个得到的数值是3;

第七个和第六个一样,会执行后面的new Foo().getname;得到一个结果是3;随后把这个函数new了一下,变成了一个空的对象

        所以得到的结果是2,4,1,1,2,3,3     

 

        注意:

posted @ 2020-01-22 21:19  珍惜缘份  阅读(...)  评论(...编辑  收藏