浅谈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,其实单独拿出来每一个都可以一篇大文章。

本文只是自己的一些理解,如果错误,望指正。