浏览器是如何运行JavaScript代码的?
看例1:
|
|
用图解释这一行代码是如何执行的。
这是一个简单的图示,现在我们来增加几个概念:栈内存、堆内存和预处理。
栈内存
栈内存用来存放基本数据类型(Number、String、Boolean、Null和Undefined),在执行完之后销毁。
栈内存与另一个概念息息相关——作用域,即代码的执行环境。上图中左边的栈内存就是全局作用域,而右边的则是局部作用域。全局作用域在浏览器窗口关闭之后才销毁。局部作用域在执行完之后就会销毁。
JavaScript规定,父作用域不能使用子作用域中变量和方程,而反过来是可以的。这个反过来的方向链条则被称为作用域链。
这里需要注意的是,判断子作用域的父作用域是哪一个,要看这个子作用域是在哪里定义的,而不是在哪里执行。
堆内存
堆内存用来存放引用数据类型(object、array、function、date),在没有被引用之后销毁。
当我们声明和定义了一个引用数据类型之后,这个对象保存在堆内存中,而这个对象的地址则保存在栈内存中以用于引用。
在全局作用域声明和定义的引用数据类型,销毁的方法是手动赋值null。
看一组例子来说明栈内存与堆内存的区别:
例2
|
|
例3
|
|
例2的图解
例3的图解
由上两个图解可见,当基本数据类型传递的时候,其实是复制了一个新的数据给另一个变量;而当引用类型传递的时候,复制的仅仅是引用数据类型的地址,两个变量通过地址指向的是同一个堆内存中的数据。
所以在例3中,当我们改变n.a的时候,m.a也同样改变了。
预处理
预处理是浏览器在执行代码前要做的任务,它包括变量的声明和函数声明与定义。
预处理是变量提升的原因。
当我们写了var num = 12
这样的一行代码的时候,在执行时其实是分为两步:声明var num
和定义num = 12
。对于变量,预处理只做声明而不做定义。
而相对于函数function fn(){var num 12}
,同样有声明和定义之分,与变量不同的是,预处理时声明和定义全部执行。具体步骤是:声明function fn()
,定义fn() = "{var num = 12}"
。
理解了栈内存、堆内存和预处理之后,重新画出例1的图示:
这里的堆内存xxxfff000被全局作用域的函数fn引用,而全局作用域只有在浏览器窗口关闭的时候才会销毁,所以,只要浏览器窗口没有关闭,则堆内存xxxfff000一直被引用而不会销毁。
参考资料:
JavaScript高级程序设计(第三版);