数据的存储
要理解执行上下文、变量提升等,首先回顾一下js的基本数据类型
最新的 ECMAScript 标准定义了 9种数据类型:
而在中文文档里,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闭包