说明
下面代码演示基于window系统chrome浏览器环境,版本号为63.0.3239.132,32位!相关结果可能会有一点出入,请也实际为准!
相关代码调试的过程中查看结果的步骤:
-
打开浏览器控制台,切换到sources板块,并选择相应的源文件;
-
在对应的源文件代码左边的行号上打上断点;
-
然后刷新浏览器,浏览器会在对应打断点的代码出停止执行,此时我们根据需要按f11键一步一步的运行代码,并同时查看代码的调用栈,作用域等情况,主要查看source板块下最右边子板块的Call Stack和Scope项。
相关概念梳理
其实从我自身出发,我觉的如果需要更好地理解变量对象的话,那么需要对以下概念有一个比较基本的理解会更方便些!
- 函数调用栈
为了理解函数调用栈,我们先写一段代码:
function fn1(){
console.log('fn1');
fn2();
};
function fn2(){
console.log('fn2');
};
fn1();
我们在fn2函数调用的地方打上断点,然后刷新浏览器,查看Call Stack选项,会看到下面的结果:
fn1
(anonymous)
此时再按一次f11键,此时Call Stack显示结果如下:
fn2
fn1
(anonymous)
这里说明一下,anonymous指代全局匿名调用函数环境,而fn1和fn2分别指代fn1函数作用域和fn2作用域环境。
我们梳理一下浏览器的调用过程:1 js进入全局匿名函数环境,把这个所谓的匿名函数推入调用栈;2 发现此时又调用了fn1,于是把fn1函数推入调用栈,此时fn1在anonymous的上面; 3 紧接着,发现调用了fn2,于是把fn2推入调用栈,于是得到了上面的结果。
如果后续我们继续按f11键调试,会发现Call Stack会依次出现先面的结果:
fn2
fn1
(anonymous)
fn1
(anonymous)
(anonymous)
也就是说最后只剩下了全局匿名函数环境,这里强调一下,anonymous环境将会伴随着程序运行一直存在,除非你关闭了浏览器。
于是我们总结得到这样的结果:存在这样一个调用栈,默认推入一个全局匿名函数在栈底,当此时再调用其它全局函数的时候,会把该函数推入栈,并在anonymous上面,如果该函数内部继续调用了其它函数,那么同样道理,会把其它函数推入栈,放在该函数上面,最后当函数在调用完成的过程中,会依次退出该调用栈,退出的过程中会把权限交给上一层函数,最后又回归了只剩下全局匿名函数环境。于是我们说了这么多,其实这就是函数调用栈!
函数调用栈你可以理解为函数调用前后包含关系:先进后出,后进先出!它描述了代码执行的先后顺序以及当前代码执行控制权限的拥有者关系等。
- 函数作用域
我们都知道javascript是没有块级作用域的,只有函数作用域,怎么理解?我们看下面的代码:
代码1
for(var k = 0;k<10;k++){
//...
};
console.log(k);//输出10
我们发现for循环代码块执行完了之后,依然能得到k的值。再看下面的代码:
代码2
function fn3(){
var a = 'a';
};
fn3();
console.log(a);//报错 a is not defined
我们发现在函数里面定义的变量a,在函数外面是拿不到的,这就是函数作用域能做到的。
函数作用域能让我们定义一些函数内部使用的与外部环境同名的变量而不会跟外部环境冲突,我们用的较多的地方就是即时函数,如下:
var a = 'outer';
(function(){
var a = 'inner';
conosle.log(a);//输出inner
})();
console.log(a);//输出outer
当然了在es6及后续版本javascript中,我们可以使用let和const标志符来定义块级变量,这里不作讨论!
这里再做一下扩展,作用域中涉及到最多的一个概念就是作用域链,这个是什么意思呢!看下面的代码:
let a = 'a';
function fn4(){
let b = 'b';
return a+b;
};
let c = fn4();
console.log(c);//输出'ab'
作用域链描述的是一种函数在执行的过程中查找变量的方式,具体来说:函数执行,如果遇到某变量,会首先在自身作用域环境查找改变量,如果不存在,会向上一层作用域查找变量,也就是函数调用栈中当前执行函数的下一层函数环境查找变量,依次类推,直到到全局环境中查找,如果在全局中都没有找到变量的话,那么就会报错!
其实原型链也是一种描述实例属性查找的过程,跟作用域链类似!
- 闭包
恐怕接触过javascript的开发者,听到最多与其有关的概念就是闭包了。而且网上有很多文章和书籍都对闭包进行了说明,其实我觉的理解闭包并不难哈!我们来看段代码吧:
function fn5(){
let a = 0;
return function(){
a++;
console.log(a);
};
};
let fn = fn5();
fn();
我们在fn调用的地方打一个断点,随后按一次f11键进入fn执行环境,看下Scope项结果,会发现Scope下有一项子项:
Closure(fn5)
|_ a
我可以明确的告诉你,此时的fn5对于fn来说就是一个闭包(你可以理解是一个环境概念,该环境维护了一些内部返回的匿名函数用到的一些外部变量)!
梳理一下,闭包指得是某个函数调用之后,自身执行环境已不复存在,但返回了一个函数,由于该返回函数内部用到了外部函数里面的一些变量,并又该内部函数又赋值给了其它变量,导致虽然外部函数不存在了,但是它引用的那些外部变量却不能回收的一个环境(闭包)。
闭包用好了,可以保证好多变更的作用域周期得以提升,减少变量命名冲突,但是过多的使用闭包,也会存在内存泄漏的问题,因为你的很多变量都没有被垃圾回收器回收。
进入正题,变量对象
为了让你对变量对象整体有个最初的概念。在详细介绍之前,我对变量对象概念作如此表述:变量对象指函数执行过程中,函数自身用到数据从哪里来的,函数怎么管理这些数据的等等,其实变量对象里面保存了函数在执行过程中所有用到的数据以及某个时刻值等!
时间仓促,后续待更新……
原文作者:掘金
本文来源: 掘金 如需转载请联系原作者