前端学习之路


  • 首页

  • 归档

  • 公益404

堆和栈的区别

发表于 2017-03-08 |

栈(stack)

栈是为执行线程而在内存中临时留出的空间。当一个程序被调用,栈顶的块被保留下来,作为局部变量和一些临时的数据。当那个程序返回后,这个块被释放了,并且能够在下次一个程序被调用的时候被使用。栈总是以后进先出(LIFO)的顺序保存数据,最近被保留的块总是最先被释放的。这使得记录栈很容易;释放一个块仅仅只是调整了一个指针。

堆(heapd)

堆是为了动态分配而在内存中留出的。不像栈,在堆中没有固定的模式去分配和重新分配块;你能够在任何时刻分配一个块和释放一个块。这使得在给定时间内去追踪堆的哪部分是被分配了的和是被释放了的变得十分复杂;有许多可用的自定义的堆分配器,他们可以根据不同的使用模式来对堆的表现形式进行调整。

每个线程都有一个栈,但在一个应用中一般只有一个堆。

原文地址

进程与线程的区别

发表于 2017-03-08 |

原文地址

进程 VS 线程

进程是一个应用的执行实例。这意味着什么?举个例子,当你双击微软的Word图标的时候,你启动了一个运行Word的进程。线程是进程内部的执行路径。同时,一个进程能容纳多个线程。当你启动Word的时候,操作系统创建了一个进程并且开始执行进程的主线程。

值得一提的是,进程能坐的任何事情,线程都能做。但是当一个进程包含了多个线程,每个线程可以被当做一个轻量的进程。因此,进程和线程在本质上的区别,是他们被用来完成的工作。线程被用来完成小任务,然而进程被用来完成更繁重的任务——基本上用来执行应用。

进程和线程的另一个区别是:在同一个进程中的线程共享同样的地址空间,而不同的进程却不会这样。这允许线程去读/写相同的数据结构和变量,也使得线程之间的通信变得容易了。进程之间的通信——也被称作IPC——是很困难而且资源密集的。

多线程

多个线程是被允许的。有一个常见的例子用来说明多线程的优势:你能够让一个Word进程通过背景线程去渲染文档,但同时另一个运行中的线程能够接受用户输入,因此你才能够编辑一个新的文档。

如果我们面对一个只有单线程的应用,那么应用程序将只能一次做一件事——因此同时渲染和响应用户输入在单线程应用中是不可能实现的。

每个进程都有他自己的地址空间,但是同一个进程中的多个线程共享着这个地址空间。线程也能够在该进程内共享任何资源。这意味着在线程之中共享数据非常容易,但也使得线程之间很容易相互依赖,而导致不好的事情发生。

多线程程序必须谨慎的编写代码去防止这种事情发生。更新被多个线程所共享的数据结构的代码段被称为critical sections(临界区)。当临界区运行在一个线程上的时候,其他线程就不被允许进入这个临界区,这一点是非常重要的。这被叫做同步,我们不会在这一点上再做深入的讨论。但重点是多线程需要谨慎的编程。

同样的,线程之间上下文环境切换所需要的代价通常也比进程之间的要小。最后,线程之间通信的花销(通信时所需的代价)相对于进程来说也小得多。

这里有几点关于进程和线程之间区别的总结:

  1. 线程比进程更容易创建,因为他们不需要独立的地址空间
  2. 多线程需要谨慎编程,因为线程共享的数据结构一次只应该被一个线程改变。不像线程,进程不共享相同的地址空间
  3. 线程被认为是轻量的,因为相对于进程来说,线程所需的资源要少很多
  4. 进程相互独立。而线程之间共享了同样的地址空间,因此是相互依存的,所以一定要注意不要让不同的线程之间相互干扰。这和上面的第二点总结表达的是相同的观点
  5. 进程可由多个线程组成

转载请注明出处

querySelector和getElementBy方法的比较

发表于 2017-03-07 |

今天发现了一个新的API用来获取页面中的节点元素querySelector和querySelectorAll,他们的作用和getElementByXXX系列是一样的。下面看一下这个新方法的性能如何。

程序执行时间测试方法

先来科普一下如何看某段程序的运行时间。其实方法很简单:

1
2
3
4
5
var start = +new Date();
someLongProcess();
var stop = +new Date();
console.log(stop - start); // 函数执行时间

像上面这样我们就可以获取到函数的运行时间

构造测试环境

为了使测试效果更明显,我们先构造一个巨大的DOM树:

1
2
3
4
5
<!--index.html-->
<body>
<div id="root"></div>
<script src="./index.js" type="text/javascript"></script>
</body>

1
2
3
4
5
6
7
8
// index.js
var root = document.getElementById('root');
var html = '';
for(var i = 0; i < 50000; i++) {
html += '<span class="test-span-tag"><a class="inner-class-name"></a></span>';
}
root.innerHTML = html;

这样我们就得到了一个内部有5W个span元素的DOM树结构

下面我们将比较querySelectorAll()方法和getElementsByTagName()方法的性能

性能测试

1
2
3
4
5
6
7
8
9
10
11
12
13
// getElementsByTagName 方法
var ta1 = +new Date();
var domWay = document.getElementsByTagName('span');
var ta2 = +new Date();
domWay[0].innerHTML = '0';
var ta3 = +new Date();
// querySelectorAll 方法
var tb1 = +new Date();
var apiWay = document.querySelectorAll('span');
var tb2 = +new Date();
apiWay[0].innerHTML = '1';
var tb3 = +new Date();

可以看到我们在执行要比较的两个函数之前和之后分别调用了new Date()来获取当前时刻,他们的差值就是两个函数获取节点所需的信息;后面我们又对获取到的节点进行了赋值操作。

最后输出时间间隔

1
2
3
4
5
6
var record = document.createElement('div');
record.innerHTML = '<p>document.getElementById("root").getElementsByTagName("span"): ' + (ta2 - ta1) + 'ms</p>\
<p>document.getElementById("root").getElementsByTagName("span") 并改变一个节点的值: ' + (ta3 - ta1) + 'ms</p>\
<p>document.querySelectorAll("#root span"): ' + (tb2 - tb1) + 'ms</p>\
<p>document.querySelectorAll("#root span") 并改变一个节点的值: ' + (tb3 - tb1) + 'ms</p>';
root.appendChild(record);

源代码

每次结果都有些差异,我截取了其中一个结果

图片

我们可以看到,使用getElementsByTagName方法,在获取节点的过程中,性能是相当好的,获取5W个节点的集合,时间基本不会超过1ms,相对来说querySelectorAll方法的性能就稍差一些;这么说来querySelectorAll的出现就没有意义了吗?答案是否定的。我们可以观察到,如果对获取到的节点集合进行简单的操作,getElementsByTagName消耗的时间会很长,而querySelectorAll几乎不消耗时间,这是为什么呢?

原理

当我们在使用getElementsByClassName方法的时候,我们得到的节点集合是实时的,也就是说,每次对集合进行操作之前,都会重新遍历一遍节点以获取DOM树中最新的状态,可以理解为获得了节点集合的引用;而querySelectorAll方法正相反,其获得的是DOM树中当前时刻节点集合的快照,是静态的值。

结论

于是我们可以得出结论,如果只要单纯的获取DOM树中节点的集合,而不需要改变节点元素的时候,getElementsByClassName方法的速度要快很多;如果要对集合中的节点元素进行批量操作的时候,可以选择使用querySelectorAll方法来提高性能

转载请注明出处 http://cthblog.cn/2017/03/07/querySelector-vs-dom-method/

离线应用与客户端缓存

发表于 2017-03-03 |

离线检测

navigator.onLine属性检测是否离线

1
2
3
4
5
if (navigator.onLine) {
// online
} else {
// offline
}

浏览器在离线和在线状态相互转换时,会在window对象上触发online和offline事件,可以通过监听事件检测浏览器状态

应用缓存

Go offline with application cache

HTML5的应用缓存,简称appcache。
可以用一个描述文件,列出要下载和缓存的资源

1
2
3
4
5
CACHE MANIFEST
#Comment
file.js
file.css

关联描述文件与页面

1
<html manifest="/offline.manifest">

##待插入内容

数据储存

Cookie

高级计时器

发表于 2017-03-03 |

概念

JavaScript的定时器不是线程,JavaScript是运行于单线程的环境中的,而定时器仅仅只是计划代码在未来的某个时间执行。

实际上,浏览器负责进行排序,指派某段代码在某个时间点运行的优先级。

JavaScript进程线

可以想象JavaScript在一条时间轴上运行。

页面载入时,首先执行任何包含在<script>元素内部的代码。在这之后,JavaScript进程将等待更多代码执行。当进程空闲的时候,下一个代码会被触发并立刻执行。

例如,当点击某个按钮时,onclick事件就会立即执行,只要JavaScript进程处于空闲状态

JavaScript进程线

除了主JavaScript执行进程外,还有一个需要在进程下一次空闲时执行的代码队列。随着页面在其生命周期中的推移,代码会按照执行顺序添加入队列。

这就意味着,任何代码都会先经过队列,然后再在后面可能的时刻被添加到进程中执行。即没有任何代码是真正意义上的立即执行。

定时器

定时器对队列的工作方式是,当特定时间过去后将代码插入。

给队列添加代码不意味着代码立即执行,这只表示在150ms后代码会被添加到队列中。

1
2
3
4
5
6
var btn = document.getElementById("my-btn");
btn.onclick = function() {
setTimeout(function(){
document.getElementById("message").style.visibility = 'visible';
}, 250);
}

点击按钮后,首先将onclick事件处理程序加入队列。待该程序执行后,才设置定时器。再有250ms后,制定代码才被添加到队列中等待执行。

定时器代码执行时间

虽然在255ms时定时器代码被添加至队列,但此时JavaScript进程并非空间,因此至少要等到onclick事件执行完毕后,定时器程序才会执行

重复的定时器

当使用setInterval()时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中。这确保了定时器代码加入到队列中的最小时间间隔为指定间隔。

但仍存在两个问题

  • 某些间隔会被跳过
  • 多个定时器的代码执行之间的间隔可能会比预期小

重复定时器代码执行过程

第一个定时器在205ms处添加到队列,但是要直到过了300ms这一点才能执行,在执行这个定时器代码时,在405ms处又添加了一个定时器代码。在605ms处,第一个定时器代码还在运行,同时队列中已经有了一个定时器代码的实例,所以在这个时间点上的定时器代码不会被添加到队列中。结果5ms添加的定时器代码执行结束后,405ms时添加的定时器代码就立刻执行。

解决方案

为了避免setInterval的这两个缺点,可以用链式setTimeout调用

1
2
3
4
setTimeout(function() {
// 其他代码
setTimeout(arguments.callee, interval);
}, interval);

这样在前一个定时器代码执行完成之前,都不会再向队列插入新的定时器代码

总结

需要使用JavaScript的定时器按一定时间间隔执行一段程序时,最好使用setTimeout来模拟setInterval

转载请注明出处 http://cthblog.cn/2017/03/03/%E9%AB%98%E7%BA%A7%E8%AE%A1%E6%97%B6%E5%99%A8/

123
Tianhao Cui

Tianhao Cui

私、気になります!

15 日志
© 2017 Tianhao Cui
由 Hexo 强力驱动
主题 - NexT.Pisces