您的位置:首页 > Web前端 > JavaScript

由一道面试题浅析JS的底层运行机制

2020-06-07 04:31 176 查看

这道面试题看似简单,涉及到的底层知识却很多,今天带领大家来一起分析这道题,题目如下

var x = [12, 23];
function fn(y) {
y[0] = 100;
y = [100];
y[1] = 200;
console.log(y);
}
fn(x);
console.log(x);

在分析这道题之前咱们首先来了解几个概念:

  • 堆(Heap) /栈(Stack) 内存
  • 执行上下文栈 ECStack(Execution [ˌeksɪˈkjuːʃn] Context Stack)和 执行上下文 EC(Execution Context )
  • 全局对象 GO(Global Object)
  • 变量对象 VO(Variable Object)
  • 活动对象 AO(Activation Object)

这道图的流程图如下:

上图分析:
  1. 首先函数fn执行,就是把全局变量x的值作为实参传递给函数的形参。
  2. 函数执行的目的:想让之前存储在堆中的代码字符串执行 -> 代码执行就要有自己的执行环境;
  3. 函数执行的时候:
    1> 首先形成一个全新的私有的上下文,供代码执行
    2> 在执行代码之前先初始化作用域链(scopeChain):作用域链的组成规则是–<EC(FN),EC(G)>。 EC(FN):自己所在的上下文,EC(G):函数的作用域
  • 当前函数的作用域指什么:作用域是在函数创建的时候声明的,在哪个环境(上下文中)创建的,函数的作用域就是谁–比如此题中的函数fn在EC(G)全局执行上下文中创建的,那么函数fn的作用域就是全局执行上下文;

初始化作用域链规则:当前所在上下文是谁链的开头第一条就是谁,链接向谁就看当前作用域是谁,因为此函数在全局上下文中创建的,所以它的作用域就是全局执行上下文,所以这个函数的作用域链开头是自己的上下文,结尾是函数的上下文。

作用域链查找机制:如果函数fn在代码执行过程中遇到一个变量a,先看这个a是不是自己的私有变量,如果是私有的那接下来进行的所有操作都是自己的,跟别人没关系。如果a在私有变量中没有那就不是私有的,那就找到链的另外一头即全局上下文中,全局上下文中有a就是全局变量,这就是所谓的作用域链查找机制。

初始化作用域链目的:初始化作用域链就是为了构建出我们用变量时,这个变量是自己的还是它的上级上下文中的,作用域链就是给我们的查找提供了一个方向/路径,这是作用域链的目的。
作用域链机制(初始化作用域链的目的):日后在私有上下文执行代码的时候,遇到一个变量,我们首先看是否为自己的私有变量(在自己的变量对象中有是私有的,没有则不是);不是私有的按照作用域链找上级上下文中的……一直找到全局上下文为止。

  • 函数在执行之前: (初始化顺序就按照以下的顺序记忆即可)
    No.1 初始化作用域链 <EC(FN),EC(G)>
    No.2 初始化this执行:window
    No.3 初始化实参集合:arguments
    No.4 形参赋值:y=AAAFFF000(形参赋值的时候也会放在函数的变量对象,
    形参变量也是存放到自己上下文中的私有变量对象中 => 它是私有变量 AO(FN))
    No.5 变量提升:
    No.6 代码执行: y都是自己私有的:AAAFFF000
    y[0] = 100;//y虽然是自己私有的,但是y跟全局中的x指向同一个堆AAAFFF000,所以X也改变了
    y = [100];//私有的y重新指向了[100]这个对象,不再跟全局变量x共用一个堆
    y[1] = 200;//这一步操作就是私有变量y自己的跟全局的x不再有关系
    console.log(y);//[100,200]

  • 每次函数执行的时候都先形成一个全新的私有的上下文来供函数体中的代码执行的执行环境,但最终执行的时候都会放进栈中执行,所以私有上下文先进栈执行,如果此时栈中有一个没有被释放的上下文,比如这题中全局上下文还没被释放,就先把全局上下文压缩到底部,把自己的上下文放到栈的顶部。

  • 所有的形成的私有上下文在执行的时候都会放在栈的顶部,原来的会被压在栈的底部。

私有上下文EC(FN)在执行的时候,先进栈,把之前的全局上下文EC(G)压到栈的底部,自己在栈的顶端
新形成的上下文要进栈执行:

  1. 把全局上下文放到栈的底部(压缩栈)
  2. 新进来的上下文放到栈的顶部
  3. 上下文中的代码执行完,就会被出栈(释放);
  4. 此时栈中只剩下全局EC(G)了,fn出栈后,全局移动到上面开始执行

VO变量对象(Variable Object)
AO活动对象(Activation Object)
AO是VO的一个分支,都是变量对象;AO是活动对象,函数中的变量对象都称为AO
私有上下文EC(FN)中有活动对象AO(FN),AO(FN) 是私有变量对象,用来存储在代码执行的过程中创建的私有变量的。

此题整体思路总结:

  • 函数的执行目的是让代码执行,一定要形成一个自己的全新的私有环境,叫做私有执行上下文EC(FN),此处的这个FN是指目前例题中的函数,代码执行都要放进栈中执行,所以形成私有上下文之后的第一件事就是进栈。

  • 进栈之前发现栈ECStack中已经有一个全局上下文EC(G)了,就把这个全局上下文压缩到栈的底部,然后再把这个私有上下文EC(FN)放进栈的顶部来进行执行。

  • 每一个上下文里都有一个自己的私有变量对象,全局下叫VO(G),函数私有上下文中叫AO,AO是VO的一个分支也是代表私有变量对象。在私有上下文中创建的变量都会放进私有变量对象中进行存储。
    除了以上这些,函数代码在执行之前还要进行一些步骤:

  1. 初始化作用域链 <EC(FN),EC(G)> 起始端是自己的上下文,结尾端是函数作用域,即函数创建时所在的上下文。形成这个链条的作用是:遇到一个变量比如说y就看是否为自己私有的变量,如果是自己私有的就不再顺着链条向上查找了,如果不是自己私有的变量就沿着链条向上查找,假设如果此题中有一个变量a,在当前上下文中没找到,那就去链条的尾端即创建函数的上下文中找,一直找到全局变量为止。
  2. 初始化this执行:window
  3. 初始化实参集合:arguments
  4. 形参赋值:y=AAAFFF000(形参赋值的时候也会放在函数的变量对象,
    形参变量也是存放到自己上下文中的私有变量对象中 => 它是私有变量 AO(FN))
  5. 变量提升:
  6. 代码执行:

代码执行时的流程解析:

  1. 形参变量也是私有变量,要放进私有变量对象中存储。把X作为实参传递进来,因为X是引用数据类型,所以传递进来的实参实际是X关联的堆地址AAAFFF000,y会基于这个地址AAAFFF000找到跟X共用的这个堆,将索引为0的元素修改为100,那么全局X再访问这个堆也会受到影响。
  2. y = [100]相当于在私有上下文中创建了一个新的值,是一个数组地址为BBBFFF000,此时的y都是私有的。y[1] = 200就是给这个数组增加一个元素[100,200],length也变为2,然后打印出y这个私有变量就是[100,200]。
  3. 当前函数这个上下文一旦执行完,正常情况下这个函数没有东西被别的占用,我们就把它移出栈内存释放掉,这样可以保证栈内存中的上下文不会特别多,栈内存的空间也不会特别大,提高运行速度,性能优化。
  4. 把函数的私有上下文释放掉后,把全局的上下文移动到栈的顶部,继续执行剩下的代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: