1.语言类型
静态语言 VS 动态语言
静态语言,在使用变量之前,需要确认变量的数据类型;
相反,动态语言在运行过程中需要检查数据类型
强类型语言 VS弱类型语言
支持隐式类型转换的语言称为弱类型语言;
相反,不支持的称为强类型语言
JavaScript语言
(1)JavaScript 是一种弱类型的、动态的语言
弱类型,意味着你不需要告诉 JavaScript 引擎变量是什么数据类型,JavaScript 引擎在运行时会计算出来
动态,意味着你可以使用同一个变量保存不同类型的数据
(2)JavaScript有8 种数据类型,分为原始类型和引用类型
如果想要查看变量的类型,可以使用“typeof”运算。但是,typeof 检测 Null 时,返回的是 Object。这是当初 JavaScript的Bug
2.内存空间
1. 内存空间类型
在 JavaScript 的执行过程中, 主要有代码空间、栈空间和堆空间三种类型内存空间
2. 存储的数据类型
原始类型的数据值保存在**“栈”中的,引用类型的值是存放在“堆”**中
(1)因为 JavaScript 引擎需要用栈来管理执行上下文的状态, 如果栈空间大了,会影响到上下文切换的效率,进而又影响到整个程序的执行效率,所以栈主要用来存放一些原始类型的小数据
(2)而堆空间很大,引用类型的数据占用的空间都比较大,会被存放到堆中,不过缺点是分配和回收内存都会占用一定的时间
3. 变量赋值
原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址,堆中的数据是通过引用和变量关联起来的。也就是说,JavaScript 的变量是没有数据类型的,值才有数据类型,变量可以随时持有任何类型的数据
3. 闭包的内存分配与回收
function foo() {
var myName = " 你的时间 "
let test1 = 1
const test2 = 2
var innerBar = {
setName:function(newName){
myName = newName
},
getName:function(){
console.log(test1)
return myName
}
}
return innerBar
}
var bar = foo()
bar.setName(" 你的名字 ")
bar.getName()
console.log(bar.getName())
//1
//"你的名字"
当 foo 函数的执行上下文销毁时,由于 foo 函数产生了闭包,所以变量 myName 和 test1 并没有被销毁,而是保存在内存中,那么应该如何解释这个现象呢?
站在内存模型的角度,分析这段代码的执行流程:
-
首先,执行js代码前会先编译再运行,因此会先创建全局的执行上下文;
-
接着,当调用foo 函数时,会编译并创建函数的执行上下文;
-
在编译过程中,遇到内部函数setName和getName,JavaScript 引擎会进行词法扫描,发现内部函数引用了外部函数的变量,判断是一个闭包,于是在堆空间创建换一个**“closure(foo)”的对象**(这是一个内部对象,JavaScript 是无法访问的),用来保存引用的****变量myName和test1,此时这些被引用的变量的集合就形成了闭包;
-
由于test2并没有被内部函数引用,所以test2依然保存在调用栈中;
那是如何做到函数执行上下文弹出后,还可以访问原来函数的变量?
当 foo 函数执行结束之后,返回的getName和setName方法都引用“clourse(foo)”对象,所以在下次调用bar.setName或者bar.getName时,创建的执行上下文中就包含了“clourse(foo)”,通过这个对象就可以访问原来函数的变量了
总的来说,产生闭包的核心有两步:第一步是需要预扫描内部函数;第二步是把内部函数引用的外部变量保存到堆中
还有一个问题,闭包是怎么回收的?
通常,如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;如果这个闭包以后不再使用的话,就会造成内存泄漏;
如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存;
所以在使用闭包尽量注意一个原则:如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大,那就尽量让它成为一个局部变量