聊一聊:变量,作用域与内存

数据类型

js数据类型分两种:

  1. 基本数据类型:Undefined、 Null、 String、 Boolean、 Number、 String 和 Symbol(ES6新增)
  2. 复杂数据类型:Object

区别:内存的分配不同

  1. 基本数据类型存储在栈中
  2. 复杂数据类型存储在堆中,栈中存储的变量,是指向堆中的引用地址

区分数据类型的方法:

  1. typeof:可以确定值是否是基本数据类型
  2. instanceof:可以确定值是否是引用数据类型用
    1
    2
    typeof null // object
    null instanceof Object // false

区分数组和Object

1
2
// [Object, objct]、[Object, array]
Object.prototype.toString.call()

null是一个对象吗?

另外尽管 typeof null === 'object',但null 不是一个对象,这是一个历史遗留问题,JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,null 表示为全零,所以将它错误的判断为 object 。


作用域链

内部上下文可以通过作用域链访问外部上下文的一切,但外部上下文无法访问内部上下文中的东西。找一个变量,如果在局部找到,则搜索停止;否则,继续沿着作用域链向上搜(作用域链中的对象有原型链,因此搜索也可能涉及每一个对象的原型链),这个过程一直延续到全局上下文的变量对象;若仍找不到,则未声明。

js执行上下文

执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行。

执行上下文分全局上下文、函数上下文和块级上下文

JS执行上下文栈(后面简称执行栈)

执行栈,也叫做调用栈,具有 LIFO (后进先出) 结构,用于存储在代码执行期间创建的所有执行上下文。

规则如下:
首次运行JavaScript代码的时候,会创建一个全局执行的上下文并Push到当前的执行栈中,每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push当前执行栈的栈顶。
当栈顶的函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文的控制权将移动到当前执行栈的下一个执行上下文。

以一段代码具体说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
function fun3() {
console.log('fun3')
}

function fun2() {
fun3();
}

function fun1() {
fun2();
}

fun1();

Global Execution Context (即全局执行上下文)首先入栈,过程如下:

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//全局执行上下文首先入栈
ECStack.push(globalContext);

//执行fun1();
ECStack.push(<fun1> functionContext);

//fun1中又调用了fun2;
ECStack.push(<fun2> functionContext);

//fun2中又调用了fun3;
ECStack.push(<fun3> functionContext);

//fun3执行完毕
ECStack.pop();

//fun2执行完毕
ECStack.pop();

//fun1执行完毕
ECStack.pop();

//javascript继续顺序执行下面的代码,但ECStack底部始终有一个 全局上下文(globalContext);

参考

块级作用域的概念

由最近的一对{}界定

var、let 和 const

  1. var存在变量的提升
  2. let也存在变量的提升,但因存在“暂时性死区”,故不能在声明前使用let变量
  3. let与var不同在于,同一作用域内let不能重复声明,let有块级作用域;let在全局作用域声明的变量不会成为window的属性
  4. const和let基本相同,除了一点,声明时必须初始化变量,且不能修改;但可以修改const对象的属性(const a = Object.freeze({})可以冻结)

js变量是松散类型,意思是可以保存任何类型的数据

内存管理

优化内存的手段:

  1. 数据不再用,赋值null,释放其引用
  2. let和const有块级作用域,有助于垃圾回收