开始......
第一步:当一个页面产生时浏览器就创建了一个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();
画个丑图(见笑):
延长作用域链: 官方的说法总是这么高大上,但是总是让人不明白,我来翻译一下,延长嘛,就是在原有作用域列表上再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
************************************************************************************************************************************************