浅谈javascript执行环境、作用域
js的执行环境是一个很基础很基础的概念,基础到很多人都知道这个东西,但是又讲不清楚,本文讲讲作者对执行环境的理解(也许理解有错误,如果有望指正)
作用域
JS中声明变量是有作用域的,全局作用域和局部作用域(函数)还有ES6新引入的块级作用域。
var name='fakin'//全局作用域
function fakin(){
var name2='blog'//函数作用域
}
{//块级作用域
{let i = 5;}
console.log(i);//报错
}
用简单的话讲,每个变量有自己可蹦跶的地区,这个蹦跶的地区就是作用域,如果一个变量在函数体内部声明,则该变量的作用域为整个函数体(蹦跶的地区),在函数体外不可引用该变量。内部函数可以访问外部函数的变量,一层一层形成作用域链。
变量提升
使用var定义的一个特点就是变量提升,解析器会扫描这个函数体,然后边var 定义的变量全部’提升’到函数体内的顶部。
function fakin(){ //函数
console.log(name)
var name='fakin'
}
fakin()//undefined,没有报错哦
其实上面的代码相同于下面
function fakin(){ //函数
var name;//声明后没有赋值自动赋值undefined
console.log(name)
name='fakin'
}
fakin()
执行环境
执行环境(execution context,为简单起见,有时也称为“环境”)是 JavaScript 中最为重要的一个概
念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。——《高程》
说老实话,看到这些文字,我是想吐的,当然仔细想想也能明白,但是《高程》中也没解释‘执行环境’到底是个什么东西。
执行环境的定义
个人理解‘执行环境’,就是代码执行所在的环境。在web浏览器中,应该有两种执行环境
- 全局执行环境:web中的window
- 函数执行环境:函数自身的执行环境(局部执行环境)
当然应该还有一种叫做eval环境
。
执行环境的产生
-
全局执行环境会在执行流最开始执行的时候就产生了。
-
而函数执行环境是在函数调用的时候产生,每次调用都是产生一次新的执行环境。
很多人或者书籍上面都有说道:当执行流进入函数的时候,函数的执行环境就是被推入环境栈
,等函数执行完毕后环境栈
就会把这个执行环境推出。
个人认为,应该是把函数的变量对象推入环境栈
,等函数执行完毕后环境栈
就会把这个变量对象推出。
var name = "fakin";
function fakin(){
var color='red'
console.log(name)
}
fakin();
每一个执行环境都三个重要的属性:变量对象、作用域链、this
变量对象(VO)
一个执行环境一产生就会有一个对应的变量对象(variable object),这个对象保存执行环境中的所有变量和函数。(无法访问,解析器会使用)。
var name = "fakin";
function fakin(){
var color='red'
function blog(){
}
}
fakin();
如果执行环境是函数的话,则将活动对象(activation object)作为变量对象。
心累啊,一会变量对象,一会又活动对象!!!
活动对象也不复杂,其实就是函数的arguments
+函数的变量对象
作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。——《高程》
var name = "fakin";//全局作用域
function fakin(){
var color = "red";
//局部作用域,通过作用域链有权访问全局作用域中的变量和函数
}
fakin();
按照这个说法,就是当函数调用的时候,作用域链就会被创建。然后根据作用域链去访问变量和函数。
var a='global'
function fakin(){
console.log(a)
}
function blog(){
var a='local'
fakin()
}
blog()
先不要把你认为的答案说出来,咱们按照《高程》中的说法来做一次。我觉得按照《高程》的定义,那么肯定有人和最开始我认为的一样
blog()//local
说好的在调用的时候创建作用域链的嘛。所有fakin()
是不是有权访问blog()
内部的变量和函数呢!
所以我觉得,作用域链是函数调用的时候创建的,但是作用域链长度(深度)是很大程度是根据作用域来决定。而作用域是在定义的时候就写好的了,所以作用域链和函数的调用关系不大,和函数的定义有很大关系(或者说解析器在解析代码的时候)。否则无法解释上面的输出结果为什么会这样。
var a='global'
function blog(){
var a='local'
function fakin(){
console.log(a)
}
fakin()
}
blog()
为了验证这一点,咱们把fakin()
函数定义在blog()
内部,下面请看输出结果
很明显,作用域链长度(深度)是根据函数定义时候的作用域来决定的,而不是函数调用。
this(web环境下)
this指向调用它的对象。
function fakin(){
consoe.log(this)
}
fakin()//函数调用指向window
//等同于
window.fakin()
//在全局定义的任何变量和函数都挂载在window上
在看一个例子
var fakin={
name:'fakinBlog',
getName:function(){
console.log(this.name)
}
}
fakin.getName()//函数调用指向fakin,输出'fakinBlog'
从上面两个例子,可以看出this指向跟函数在哪里定义没有关系,只和函数在哪里被调用有关系。
this指向问题,非常的重要,而且很多面试题中会出现,出现的概率基本上在百分之九十以上。
结语
文章到此就结束了,涉及执行环境、作用域、作用域链、this,其实单独拿出来每一个都可以一篇大文章。
本文只是自己的一些理解,如果错误,望指正。