js数据类型和闭包在堆、栈中存储图解(5)

63 阅读4分钟

一、js数据类型

1.1 静态语言和动态语言

C语言、java、typescript等语言在定义变量时需同时定义其数据类型,比如int a = 1。这种需要在使用前定义其数据类型的语言为静态语言,在代码执行前会进行类型检查。 相反,需要在程序运行过程中检查数据类型的语言称为动态语言

1.2 弱类型和强类型语言

在java中,我们不能把一个int类型的变量赋给String类型的变量,这会触发编译时报错。除非进行显式转换。这种必须指定类型,不支持隐式类型转换的语言为强类型语言。 相反,不需指定类型,支持隐式类型转换的语言为弱类型语言。在js中可以任意相互赋值不同类型的变量。变量的类型在运行时确定,或在需要时进行类型转换。

因此,javascript是弱类型的动态语言。无需声明变量类型且支持隐式类型转换。

let a = 1
a = 'cola'
a = null
a = { face: 'no' }

1.3 原始类型和引用类型

js中的数据类型共八种:null,undefined,number,bigint,string,boolean,symbol和object。

除了object为引用类型,其余其中都为原始类型。

我们可以通过typeof运算符查看数据的类型(除了null): 在这里插入图片描述

判断是否为null,只能使用 "===null"。详细内容可见这里

二、内存空间

在js执行过程中,主要有代码空间、栈空间和堆空间三种。

  • 代码空间主要存储可执行代码。
  • 栈空间为前几篇文章中提到的调用栈,用于存储执行上下文。栈空间是连续的。
  • 堆空间用于存储引用类型的值。堆存储没什么规律,只会用一块足够大的空间来存储变量。

上代码:

function testFunc() {
  let a = 'cola'
  let b = a
  let c = { face: 'no' }
  let d = c
}

testFunc()

(1)当a和b被赋值后,调用栈状态为: 在这里插入图片描述

在testFunc函数执行上下文中,a和b都被赋值为"cola",原始类型的数据被保存在执行上下文中,执行上下文被压入栈,可以认为原始类型数据保存在栈中。

(2) 当c被赋值时,由于js引擎判断右边的值为引用类型,此时js引擎会把该对象分配到堆空间中,执行上下文中存储的为该对象在堆中的地址。此时调用栈状态为: 在这里插入图片描述

从上图可以看到,引用类型的值存放在堆空间中,js访问数据时,通过引用对象的地址进行访问。

js引擎通过栈维护程序执行期间上下文的状态,通过在栈中存放原始数据类型数据,堆空间中存放引用类型的数据,维持栈空间不会太大。如果所有数据都存储在栈中,栈空间太大会影响上下文切换的效率,进而影响整个程序执行的效率。

(3) 当d被赋值时,由于js引擎判断右边的值为引用类型,也会为d分配该对象的引用地址。 在这里插入图片描述 所以d=c的操作,是把c的引用地址赋值给d,如果修改该对象,c和d都会受到影响。 因此,原始类型的赋值,是复制值;引用类型的赋值,是复制引用地址

(4) 当testFunc函数执行结束,js引擎离开当前的执行上下文,下移到上个执行上下文的地址(本程序中为全局执行上下文)。testFunc函数执行上下文空间会被全部回收。

三、闭包的存储

在谈闭包时,有这段代码:

function func() {
    let myName = "李三岁"
    let num1 = 1
    let num2 = 2
    let innerFunc = {
        getName:function(){
            return myName
        },
        setName:function(newName){
            console.log(num1)
            myName = newName
        }
    }
    return innerFunc
}
var tem = func()
tem.setName("李逍遥")
console.log(tem.getName())

在程序执行过程中,func函数执行上下文销毁时,变量myName和num1并没有被销毁,而是保存在内存中。在内存空间中,闭包的形成和存储为:

  1. js引擎执行func函数时,编译创建其空的函数执行上下文。
  2. 编译过程中,遇到getName内部函数,发现内部函数引用了外部func函数的myName变量。由于内部函数引用外部函数中的变量,js引擎判断这是一个闭包。于是在堆空间中创建一个"closure(func)"的对象,myName作为其属性存储。
  3. 接着遇到setName函数,发现该内部函数还引用外部函数中的num1,js引擎把num1也添加到"closure(func)"对象中。 当执行到return innerFunc时func的调用栈状态为: 在这里插入图片描述 执行到该函数时,闭包就已经产生了。虽然func函数执行结束,其执行上下文被销毁。但是返回的getName和setName方法都有"closure(func)"对象的引用,所以当调用tem.setName或getName方法时,创建的函数执行上下文中就包含"closure(func)"对象。

文章参考:time.geekbang.org/column/intr… developer.mozilla.org/zh-CN/docs/…