一、作用域
1、js中作用域:
全局作用域、函数作用域、块级作用域(es6增加)
全局作用域:
1、页面打开创建、关闭销毁;
2、全局变量,window对象;
3、不小心声明的未定义好的变量,会变成全局变量,易造成全局污染。
函数作用域:
1、函数创建时产生,执行完销毁;
2、每次调用创建新的作用域,互相之间不重复;
块级作用域:
1、块作用域由 { } 包括
2、执行时创建,执行完销毁
1.1、作用域链 vs 执行上下文
js解释型语言,执行分为:解释、执行两个阶段。分别处理事情不一样:
解释阶段:
词法分析;
语法分析;
作用域规则确定;
执行阶段:
创建执行上下文;
执行函数代码;
垃圾回收
因此,作用域和执行上下文不是一个概念。作用域在定义时就确定,不会改变;执行上下文运行时确定,随时会改变。一个作用域下可能有多个或没有(函数未调用)上下文环境。
2、函数
2.1、函数定义方式
函数式声明、变量声明、function构造函数
function xx(){} // 函数声明在预编译的时候会提前
var xx = function(){}
var xx = new Function(); // 具备顶级函数作用域
2.2、调用方式
this指向:无论在何处定义创建,谁调用指向谁,无代表window。
1 、作为对象的方法调用:this -> 对象
2 、函数调用:this -> window
3 、构造器调用prototype:必须实例化对象才能调用;this -> 调用对象;
4 、call\apply\bind:this ->传入的this对象
call\apply\bind函数作用:重定义this指向,参数不限定类型
函数名 | 参数 | 生效 |
---|---|---|
apply | 参数数组形式放入 | 立即生效 |
call | 参数按顺序放入 | 立即生效 |
bind | 参数按顺序放入 | 返回一个函数,调用时生效 |
forEach\map的回调函数后都接受第二个参数,调整this指向。
二、预编译
预编译是某作用域在执行解释之前将该作用域下的变量声明、函数声明的数据进行初始化的过程。
全局作用域会生成对象GO、函数AO。
1、全局GO:
先创建GO空对象;
找变量声明,将之作为属性名,值为undefined
找函数声明,值赋予函数体
2、函数AO
先创建GO空对象;
找变量声明,将之作为属性名,值为undefined
实参形参统一;
找函数声明,值赋予函数体
三、作用域链
作用域链就是创建时在不同作用域对应的对象连接起来的链式结构。
1、闭包
表现形式:A中return B函数;函数嵌套;函数自调用
原理:函数调用执行时,其作用域才开始创建,可追溯到父级作用域链。B调用时,作用域链的关系A作用域的内容依旧可以访问,来达到B可以操作其创建之前A的作用域的内容。
经典例题:for循环里的定时器、获取多个元素添加点击事件
js执行时,先执行主线程,异步相关会存到异步队列里,当主线程执行完毕开始执行异步队列,循环里的值已执行到最后
好处:
函数局部变量长存内存中,避免全局变量污染,私有成员存在;
坏处:
内存泄露导致内存溢出;长存内容过多,性能问题。
使用场景:
1、迭代器
2、getter\setter
3、循环赋值
4、函数赋值、函数参数、返回值、自执行函数
5、首次区分:防抖、节流、缓存、单例模式
防抖:n秒后执行一次;节流:n秒内执行一次
2、内存管理
内存的生命周期
内存分配:声明变量、函数、对象等的时候,系统自动分配内存
内存使用:即读写内存,也就是使用变量、函数等
内存回收:使用完,由垃圾回收机制自动回收不在使用的内存
常见内存泄露
意外的全局变量
被遗忘的计时器或回调函数
脱离DOM的引用(DOM清除,事件还在)
闭包
如何避免内存泄露
减少不必要的全局变量。使用严格模式避免意外创建全局变量
使用完数据后,及时解除引用(闭包中的变量、dom引用、定时器清除;赋值null)
组织好代码逻辑,避免死循环
垃圾回收机制
原理:垃圾回收算法主要依赖于“引用”概念,原型(隐式)引用、属性(显式)引用;
方法
1、引用计数垃圾回收
是否有指向该对象的引用;
问题:循环引用(两个对象互相引用)。不会回收
2、标记清除算法
从根部(js即全局变量)出发定时扫描内存中的对象;不能到达即稍后回收
工作步骤:运行时给所有变量加上标记;从根部触发,能触及对象去掉标记;清除待标记的对象并回收他们占用的空间
应用:sizeOf函数,计算传入对象所占的Bytes数值
研究原因:线上使用了需要计算从服务端传输数据的大小
浏览器查看:chrome 开发者工具 -> memory -> Take snapshot -> 点击快照,在列表中寻找你的变量
实现 npm包:object-sizeof
function sizeof (object) {
if (Buffer.isBuffer(object)) {
return object.length
}
var objectType = typeof (object)
switch (objectType) {
case 'string':
return object.length * ECMA_SIZES.STRING
case 'boolean':
return ECMA_SIZES.BOOLEAN // 4
case 'number':
return ECMA_SIZES.NUMBER //8
case 'object':
if (Array.isArray(object)) {
return object.map(sizeof).reduce((acc, curr) => acc + curr, 0)
} else {
return sizeOfObject(object)
}
default:
return 0
}
}
function sizeOfObject (object) {
if (object == null) {
return 0
}
var bytes = 0
for (var key in object) {
if (!Object.hasOwnProperty.call(object, key)) {
continue
}
bytes += sizeof(key)
try {
bytes += sizeof(object[key])
} catch (ex) {
if (ex instanceof RangeError) {
// circular reference detected, final result might be incorrect
// let's be nice and not throw an exception
bytes = 0
}
}
}
return bytes
}