javascript变量对象 函数调用栈 作用域 闭包等细解!

简介:

说明

下面代码演示基于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来说就是一个闭包(你可以理解是一个环境概念,该环境维护了一些内部返回的匿名函数用到的一些外部变量)!

梳理一下,闭包指得是某个函数调用之后,自身执行环境已不复存在,但返回了一个函数,由于该返回函数内部用到了外部函数里面的一些变量,并又该内部函数又赋值给了其它变量,导致虽然外部函数不存在了,但是它引用的那些外部变量却不能回收的一个环境(闭包)。

闭包用好了,可以保证好多变更的作用域周期得以提升,减少变量命名冲突,但是过多的使用闭包,也会存在内存泄漏的问题,因为你的很多变量都没有被垃圾回收器回收。

进入正题,变量对象

为了让你对变量对象整体有个最初的概念。在详细介绍之前,我对变量对象概念作如此表述:变量对象指函数执行过程中,函数自身用到数据从哪里来的,函数怎么管理这些数据的等等,其实变量对象里面保存了函数在执行过程中所有用到的数据以及某个时刻值等!

时间仓促,后续待更新……


原文发布时间为:2018年06月21日
原文作者: 掘金

本文来源: 掘金 如需转载请联系原作者

相关文章
|
3月前
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
3月前
|
JavaScript 前端开发
js的作用域作用域链
【10月更文挑战第29天】理解JavaScript的作用域和作用域链对于正确理解变量的访问和生命周期、避免变量命名冲突以及编写高质量的JavaScript代码都具有重要意义。在实际开发中,需要合理地利用作用域和作用域链来组织代码结构,提高代码的可读性和可维护性。
|
3月前
|
Web App开发 JavaScript 前端开发
如何确保 Math 对象的方法在不同的 JavaScript 环境中具有一致的精度?
【10月更文挑战第29天】通过遵循标准和最佳实践、采用固定精度计算、进行全面的测试与验证、避免隐式类型转换以及持续关注和更新等方法,可以在很大程度上确保Math对象的方法在不同的JavaScript环境中具有一致的精度,从而提高代码的可靠性和可移植性。
|
7天前
|
JavaScript 前端开发 容器
盘点JavaScript中所有声明变量的方式及特性
本文详细介绍了JavaScript中变量定义的多种方式,包括传统的`var`、`let`和`const`,以及通过`this`、`window`、`top`等对象定义变量的方法。每种方式都有其独特的语法和特性,并附有代码示例说明。推荐使用`let`和`const`以避免作用域和提升问题,谨慎使用`window`和`top`定义全局变量,不建议使用隐式全局变量。掌握这些定义方式有助于编写更健壮的JS代码。
31 11
|
3月前
|
JSON 前端开发 JavaScript
JavaScript中对象的数据拷贝
本文介绍了JavaScript中对象数据拷贝的问题及解决方案。作者首先解释了对象赋值时地址共享导致的值同步变化现象,随后提供了五种解决方法:手动复制、`Object.assign`、扩展运算符、`JSON.stringify`与`JSON.parse`组合以及自定义深拷贝函数。每种方法都有其适用场景和局限性,文章最后鼓励读者关注作者以获取更多前端知识分享。
37 1
JavaScript中对象的数据拷贝
|
3月前
|
自然语言处理 JavaScript 前端开发
[JS]作用域的“生产者”——词法作用域
本文介绍了JavaScript中的作用域模型与作用域,包括词法作用域和动态作用域的区别,以及全局作用域、函数作用域和块级作用域的特点。通过具体示例详细解析了变量提升、块级作用域中的暂时性死区等问题,并探讨了如何在循环中使用`var`和`let`的不同效果。最后,介绍了两种可以“欺骗”词法作用域的方法:`eval(str)`和`with(obj)`。文章结合了多位博主的总结,帮助读者更快速、便捷地掌握这些知识点。
38 2
[JS]作用域的“生产者”——词法作用域
|
3月前
|
JavaScript 前端开发
如何在 JavaScript 中实现块级作用域?
【10月更文挑战第29天】通过使用 `let`、`const` 关键字、立即执行函数表达式以及模块模式等方法,可以在JavaScript中有效地实现块级作用域,更好地控制变量的生命周期和访问权限,提高代码的可维护性和可读性。
|
3月前
|
JavaScript 前端开发 图形学
JavaScript 中 Math 对象常用方法
【10月更文挑战第29天】JavaScript中的Math对象提供了丰富多样的数学方法,涵盖了基本数学运算、幂运算、开方、随机数生成、极值获取以及三角函数等多个方面,为各种数学相关的计算和处理提供了强大的支持,是JavaScript编程中不可或缺的一部分。
|
3月前
|
存储 缓存 自然语言处理
掌握JavaScript闭包,提升代码质量与性能
掌握JavaScript闭包,提升代码质量与性能
|
3月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包(Closures)
深入理解JavaScript中的闭包(Closures)