事件处理和垃圾回收机制
事件处理机制
过程分析
JavaScript是单线程执行的,现在推行的多线程执行,都可以理解成伪多线程,事件循环是实现异步的机制
更具体来讲:
js的事件大体可以分为两种,Macrotask宏任务,Microtask微任务,循环机制为先执行宏任务,一个宏任务结束后执行内部对应的微任务,然后再执行下一个宏任务
Macrotask: script(整体代码),setTimeout, setInterval, setImmediate, I/O, UI rendering
Microtask:process.nextTick, Promises, Object.observe, MutationObserver
在现在浏览器中可能使用到就script(整体代码),setTimeout,setInterval,UI rendering,promise等,nodejs可能会用到其他的。
题目示例
下面来看一个题目理解一下:
console.log('start');
setTimeout(()=>{
console.log('A');
},0);
console.log('end');
// start
// end
// A
先执行主代码块,即先执行start输出语句,然后将settimeout压入宏任务执行队列,再执行end输出语句,再执行宏任务执行队列输出A
进阶题目
1.
setTimeout(()=>{
console.log('A');
},0);
var obj={
func:function () {
setTimeout(function () {
console.log('B')
},0);
return new Promise(function (resolve) {
console.log('C');
resolve();
})
}
};
obj.func().then(function () {
console.log('D')
});
console.log('E');
分析题目:
- 将第一行语句压入settimeout执行队列。
- 调用func()时,将console.log(‘B’)所在函数压入settimeout执行队列
- 返回promise,promise的exector函数执行console.log(‘C’),输出C
- 将.then语句压入promise的微任务队列
- 执行console.log(“E”)
- 执行微任务队列,输出D;执行settimeout宏任务队列,输出A,B
结果:CEDAB
2.
setTimeout(function(){
console.log('定时器开始啦')
});
new Promise(function(resolve){
console.log('马上执行for循环啦');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('执行then函数啦')
});
console.log('代码执行结束');
// 马上执行for循环啦
// 代码执行结束
// 执行then函数啦
// 定时器开始啦
3.
console.log('1');
setTimeout(function() {
console.log('2');
new Promise(function(resolve) {
console.log('3');
resolve();
}).then(function() {
console.log('4')
})
})
new Promise(function(resolve) {
console.log('5');
resolve();
}).then(function() {
console.log('6')
})
setTimeout(function() {
console.log('7');
new Promise(function(resolve) {
console.log('8');
resolve();
}).then(function() {
console.log('9')
})
})
// 1 5 6 2 3 4 7 8 9
-
执行宏任务完整script,输出1
-
异步setTimeout,压入宏任务queue,命名为settimeout1
-
执行promise,输出5,将.then压入微任务queue,命名为then1
-
异步setTimeout,压入宏任务queue,命名为settimeout2
此时执行完第一个宏任务,则eventTable如下:
宏任务 macrotask queue 微任务 microtask queue settimeout1 then1 settimeout2 -
执行微任务,then1输出6
-
执行宏任务settimeout1,输出2 3 将then压入微任务,命名为then2
此时执行完第二个宏任务,则eventTable如下:
宏任务 macrotask queue 微任务 microtask queue settimeout2 then2 settimeout2 -
执行微任务,then2输出4,
-
执行宏任务settimeout2,输出7 8 将then压入微任务,命名为then3
此时执行完第三个宏任务,则eventTable如下:
宏任务 macrotask queue 微任务 macrotask queue then3 -
执行then3,输出9
垃圾回收机制
JavaScript的垃圾回收机制
Javascript 会找出不再使用的变量,不再使用意味着这个变量生命周期的结束。Javascript 中存在两种变量——全局变量和局部变量,全部变量的声明周期会一直持续,直到页面卸载
两种回收的实现思路
标记清除
当变量进入执行环境时标记为“进入环境”,当变量离开执行环境时则标记为“离开环境”,被标记为“进入环境”的变量是不能被回收的,因为它们正在被使用,而标记为“离开环境”的变量则可以被回收
function a(){
name = 123 // 进入执行环境
}
a(); // 离开执行环境 name被回收
这种计数方式可能会因为闭包造成内存泄露(下文会讲到)
引用计数(不太常用)
统计引用类型变量声明后被引用的次数,当次数为 0 时,该变量将被回收
function b(){
let a = {}; 此时次数为0
let b = a; 次数+1
let c = a; 次数+1
let b = 0; 次数-1
let c = 0; 次数-1 计数为0 被回收
}
但可能出现互相引用的情况
function func5 () {
let f = {}
let g = {}
f.prop = g
g.prop = f
// 由于 f 和 g 互相引用,计数永远不可能为 0
}
可能造成内存泄露的情况
意外的全局变量: 无法被回收
function a(){
this.name = "mm"
}
console.log(name)
a的this指向window,此时name在全局中被保存,不会在a中被回收,导致内存泄露
定时器: 未被正确关闭,导致所引用的外部变量无法被释放
var m = test(){ console.log('lala') }
setInterval(function(){
console.log(m);
},1000)
此时m无法被释放,且次操作没有意义
闭包: 会导致父级中的变量无法被释放
function m(){
let d = '123'
return function(){
this.d = '456'
}
}
let c = m();
此时d为活动对象,不会被销毁
dom 引用: dom 元素被删除时,内存中的引用未被正确清空
var m = {
ele:document.getElementById('test')
}
function removeEle(){
document.body.removeChild(document.getElementById('test'));
}
removeEle();
console.log(m);
此时仍然指向对于div的内存引用