Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

利用断点调试进行代码分析(以闭包为例) #2

Open
XiangLuoyang opened this issue Jun 29, 2017 · 0 comments
Open

利用断点调试进行代码分析(以闭包为例) #2

XiangLuoyang opened this issue Jun 29, 2017 · 0 comments
Assignees

Comments

@XiangLuoyang
Copy link
Owner

XiangLuoyang commented Jun 29, 2017

利用断点调试进行代码分析(以闭包为例)

参考资料

(本次主要是参考这个地址的资料,本文仅做引用分享,更加详细的信息和用例可以点进链接之后详细参考)

基础概念回顾

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

认识断点调试工具

断点调试工具的打开方式和常用功能见上一篇文档《浏览器开发者工具使用(以Chrome为例)》相关章节,这里不再进行补充。

断点设置

在显示代码行数的地方点击,即可设置一个断点。断点设置有以下几个特点:

  • 在单独的变量声明(如果没有赋值),函数声明的那一行,无法设置断点。
  • 设置断点后刷新页面,JavaScript代码会执行到断点位置处暂停执行,然后我们就可以开始调试了。
  • 当设置多个断电时,Chrome工具会自动判断从最早执行的那个断点开始执行,因此大多数情况只要设置一个断点就行了。

实例

//demo

var fn;
var a=2;
function f1(){
    var a=4;
    function f2(){
        console.log(a);
    }
    fn=f2;
}
f1();
fn();//4

这是一个很典型的闭包例子,在实际开发中,闭包不可谓不常见。但对于闭包的定义是什么,却不是一个很好回答的问题。在这里,通过浏览器的调试工具,我们至少可以知道Chrome浏览器是怎么解释闭包这一回事的。

  • 第一步:设置断点,然后刷新页面。

image

  • 第二步:点击上图红色箭头指向的按钮(step into),该按钮的作用会根据代码执行顺序,一步一步向下执行。在点击的过程中,我们要注意观察下方call stack与scope的变化,以及函数执行位置的变化。

一步一步执行,当执行到上面的例子中:

image

我们可以看到,在Chrome工具的理解中,由于在f1内部声明的f2函数在调用时访问了它的变量a,因此f1成为了闭包。

我们修改一下demo中的例子,来看看一个非常有意思的变化。

//demo02
var fn;
var m = 20;

function f1() {
    var a = 2;

    function f2(a) {
        console.log(a);
    }
    fn = f2;
}

function f3() {
    fn(m);
}

f1();
f3();

这个例子在demo01的基础上,在f2函数中传入一个参数,并打印出来。在调用时,将全局变量m传入。输出结果变为20。再使用断点调试看看作用域链。

image

这时我们可以发现,闭包没有了,作用域链中没有包含f1了。通过这个对比,我们可以确定闭包的形成需要两个条件。

  • 在函数内部创建新的函数;
  • 新的函数在执行时,访问了函数的变量对象;

我们继续来看看一个例子。

//demo03
function f1() {
    var a = 2;

    function f2(a) {
        console.log(a);
    }
    fn = f2;
}

function f3() {
    fn(m);
}

f1();
f3();

在这个例子中,fn只访问了f1中的a变量,因此它的闭包只有f1。

image

不出所料,当fn添加访问f2中的b变量的时候,它的闭包也变为了2个。

image

我们知道,闭包在模块中的应用也非常重要。因此,我们来一个模块的例子,也用断点工具来观察一下。

(function(){
    var a=10;
    var b=20;
    var test={
        m:20,
        add:function(x){
            return a+x;
        },
        sum:function(){
            return a+b+this.m;
        },
        mark:function(k,j){
            return k+j;
        }
    }
    window.test=test;
})();

test.add(100);
test.sum();
test.mark();

var _mark=test.mark;
_mark();

image

当add执行时,闭包为外层的自执行函数,this指向test

image

sum执行时,同上

image

mark执行时,闭包为外层的自执行函数,this指向test

image

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

更多的例子,大家可以自行尝试,总之,学会了使用断点调试之后,我们就能够很轻松地了解一段代码的执行过程了。这对快速定位错误,快速了解他人的代码都有非常巨大的帮助。大家一定要动手实践,把它给学会。

最后,根据以上的摸索情况,再次总结一下闭包:

  • 闭包是在函数被调用执行的时候才被确认创建的。
  • 闭包的形成,与作用域链的访问顺序有直接关系。
  • 只有内部函数访问了上层作用域链中的变量对象时,才会形成闭包,因此,我们可以利用必报来访问函数内部的变量。
  • Chrome中理解的闭包,与《你不知道的js》和《JavaScript高级编程》中的闭包理解有很大不同,建议多做比较再下结论。

后记

断点调试是个非常有趣又实用的功能,经常使用可以提高熟练度和理解一些原理上的难题(如闭包和this指向的问题)。新手在接手新的任务的时候,可以对原有的代码进行断点调试和代码分析,可以对任务快速熟练上手。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant