美文网首页
详细解读函数的底层处理机制

详细解读函数的底层处理机制

作者: 小生菜呢 | 来源:发表于2021-07-28 21:11 被阅读0次

    JS中的堆(Heap)栈(Stack)内存

    都是在计算机内存中开辟的空间
    • 栈内存 Stack:ECStack(Execution [ˌeksɪˈkjuːʃn] Context Stack)
      1.存储原始值类型
      2.代码执行的环境
    • 堆内存 Heap:
      1.存储对象值类型
    EC(Execution [ˌeksɪˈkjuːʃn] Context )执行上下文:区分代码执行的环境
    • 常见上下文分类:
    1. 全局上下文EC(G)
      2.函数私有上下文EC(?)
      3.块级私有上下文EC(Bolck)
    • 产生私有上下文->进栈执行->出栈释放(可能释放)
    • 变量对象:当前上下文中,用来存储声明的变量的地方
      1.VO(Varibale Object):VO(G) 或者 VO(BLOCK)
      2.AO(Active Object):AO(?)

    GO(Global Object)全局对象

    • window指向GO对象
    • 全局上下文中,基于var/function声明的变量是直接存储到GO
      对象上的,而基于let/const生命的变量才是存放在VO(G)中的

    let 变量 = 值 的操作步骤

    • 第一步:创建值
      1.原始值类型:直接存储在栈内存中,按值操作
      number
      string
      boolean
      null
      undefined
      Symbol
      bigInt
    1. 对象类型值:按照堆内存地址来操作
      @1、对象:开辟一个堆内存空间(16进制地址)、一次存储对此昂的键值对、把空间地址赋值给变量
      @2、函数:内存空间存储三部分信息
      作用域[[scope]]:当前所处上下文
      函数体中的代码字符串
      当作普通对象存储的静态属性和方法[name&length]
    • 第二步:声明变量 declare
    • 第三步:变量和值关联在一起(赋值)defined

    以代码为例来详细解读函数的底层处理机制

    1 、

    var x = 12
    let y = 12
    z = 14
    console.log(x)
    console.log(window.x)
    console.log(y)
    console.log(window.y)
    console.log(z)
    console.log(window.z)
    

    全局代码执行,形成EC(G)全局执行上下文

    • EC(G)全局执行上下文
      VO(G):全局变量对象(基于let/const声明的全局变量存储在这里)
      y:13
      window -- > GO全局对象(基于var/function声明的全局变量存在这里)
      x:12
      z:14 // window.z = 14 直接设置在GO中,相当于省略了‘.window’
    • 代码执行:
      @1、console.log(x) :结果是 12 ;
      首先看VO(G)中是否存在,如果不存在 再去GO中看看是否存在,如果都不存在则报错:x is not defined
      @2、console.log(window.x):结果是12;
      直接到GO中找这个属性,如果不存在,则是undefined(因为是访问当前对象的某个成员,成员不存在的话结果是undefined,所以不是报错)
      @3、console.log(y):结果是13;
      基于let声明的变量存在VO(G)中
      @4、console.log(window.y):结果是undefined;

    @5、console.log(z):结果是14;直接去GO中找
    @6、console.log(window.z) :结果是14;直接去GO中找
    2、

    let x = [12,23]
    function fn(y) {
      y[0] = 100
      y = [100]
      y[1] = 200
      console.log(y)
    }
    fn(x)
    console.log(x)
    
    image.png
    文字版:

    再重复一边

    • 计算机会开辟两两个空间:栈内存、堆内存
      @1、栈内存:存储原始值(值类型)、提供代码执行的环境
      @2、堆内存:存储对象值类型

    • 上边已经提到:凡是变量=xxx 都是:
      @1、先创建值
      @2、定义变量
      @3、赋值

    代码执行步骤:
    1、浏览器开辟一块空间-->栈内存-->ECStack代码执行环境栈
    2、浏览器开辟一块空间-->堆内存-->-GO全局对象:16进制地址定为 0x000
    @1、存放内置属性:
    setTimeout,
    setInterval,
    requestAnimationFrame
    .......
    3、最开始肯定是全局代码执行,形成EC(G):全局执行上下文-->进栈执行
    @1、全局执行上下文中,基于let/const声明的变量要存放到全部变量对象VO(G)中
    4、代码自上而下执行
    5、[12,23]是个数组,所以开辟一个堆内存来存储,16进制地址暂定为:0x001
    @1、存储的内容有:
    0:12,
    1: 23,
    length:2
    .....
    6、定义变量x
    基于let声明,存储到VO(G)中
    7、x和0x001关脸 x --> 0x001
    8.VO(G)中存储的变量:
    @1、x -------> 0x001
    9、function xxx 也是定义一个变量,基于function/let声明的而变量存在GO中
    @1、函数也是一个对象,所以开一个堆内存,16进制地址为:0x002
    @2、存储的内容:

    • 作用域[[scope]]:函数在哪个上下文中创建,那么它的作用域就是哪个上下文(为作用域链做铺垫)
    • 函数代码字符串
    • 当作普通对象来存放键值对值(静态私有属性方法)
      (a)、name:fn
      (b)、length:1(形参个数)
      所以函数如果不执行,一点意义都没有
      [[重复:函数的作用域是在它创建的时候声明的]]
      @3、把10进制地址0x002赋值给fn

    10、此时GO中存储的属性:
    setTimeout,
    setInterval,
    requestAnimationFrame,
    fn:0x002
    ..........
    11、函数执行并传值:fn(x) ----> fn(0x001)
    @1、产生全新的私有执行上下文EC(FUN),然后进栈执行[私有上下文中有个私有变量对象AO(FUN),用来存储私有上下文中声明的变量]
    @2、代码正式执行前:

    • 初始化作用域链<EC(FUN)当前自己的私有上下文,EC(G)函数的作用域(上级执行上下文)>,明确变量的归属
    • 初始化THIS指向:window
    • 初始化aguments:{0:’0x001‘}
      (aguments是类数组集合,存的是实参,不管有没有形参,只要传了实参,arguments中都会有值)
    • 形参赋值:y = 0x001
    • 变量提升:无
    • 形参和上下文中声明的变量都是私有的
    • 此时AO(G)中存的值是:
      y ---> 0x001

    @3、代码执行

     y[0] = 100  
     y = [100]
     y[1] = 200
     console.log(y)
    
    • 第一行:是自己的私有变量,地址指向0x001,所以把堆内存0x001中索引为0的值改成100
      此时0x001中存储的内容变成:
      0:100,
      1:23,
      length:2,
      ......
      y虽然是私有变量,但是和全局变量x指向的同一个地址,全局x的值也受影响,此时x的值为:[100,23]

    • 第二行 y = [100] :[100]是个数组,所以再重新开一个堆内存0x003,用来存储这个数组
      0:100,
      length:1
      .....
      此时jy指向新的堆内存地址0x003

    • 第三行:根据y执行的新地址,在0x003这个堆内存中增加了一个索引为1的值,此时0x003的存储内容是:
      0:100,
      1:200,
      length:2
      .....

    • 第四行:输出y,y是私有的,地址指向0x003
      所以输出值为:*[100,200]

    @4、fn执行完后,出栈释放

    12、函数执行完,执行全局代码:console.log(x)
    刚才已经提到 x指向0x001
    0x001中的值在fn执行的时候已给修改成:[100,23]

    全局代码在浏览器关掉的时候才会释放

    以上就是整个代码的运行机制

    总结:

    1、创建函数的过程:

    @1、开辟堆内存[16进制地址]
    @2、存储的内容

    • 作用域[[scope]]:在哪个上下文中创建的,那么它的作用于就是哪个上下文(为作用域链做铺垫)
    • 函数代码字符串
    • 作为普通对象有的属性:
      name:函数名
      length:形参的个数

    @3、把16进制空间地址赋值给变量(函数名)即可

    2、普通函数执行要做的事情:

    @1、产生全新的私有执行上下文EC(?),然后进栈执行
    私有上下文中有私有变量对象AO(G),用来存放私有变量
    @2、代码执行前还要做的事情:

    • 初始化作用域链:<EC(?)自己私有的执行上下文,EC(G)函数的作用域(上级执行上下文)>
    • 初始化this
    • 初始化arguments:类数组集合,存储实参
    • 形参赋值
    • 变量提升
      [[形参和在私有上下文中声明的变量都是私有变量,存在AO中]]
      @3、代码正式执行
      @4.执行完出栈(如果被外部占用,不出栈,类如闭包)
    3、作用域链

    <自己私有上下文,作用域(上级上下文)>
    @1、在私有上下文中遇到个变量,首先看是否为私有变量(看AO中是否存在),如果是私有变量,则接下来操作的都是私有变量(和外界都没有关系)
    @2、如果不是自己私有的,则去上级上下文中查找,如果是上级的,则操作的都是上级的
    @3、如果也不是上级的,则继续找上级的上级的上下文....直到找到EC(G)为止
    @4 如果EC(G)中也没有,则:

    • 如果是获取变量值,则报错
    • 如果是设置变量值,则相当于给window设置对应的属性
    function fn() {
    /* fn执行,形成私有上下文EC(FN)
    *      AO(FN):
    *      作用域链:<EC(FN),EC(G)>
    *      形参赋值:--
    *      变量提升:--
    *      代码执行
    */
      console.log(n)  //  既不是形参,也没有在这个上下文中声明过,所以AO(FN)中没有,沿着作用域链往上找,全局GO也没有,所以会报错:n is not defined
    }
    fn()
    

    如果是:

    function fn() {
      n = 100   // 相当于给GO中设置一个属性n,值是100 --> window.n = 100
    }
    fn()
    console.log(n, window.n) // 100  100
    

    相关文章

      网友评论

          本文标题:详细解读函数的底层处理机制

          本文链接:https://www.haomeiwen.com/subject/gusomltx.html