1,用setTimeout模拟定期计时和直接用setInterval是有区别的。
-
因为每次setTimeout计时到后就会去执行,然后执行一段时间后才会继续setTimeout,中间就多了误差(误差多少与代码执行时间有关)
-
而setInterval则是每次都精确的隔一段时间推入一个事件(但是,事件的实际执行时间不一定就准确,还有可能是这个事件还没执行完毕,下一个事件就来了)
而且setInterval有一些比较致命的问题就是:
-
累计效应(上面提到的),如果setInterval代码在(setInterval)再次添加到队列之前还没有完成执行,
就会导致定时器代码连续运行好几次,而之间没有间隔。
就算正常间隔执行,多个setInterval的代码执行时间可能会比预期小(因为代码执行需要一定时间) -
譬如像iOS的webview,或者Safari等浏览器中都有一个特点,在滚动的时候是不执行JS的,如果使用了setInterval,会发现在滚动结束后会执行多次由于滚动不执行JS积攒回调,如果回调执行时间过长,就会非常容器造成卡顿问题和一些不可知的错误(这一块后续有补充,setInterval自带的优化,不会重复添加回调)
-
而且把浏览器最小化显示等操作时,setInterval并不是不执行程序
-
setTimeout的执行原理:js除了正常的消息队列之外,还有一个延迟队列,延迟队列记录延迟时间和延迟的回调,在正常消息队列结束之后,开始调用延迟队列的消息,所以setTimeout的执行时间其实是不准确的,比如你同步代码太多,就会影响setTimeout的执行时间;注意点,setTimeout是有最大时间值的,这个最大值跟浏览器有关Chrome、Safari、Firefox用32bit来存储延迟时间,换算下来2147483647ms,24.8天左右
2,异步函数的回调函数会新开一个执行堆栈,try-catch不能捕获信息
3,临时加个小点(因为我觉得未来我会用到)
MutationObserver:监控DOM变化(属于HTML5的api)
可以用来模拟Promise(浏览器不支持Promise的话,但是优先级小于Promise)
//选择一个需要观察的节点
var targetNode = document.getElementById('some-id');
// 设置observer的配置选项
var config = { attributes: true, childList: true, subtree: true };
// 当节点发生变化时的需要执行的函数
var callback = function(mutationsList, observer) {
for(var mutation of mutationsList) {
if (mutation.type == 'childList') {
console.log('A child node has been added or removed.');
}
else if (mutation.type == 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
// 创建一个observer示例与回调函数相关联
var observer = new MutationObserver(callback);
//使用配置文件对目标节点进行观测
observer.observe(targetNode, config);
// 停止观测
observer.disconnect();
像以前的Vue源码中就是利用它来模拟nextTick的,
具体原理是,创建一个TextNode并监听内容变化,
然后要nextTick的时候去改一下这个节点的文本内容,
如下:(Vue的源码,未修改)
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
4, js的垃圾回收机制(检测没办法调用该资源时,该资源会被回收)
JavaScript 中主要的内存管理概念是 可达性。
简而言之,“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。
这里列出固有的可达值的基本集合,这些值明显不能被释放。
比方说:
- 当前函数的局部变量和参数。
- 嵌套调用时,当前调用链上所有函数的变量与参数。
- 全局变量。(还有一些内部的)
这些值被称作 根(roots)。
如果一个值可以通过引用或引用链从根访问任何其他值,则认为该值是 可达 的。
比方说,如果局部变量中有一个对象,并且该对象有一个属性引用了另一个对象,则该对象被认为是可达的。而且它引用的内容也是可达的。下面是详细的例子。
在 JavaScript 引擎中有一个被称作 垃圾回收器 的东西在后台执行。它监控着所有对象的状态,并删除掉那些已经不可达的。
细化一下V8的回收机制:
我感觉我解释不清楚(毕竟菜鸟一只)
参考资料:参考资料(已翻译)
堆的构成
在我们深入研究垃圾回收器的内部工作原理之前,首先来看看堆是如何组织的。V8将堆分为了几个不同的区域:
新生区:大多数对象被分配在这里。新生区是一个很小的区域,垃圾回收在这个区域非常频繁,与其他区域相独立。
老生指针区:这里包含大多数可能存在指向其他对象的指针的对象。大多数在新生区存活一段时间之后的对象都会被挪到这里。
老生数据区:这里存放只包含原始数据的对象(这些对象没有指向其他对象的指针)。字符串、封箱的数字以及未封箱的双精度数字数组,在新生区存活一段时间后会被移动到这里。
大对象区:这里存放体积超越其他区大小的对象。每个对象有自己mmap产生的内存。垃圾回收器从不移动大对象。
代码区:代码对象,也就是包含JIT之后指令的对象,会被分配到这里。这是唯一拥有执行权限的内存区(不过如果代码对象因过大而放在大对象区,则该大对象所对应的内存也是可执行的。译注:但是大对象内存区本身不是可执行的内存区)。
Cell区、属性Cell区、Map区:这些区域存放Cell、属性Cell和Map,每个区域因为都是存放相同大小的元素,因此内存结构很简单。
每个区域都由一组内存页构成。内存页是一块连续的内存,经mmap(或者Windows的什么等价物)由操作系统分配而来。除大对象区的内存页较大之外,每个区的内存页都是1MB大小,且按1MB内存对齐。除了存储对象,内存页还含有一个页头(包含一些元数据和标识信息)以及一个位图区(用以标记哪些对象是活跃的)。另外,每个内存页还有一个单独分配在另外内存区的槽缓冲区,里面放着一组对象,这些对象可能指向其他存储在该页的对象。这就是一套经典配置方案,其他的方案我们稍后讨论。
有了这些背景知识,我们可以来深入垃圾回收器了。