JS是一门编译语言,是在浏览器运行时候开始编译的,浏览器是如何解析我们的 JS的呢?JS的解析过程可分为: 语法检查阶段和执行阶段
- 语法分析:
- 词法分析:把js的字符流转换成标记流
- 语法分析: 把标记流产生的记号按照ECMAScript标准生成语法树 (把收集到的信息存储到数据类型中)
- 执行阶段:
- 预解析:
- 创建执行上下文;变量对象(变量声明,函数声明,参数),作用域链(变量对象以及所有父级作用域),this
- 对变量对象和活动对象填充数值(权重:函数参数>函数声明>变量声明)
- 函数参数:执行上下文变量对象的一个属性,属性名称是这个形参,值是实参的,没有传递参数值就是undefined
- 函数声明:执行上下文变量对象的一个属性,属性名和属性值都是是函数对象创建出来的,如果变量对象已经包含了与他相同名字的属性,则会替换它的值
- 变量声明:执行上下文变量对象的一个属性,属性名就是这个变量名,值是undefined,如果变量名和参数名函数声明名重复,则该声明会被忽略,但是其赋值操作不会被忽略
- 执行阶段
- 预解析之后就是js真正的执行阶段了,js引擎会一行一行读取运行代码,变量对象和活动对象都被赋予真是的值,如果函数没有被调用则里边的变量永远都是undefined
我们为了方便存储和读取数据,往往会将不同的数据存放在不同的地方,这些存放数据的地方就可以叫做作用域(作用域就是存储和读取的一套规则),JS中没有只有全局作用域和函数作用域(ES6才引入块级作用域)
- 作用域
- 创建: js采用的是静态作用域(词法作用域),也就是在词法分析的时候就把作用域确定好了,之后是不能改变的(evel()和with()可以改变但是消耗性能)
- 特点:作用域可以嵌套但是不能重叠(重叠就覆盖)
- 定义:是一种分类存储数据规则
- 作用域链
- 创建:作用域链是一个数组 [[Scope]] :[当前变量对象,父级上下文变量对象...];其中当前变量对象是在函数执行之前创建的,第二部分是在函数声明时就已经确定的
- 特点:变量查找是只能一级一级向上找,不能向下找(子级能访问父级的变量,父级不能访问子级的变量)
- 定义:是 一种变量查找的规则
垃圾回收机制:
我们知道我们的浏览器内存是有限的,随着js中的变量、对象增多势必会对浏览器造成很大压力,那么我们就需要在使用完这些变量、对象之后就给他们清楚除掉;很幸运的是我们的JS有一套自己的垃圾回收机制,在我们不需要这些变量和对象的时候会自动帮我们清除他们
JS使用的清除方式是标记清除:当变量进入到执行环境中时,标记为"进入环境",在离开执行环境时标记位"离开环境";当变量为"离开环境"时就会被垃圾回收机制给清除
|
function fn(){ var a = 3;//函数执行时a被标记为"进入环境" } fn();//函数调用完之后 a被标记为"离开环境" 被清除 |
- 不会被清除的情况
- 全局作用域变量:全局作用域的变量在浏览器打开的时候会一直被标记为"进入环境",只有在关闭浏览器的时候才会被标记为"离开环境"才能被清除;所以应该尽量减少定义全局变量或者使用严格模式
- 闭包:当调用闭包的那个对象一直都没有被清除(比如在全局调用闭包,或者异步调用闭包的时候),那么闭包里的变量就一直存在不会被清除,做法是在调用完之后把调用闭包的那个对象给清空为'null'
- 定时器和回调:没有清除定时器,那里边的变量就会一直存在不会被销毁;能清除的定时器一定要清除
- Dom元素的引用:我们通常会将Dom引用放进一个数组或者对象中,当我们不需要这个对象了把这个对象删除了,但是这个对象的引用还是依然存在的,这个时候就需要手动将这个Dom的引用删除:整个对象为'null'或者删除这个属性