在chrome开发者工具中观察函数调用栈、作用域链、闭包

时间:2022-03-04 13:15:37

在chrome的开发者工具中,通过断点调试,我们能够非常方便的一步一步的观察JavaScript的执行过程,直观感知函数调用栈,作用域链,变量对象,闭包,this等关键信息的变化。因此,断点调试对于快速定位代码错误,快速了解代码的执行过程有着非常重要的作用,这也是我们前端开发者必不可少的一个高级技能。

函数在被调用执行时,会创建一个当前函数的执行上下文。在该执行上下文的创建阶段,变量对象、作用域链、闭包、this指向会分别被确定。JavaScript程序中一般有多个函数,JavaScript引擎使用函数调用栈来管理这些函数的调用顺序,函数调用栈的调用顺序与栈数据结构一致。

代码段1

var fn;
function funcOut(){
    var a = 2;
    function funcIn(){
        console.log(a);
    }
    fn = funcIn;
}
function funcTest(){
    fn();
}
funcOut();
funcTest();

在funcOut()处设置断点,刷新页面;

点击step into,注意观察下方call stack与scope的变化,以及函数执行位置的变化。

在chrome开发者工具中观察函数调用栈、作用域链、闭包

我们可以看到,在funcOut内部声明的funcIn函数在调用时访问了它的变量a,因此funcOut成为了闭包。

代码段2

var fn;
var n = 20;
function funcOut(){
    var a = 2;
    function funcIn(a){
        console.log(a);
    }
    fn = funcIn;
}
function funcTest(){
    fn(n);
}
funcOut();
funcTest();

闭包没了,作用域链中没有包含funcOut了。

在chrome开发者工具中观察函数调用栈、作用域链、闭包

代码段3

function funcOut() {
    var a = 2;
    return function funcIn() {
        var b = 20;

        return function fn() {
            console.log(a);
        }
    }
}
var funcIn = funcOut();
var fn = funcIn();
fn();

fn只访问了funcOut中的a变量,因此它的闭包只有funcOut。

在chrome开发者工具中观察函数调用栈、作用域链、闭包

代码段4

function funcOut() {
    var a = 2;
    return function funcIn() {
        var b = 20;

        return function fn() {
            console.log(a,b);
        }
    }
}
var funcIn = funcOut();
var fn = funcIn();
fn();//2 20

这个时候,闭包变成了两个。分别是funcIn,funcOut。

在chrome开发者工具中观察函数调用栈、作用域链、闭包

代码段5

//闭包在模块中的应用
(function() {
    var a = 10;
    var b = 20;
    var myObj = {
        c: 20,
        sum1: function(x) {
            return a + x;
        },
        sum2: function() {
            return a + b + this.c;
        },
        sum3: function(k, j) {
            return k + j;
        }
    }
    window.myObj = myObj;
})();

myObj.sum1(100);
myObj.sum2();
myObj.sum3();

var funcTest = myObj.sum3;
funcTest();

sum1执行时,闭包为外层的自执行函数,this指向myObj。

在chrome开发者工具中观察函数调用栈、作用域链、闭包

sum2执行时,闭包为外层的自执行函数,this指向myObj。

在chrome开发者工具中观察函数调用栈、作用域链、闭包

sum3执行时,闭包为外层的自执行函数,this指向myObj。

在chrome开发者工具中观察函数调用栈、作用域链、闭包

funcTest执行时,闭包为外层的自执行函数,this指向Window。

在chrome开发者工具中观察函数调用栈、作用域链、闭包

这里的this指向显示为Object或者Window,大写开头,他们表示的是实例的构造函数,实际上this是指向的具体实例。

代码段6

var a = 10;
var obj = {
    a: 20
}
function fn() {
    console.log(this.a);
}
fn.call(obj); 

在chrome开发者工具中观察函数调用栈、作用域链、闭包

代码段7

function funcOut() {
  var a = 10;
  function funcIn1() {
      return a;
  }
  function funcIn2() {
      return 10;
  }
  funcIn2();
}

funcOut();

在chrome开发者工具中观察函数调用栈、作用域链、闭包

这个例子,和其他例子不太一样。虽然funcIn2并没有访问到funcOut的变量,但是funcOut执行时仍然变成了闭包。而当我将funcIn1的声明去掉时,闭包便不会出现了。我暂时也不知道应该如何解释这种情况。只能大概知道与funcIn1有关,可能浏览器在实现时就认为只要存在访问上层作用域的可能性,就会被当成一个闭包吧。

在chrome开发者工具中观察函数调用栈、作用域链、闭包