函数其实有4个阶段,定义阶段、调用阶段、执行阶段、销毁阶段,4个阶段各自有不同的作用。
执行上下文(执行环境)的生命周期包括:调用阶段、执行阶段、执行完毕出栈等待被回收。
1. 定义阶段
确定作用域(变量的可见性)。
2. 调用阶段
(1)创建变量对象
变量对象包括3部分:
A. 建立arguments对象。检查当前上下文中的参数,建立arguments对象下的属性与属性值。
B. 收集当前作用域内的所有变量声明和函数声明,每找到一个声明,就在变量对象中以变量名建立一个属性。
C. 收集当前作用域内的所有函数声明,每找到一个声明,就在变量对象中以函数名建立一个属性。
(2)创建作用域链
作用域链的最前端,始终是当前正在执行的代码所在环境的变量对象,作用域链中的下一个变量对象来自该函数的包含环境,而再下一个变量对象来自再下一个包含环境。这样,一直延续到全局执行环境,全局执行环境的变量对象始终是作用域链中的最后一个对象。
作用域链保证了当前执行环境对符合访问权限的变量和函数的有序访问。也就是说,如果函数要访问一个变量,会先从自己的变量对象里找,找不到,就去上一级环境里去找,再找不到,再往上上级环境里去找,知道找到为止,如果一直到全局变量对象里面还没找到,那就报错。
这里需要注意的是,作用域在定义阶段已经确定,无论函数在哪执行,它的作用域链都是固定不变的。js中只有函数作用域和全局作用域,以及eval作用域,对象不算作用域。以下的代码帮助我们更好的理解作用域:
var name = '外层name';
var obj = {
name: '对象name',
sayName: function() {
console.log(this.name);//这里的this不一定指向obj对象,要在调用时,this的指向才确定。
console.log(name); //这里的name指向全局的name,因为对象的{}不算作用域,而obj的外层没有函数包裹,因此外层环境指向全局。
}
}
var myFunction = obj.sayName;
//上一行的赋值语句换成一下形式更好理解:
//var myFunction = function() {
// console.log(this.name);
// console.log(name);
// }
obj.sayName() //对象name 外层name
myFunction(); //外层name 外层name
复制代码
强调:this是函数调用的时候才确定,而作用域在函数定义的时候已经确定了。
对于一个函数来说,虽然它的作用域链是调用的时候才生成,但是组成作用域链的各个变量对象的值,其实早已经在编译阶段确定了。
(3)设置上下文值
上下文就是this,在函数调用的时候确定。
3.执行阶段
进行赋值操作,并且代码最终被执行。
注意
1.在编译阶段,所有变量的值都是未知的。
参考网址:yangbo5207.github.io/wutongluo/j…
如有错误,敬请指正(╹▽╹)