执行上下文、变量提升、作用域链和闭包

数据的存储

要理解执行上下文、变量提升等,首先回顾一下js的基本数据类型

最新的 ECMAScript 标准定义了 9种数据类型:
Data and Structure types
而在中文文档里,8种数据类型
7 种原始类型:

  • Boolean
  • Null
  • Undefined
  • Number
  • BigInt
  • String
  • Symbol

Object

JS在执行上下文的时候,会创建一个特殊的对象——变量对象。7种基本原始数据类型保存在变量对象中按值访问;而引用数据类型(如对象、数组、函数等)的值保存在堆内存中,并且JS不允许直接访问堆内存,只能通过保存在变量对象中的地址指针指向堆内存中的实际值。

var a = 0         // 变量a和值0都存于变量中对象 
var b = 'string'  // 变量b和值0都存于变量中对象 
var c = null      // 变量c和值0都存于变量中对象 
var d = { m: 1 }  // 变量d存于变量对象中,{m: 1} 作为对象存于堆内存中 
var e = [1, 2, 3] // 变量e存于变量对象中,[1, 2, 3] 作为对象存于堆内存中
var f = function () {...}   // 变量f存于变量对象中,function作为对象存于堆内存中

堆内存


执行上下文

顾名思义就是执行上下代码块,这里面的难点在于——“域”。


JS主要有两种执行上下文方式(会生成相应的作用域):
全局执行上下文:就是全局从上到下一步步执行语句(生成全局作用域)
函数执行上下文:某函数内部从上到下执行代码块(生成局部作用域)
规则就是从上到下,从里到外

当进入某一函数中时,必须先执行完该函数内的代码块,才会跳出当前作用域去执行父级代码块,并逐级从里到外直至全局作用域。一旦函数执行完后,就会在内存中销毁。

function fn1() {
	fn2()
}
function fn2() {
	console.log(1)
}
fn1()

变量提升

https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
变量提升(Hoisting)被认为是, Javascript中执行上下文 (特别是创建和执行阶段)工作方式的一种认识。…例如,从概念的字面意义上说,“变量提升”意味着变量和函数的声明会在物理层面移动到代码的最前面,但这么说并不准确。实际上变量和函数声明在代码里的位置是不会动的,而是在编译阶段被放入内存中。

通俗点来说,就是JS在执行上下文之前会进行预解析,将变量与函数提前申明(函数优先级高于变量)。
不同的是,变量只申明,但值为undefined,如果在编写代码时提前使用变量,得到的将会是undefined。如果在申明并赋值一个变量后,再次重复申明这个变量,那么后续赋值也不会成功。而function既申明又赋值,这也是为什么即使function写在代码块最下面依然能使用function。

作用域链

JS预解析时创建变量对象,随后就会建立作用域链,这意味着子级作用域在优先使用本作用域内的变量对象时,还可以使用父级作用域内的所有变量对象,并向上逐级寻找在当前作用域没有的变量对象,直至全局作用域,当全局作用域都没有这个变量时,就会报错xxx is not defined。

var a = 1    // 全局变量

function fn1 () {
  var b = a + 1		// 通过作用域链,可以使用全局变量
  var c = 3
  function fn2 () {
    var c = 4  // 当前作用域内的变量,不影响上级作用域
    return b + c   // 6  因为当前作用域已经申明变量c,优先使用当前作用域内的变量c
  }
  fn2()
}
fn1() // return结果为6

但是如果要用到某函数内部的变量时,就涉及到闭包问题了。

闭包

函数外部读取到函数内的局部变量的函数。

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除…原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous
function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。


内容参考与引用(侵删):
深入理解执行上下文、作用域链和闭包
学习javascript闭包

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值