【前端知识梳理-JS】深入js执行机制

1. 代码执行顺序

先来看下如下代码的执行结果,看是否符合你的预期:

showName()
console.log(myname)
console.log(myage)
var myname = '哈哈'
function showName() {
    console.log('showName被执行');
}


结论

  • 未声明的变量报错,js执行报错
  • 定义前使用不报错,值为undefined, 而非定义的值
  • 函数定义前使用,不报错且正常执行

变量提升

声明与赋值

var test = function(){
  console.log('test1')
}

function test(){
  console.log('test')
}

test()

JS代码执行流程

image.png

总结

  • JavaScript 代码执行过程中,会发生变量提升,因为 JavaScript 代码在执行之前需要先编译。
  • 在编译阶段,变量和函数会被存放到变量环境中,变量的默认值为 undefined;
  • 在代码执行阶段,JavaScript 引擎会从变量环境中去查找自定义的变量和函数。
  • 如果在编译阶段,存在两个相同的函数,前面的会被覆盖。

2. 块级作用域

作用域

指变量与函数的可访问范围,控制着其可见性及生命周期

image.png

变量提升带来的问题

1. 变量被覆盖

var myname = "哈哈"
function showName(){
  console.log(myname);
  if(0){
   var myname = "呵呵"
  }
  console.log(myname);
}

showName()

2. 变量不及时销毁


function test(){
  for (var i = 0; i < 7; i++) {


  }
  console.log(i); 
}

test()

解决方案

使变量仅在代码块内生效(即块级作用域

使用letconst关键字时浏览器的处理

function test(){
    var a = 1
    let b = 2
    {
      let b = 3
      var c = 4
      let d = 5
      console.log(a)
      console.log(b)
    }
    console.log(b) 
    console.log(c)
    console.log(d)
}   
test()

执行步骤分析:

  1. 编译并执行上下文

暂时无法在文档外展示此内容

image.png
2. 执行代码

image.png
let和const是否存在变量提升?

不存在

总结

  • Javascript引擎通过区分变量环境和词法环境,同时支持变量提升和块级作用域
  • 变量访问顺序 词法环境栈顶 -> 词法环境栈底 -> 变量环境

3. 作用域链与闭包

function say() {
    console.log(myName)
}



function test() {
    var c1 = say
    var myName = "呵呵"
    c1()
}
var myName = "哈哈"
var obj1 = {
    a1: 1,
    fn1: function () {
        console.log(this.a1)
    }
}
var obj2 = {
    a1: 2,
    fn2: obj1.fn1
}
obj2.fn2()
test()

4. 相关概念

调用栈、执行上下文、变量环境、词法环境

image.png
每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为 outer。

函数执行上下文使用外部引用的变量,会通过outer向外查找,这个查找链条就是作用域链

5. js执行过程中的作用域链是如何决定的

由代码中函数声明的位置(词法作用域)决定,词法作用域是代码编译阶段就决定好的,和函数是怎么调用的没有关系。

上面代码中,虽然test 函数调用了 say 函数,但是 say 函数的外部引用并不是 test 函数的执行上下文,而是全局执行执行上下文。

function smile() {
    var myName = "name1"
    let test1 = 100
    if (1) {
        let myName = "name2"
        console.log(test)
    }
}


function say() {
    var myName = "name3"
    let test = 2
    {
        let test = 3
        smile()
    }
}
var myName = "name4"
let myAge = 10
let test = 1
say()

变量查找链:当前执行上下文词法环境 -> 当前执行上下文变量环境 -> 外部引用outer词法环境 -> 外部引用outer变量环境

6. 闭包

1. 浏览器如何处理闭包

function test() {
    var myName = "name"
    let test1 = 1
    const test2 = 2
    var innerTest = {
        getName:function(){
            console.log(test1)
            return myName
        },
        setName:function(newName){
            myName = newName
        }
    }
    return innerTest
}
var fn = test()
fn.setName("name1")
fn.getName()
console.log(fn.getName())

当执行到 test 函数内部的return innerTest这行代码时调用栈的情况

image.png

根据词法作用域的规则,内部函数 getName 和 setName 总是可以访问它们的外部函数 test 中的变量

所以当 innerTest 对象返回给全局变量 fn 时,虽然 test 函数已经执行结束,但是 getName 和 setName 函数依然可以使用 test 函数中的变量 myName 和 test1。所以当 test 函数执行完成之后,执行栈状态如下:

image.png

虽然test函数从栈顶弹出,但是会在内存中留下一个包,这个包是test函数的专属包,且只能通过getName 和 setName方法访问,这个包也就是test函数的闭包

在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 test,那么这些变量的集合就称为test 函数的闭包。

2. 闭包的内存模型分析

js如何存储数据

image.png

执行流程再分析

  1. 执行 test 函数,先编译并创建执行上下文。
  2. 编译过程遇到内部函数setName,JavaScript 引擎还要对内部函数做一次快速的词法扫描,发现该内部函数引用了 test 函数中的 myName 变量,由于是内部函数引用了外部函数的变量,所以 JavaScript 引擎判断这是一个闭包,于是在堆空间创建换一个“closure(test)”的对象(这是一个内部对象,JavaScript 是无法访问的),用来保存 myName 变量。
  3. 接着继续扫描到 getName 方法时,发现该函数内部还引用变量 test1,于是 JavaScript 引擎又将 test1 添加到“closure(test)”对象中。这时候堆中的“closure(test)”对象中就包含了 myName 和 test1 两个变量了。
  4. 由于 test2 并没有被内部函数引用,所以 test2 依然保存在调用栈中。

image.png
当执行到 test 函数时,闭包就产生了

当 test 函数执行结束之后,返回的 getName 和 setName 方法都引用“clourse(test)”对象,所以即使 test 函数退出了,“clourse(test)”依然被其内部的 getName 和 setName 方法引用。所以在下次调用fn.setName或者fn.getName时,创建的执行上下文中就包含了“clourse(test)”。

产生闭包的核心两步:

第一步是需要预扫描内部函数;第二步是把内部函数引用的外部变量保存到堆中。

闭包是怎么回收的

如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。

如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存。

注意:如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY96OU64' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片