声明:该系列文章致力于详尽挖掘js,html,css在浏览器是如何工作的,不为别的,不弄清楚源头,感觉如同无根之萍,本人能力有限,希望路过的大神能指导一下,不胜感激。

开始......

第一步:当一个页面产生时浏览器就创建了一个window对象,他也有一个比较官方的说法:全局执行环境,所有的全局变量和函数都属于window的属性和方法,当关闭网页或者关闭浏览器时,全局执行环境才会被销毁,包括其内部所有成员都被销毁。

第二步:加载脚本文件,加载完成后,js引擎分析它的语法与词法是否合法,如果合法进入预编译

第三步:预编译,寻找全局变量声明,把它作为window的属性加入到window对象中,并给变量赋值为'undefined';寻找全局函数声明,把它作为window的方法加入到window对象中,并将函数体赋值给他。注意:匿名函数是不参与预编译的(函数表达式)

第四部:解释执行,对变量进行初始化,这时遇到了函数的调用

第五步:函数调用,开辟一块内存空间(栈空间)官方的说法是环境栈,并在这块空间内创建函数的执行环境,执行环境的创建分为两个阶段。

           第一阶段:context阶段,发生在具体代码执行前,也就是函数执行时。

                   (1)创建作用域链赋值给函数的[[scope]]内部属性。

                   (2)创建“活动对象(AO)”他是一种特殊的“变量对象”;

                             创建arguments对象,其值为undefined;

                             创建形参变量,把它作为AO的属性加入到AO对象中,并赋值为'undefined';

                             寻找局部变量声明,把它作为AO的属性加入到AO对象中,并赋值为'undefined';

                             寻找全局函数声明,把它作为AO对象的方法加入到AO对象中,并将函数体赋值给他。

                            注意:匿名函数是不参与预编译的(函数表达式)

                   (3)给this赋值,值为AO的地址

           第二阶段:解释执行,对变量进行初始化,arguments赋值为实参数组,形参变量赋值为实参,并执行。

*********************分割线:由上边流程,详细解释变量声明提升,函数声明提升,作用域,作用域链,闭包*********************

变量提升:根据上边的分析,我们可以看见,无论变量声明在什么地方,在执行前都会被预先声明并赋值‘undefined’,值得注意的是ES6的let和const声明已经解决了变量提升问题。

函数整体提升:同理

作用域: (1)作用域到底是什么,我的理解是:作用域其实就是一块相对封闭的环境,然后这个环境里面有各种在这里生存的居民,就像生态系统一样,而且每个环境之间都不会相互影响。在这里我不禁要感慨一下人类的智慧,现在的计算机,就像地球的寒武纪,生命大爆发时期,我相信未来人类真的可以创造出一个次元世界。

                 (2)  作用域分为两类,局部作用域和全局作用域,在浏览器js世界中,局部作用域就是指的函数作用域,全局作用域就是指window对象,值得注意的是,es5是没有块级作用域概念的,es6引入了块级作用域的概念。

作用域链:(1)“联系”各个作用域的纽带,所谓的作用域链,本质就是链表结构,里面存放着与当前作用域有“联系”的作用域地址(更确切的说应该是变量对象的地址)。

                  (2)再来谈一谈这个“联系”是什么,我们看一个官方的例子:

var color = "blue";

function changeColor(){
  var anotherColor = "red";
  
  function swapColors(){
    var tempColor = anotherColor;
    anotherColor = color;
    color = tempColor;
  }
  swapColors();
}

changeColor();

 画个丑图(见笑):

javascript的运行方式 js怎么运行的_javascript的运行方式

延长作用域链: 官方的说法总是这么高大上,但是总是让人不明白,我来翻译一下,延长嘛,就是在原有作用域列表上再push一个地址。有两种情况会发生作用域链延长第一使用with语句例如:

with(location){
        let url = href
     }

try-catch语句的catch语句

 

闭包:官方定义闭包是有权访问另一个函数作用域中变量的函数,那个根据前面作用域以及作用域链的描述通俗的讲就是一个A函数套着另一个函数B,那么函数B就叫闭包。我们看一个官方的例子:

function createComparisonFunction(propertyName){

   return function (obj1,ogj2){
      var v1 = obj1[propertyName]
      var v2 = obj2[propertyName]

      if(v1 < v2){
        return v1
      }else{
        return v2
      }
   }
}

var compareNames = createComparisonFunction("name")

var result = compareNames({name:"A"},{name:"B"})


compareNames = null

      我们来分析一下:

       调用 createComparisonFunction("name") ,创建createComparisonFunction的AO对象,在AO对象中创建arguments = “undefined”,创建propertyName = “undefined”;

          创建作用域链,作用域列表存着createComparisonFunction AO对象的指针,和window VO对象的指针;

          绑定this指向createComparisonFunction AO对象;

          初始化arguments = ["name"],propertyName = "name";

          返回了一个匿名函数赋值给全局变量compareNames(函数定义方式之一:函数表达式);

         createComparisonFunction函数生命周期结束。

          调用compareNames({name:"A"},{name:"B"}),创建compareNames的AO对象......

         创建作用域链,作用 域列表存着compareNamesAO对象的指针,createComparisonFunction AO对象的指针,和window VO对象的指针;

          ......

           重点来了:compareNames函数查找作用域链发现可以访问到createComparisonFunction 函数内的变量和window内的变量,compareNames函数执行并引用createComparisonFunction函数内部的propertyName变量,执行结果赋值给了全局变量result。这时会有个有趣的现象:即使createComparisonFunction函数生命结束他的AO对象也不会被销毁,因为他的作用域地址在compareNames函数作用域链内存着,也就是说compareNames函数的作用域还在引用着createComparisonFunction AO对象,那么就只有等到compareNames函数销毁才会销毁createComparisonFunction AO,那么compareNames函数什么时候被销毁呢?关闭或刷新网页或者浏览器时才会被销毁,因为compareNames是全局的。所以需要手动解除compareNames对createComparisonFunctionAO的引用。compareNames = null

          

************************************************************************************************************************************************

欲知后事如何,请听下回分解