We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
没有比页面点击没有响应更能令用户烦心的事情了。JavaScript是单线程,js脚本在解析过程中,用户点击界面没有响应。 下面这个例子,用户点击按钮的时候,他会触发UI触发两个进程,一个考虑css有没有添加:active等伪类属性,如果有就改变它的样式,另一个进程是js进程,即如下位于<script>标签内的代码,js代码创建一个新的div元素最后将他appendChild到body里面,这个时候会引发页面重绘+重排,进程如图
:active
<script>
div
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> </head> <body> <button onclick="handleClick()">click me</button> <script> function handleClick (){ const div = document.createElement('div'); div.innerHTML = 'Clicked!' document.body.appendChild(div) } </script> </body> </html>
对于js来说,多久运行时间算久,事实证明,超过100ms的都会引起用户的操作不适应,因此超过100ms就算久,那么应对业务需要,定时器应用而生。 下面这段代码,将在250ms后在UI队列中插入一个执行test()函数的JavaScript任务。
function test(){ console.log('settimeout'); } setTimeout(test,250);
总之,任何情况下创造一个定时器会造成UI线程暂停,因此定时器会重置所有有关浏览器的限制,包括长时间运行的脚本定时器,调用栈也在定时器的代码中重置为0,这一特性使得定时器成为长时间运行JavaScript代码理想的跨浏览器解决方案。setTimeout和setInterval几乎相同,除了前者重复添加js任务到UI队列中,。他们最主要的区别就是如果UI队列中已经存在由同一个setInterval创建的任务,那么后续任务不会被添加到UI队列中。 尝试运行如下代码
setTimeout
setInterval
setInterval(()=>console.log(1),0);
进程,线程简单说明 JavaScript是单线程的,这个是由于为了防止一个dom元素被绘制的时候同时又被修改 js单线程以及css对页面的渲染,这两个呈现互斥关系,css渲染则js被暂时挂起,js运行的时候,css渲染又被挂起,他们共同在UI多线程中来回切换,按顺序执行,其中还有setTimeout的定时器线程,setTimeout是独立于js单线程之外的一个线程,因此js在由setTimeout的延迟执行的时候才不会被阻塞后面的代码继续执行,这样子的js配合另一个setTimeout线程才能实现异步函数Promisse等的能力, 一般情况下浏览器至少有三个常驻线程(前三个)
setTimeout(function(){ console.log('timer'); }, 0); console.time('js耗时:') for(var i = 0; i < 1e4; i++){ console.log(1); } console.timeEnd('js耗时:')
为什么这样呢? 因为setTimeout已经被放入另外一个线程中执行,所以定时器的存在才不会对js单线程造成阻塞。所以setTimeout的延迟时间无论被设置成多少,他都会被放在js这个线程之后再执行。也许你会怀疑,因为某些浏览器默认setTimeout执行时间默认最少16ms,假设我将setTimeout后面的js代码执行1w次循环,浏览器耗时7s+才循环完毕。 但看上图我截图的,timer依然在他之后输出。这就说明了setTimeout必然是被放在了另外一个独立于js线程之外的线程,只有在js线程执行完毕后才回去执行setTimeout线程,(chrome的每一个网页都是一个独立的进程,因此才会在一个网页卡死后,另一个网页照样过可以流程运行打开,同时也防止了不同网页之间的相互干扰,因为不同进程之间在chrome里面被保护以至于他们之间相互隔离)
同样的道理,通过上面的例子可以看出,定时器所设置的延迟时间 的计算规则,它是从整一段js代码执行完毕之后开始计算的(包括我那段用了7s来循环一万次的代码,);定时器通过浏览器的时间戳来在js主线程代码执行完毕后延迟0s再执行。所以任何不报错代码但是执行时间很长的代码放入setTimeout中不会造成js阻塞。
通常发生在当你为浏览器滚动添加事件监听的时候,浏览器会高频触发事件,但是同样的道理,事件监听里面的代码也是被放入另一个事件触发线程之中,只有当你document触发屏幕滚动事件的时候才会触发这段代码,这和setTimeout有点不一致,但是和他的兄弟setInterval(间隔固定时间执行js代码)却很类似。他们都和js主线程相独立开来。
这个另外写,你也可以看阮一峰的再谈Event Loop,或者nodejs官方介绍,也许以后我会再写吧。毕竟我自己也要去搞nodejs
document.addEventListener('scroll',()=>{ console.log('scroll') },false)
web worker 的运行环境,由以下组成
他与网页通过实践接口进行通信 ,网页代码可以通过postMessage()方法给worker传递数据,
postMessage()
var work = new worker('code.js'); worker.onmessage = function(){ alert('event data'); } worker.postMessage('Nicholas'); // code.js内部代码 self.onmessage = function(event){ self.postMessage('hello, ',+event.data+ "!"); }
最终的字符串在worker的onmessage事件处理器中构造完成。消息系统是网页和worker通讯的唯一途径。
worker内部由importScripts()方法加载外部文件,该方法可以同时接受多个文件。他的加载过程是阻塞的,但是由于worker在UI之外的线程运行,所以这种阻塞在这里却是优点,并不会造成UI响应,例如
importScripts()
// code.js内部代码 importScript('file.js','file2.js'); self.onmessage = function(){ self.postMessage('hello, '+event.data+'!'); }
// chrome 。。。。用不了,但看别人用的又没问题。。
原因,渲染dom的时候不能执行javascript代码,执行javascript代码的时候,UI界面会暂停响应。 那么和setTimeout相比,web worker脱离独立与UI进程,而setTimeout则被放入js接下来的UI线程,这个遵循先入后出原则,因此是会影响接下来的操作的,所以超过100ms的JSON解析都应该被放入web worker去解析,以免造成网页界面卡顿无响应现象发生。。。而任何小于100ms的代码都可以考虑放入setTimeout去执行,因为小于100ms,用户根本察觉不到,利用好setTimeout可以让js和谐运行,而运用好web worker则可以让UI线程中的队列和谐进行下去。 如果javascript代码执行时间很长,那么UI就会无响应,这就是所谓的页面卡死。Web Workers是 HTML5 提供的一个javascript多线程解决方案,我们可以将一些耗时的javascript代码交由web Worker运行而不冻结用户界面。也就是说web worker和UI界面是运行在不同线程中的。它可以被用来处理一些接近500kb的json文件数据,计算中。。。这种一般被放入另外的事件线程,要么放入setTimeout定时器线程,要么放入worker线程,因为他在UI线程之外,不影响任何web操作。
结论:js和用户界面更新在同一个进程中进行,因此一次只能处理一件事情。这就意味着js运行过程中,用户不能输入,高效管理UI进程就是要确保js不能运行太长时间。
ajax是高性能JavaScript的基础,
在不刷新页面前提下获取后台数据,从而更新dom内容
五种常用技术用于向服务器请求数据:
这里由jquery封装的ajax,有fetch(es6),有(axios,jsonp)等package, 这里就不过多介绍。
get请求的数据会被缓存起来,而post则不会,只有当你get请求的参数长度超过2048个字符的时候才应该使用post,尽管post比get安全,
var scriptElement = document.createElement('script'); scriptElement.src = 'https://amy-domain.com/js/lib.js' document.head[0].appendChild(scriptElement);
但是和XML请求不同的是,你不能通过它设置请求的头信息,并且,参数传递也只能是get而不是post,并且她只能是纯粹的js代码,不能是json或者XML或者其他任何数据,无论任何代码都只能通过script.onload调用他们,尽管如此,这种技术速度却非常快,但是当他是不可控的时候,就是某种hack行为,因为无论这段动态注入的代码可以让网页重定向到其他网站,将用户密码发送出去,或者追踪用户操作并将他们发送到第三方服务器上面,因此引入的如果是外部不可控的js代码请格外小心。
一旦你确定了选择某种数据格式来传输,(建议用Graphql),那么接下来考虑其他优化方式
最快的Ajax请求就是没有请求,下面两种方法都可以
如果你希望响应被浏览器缓存,那么用get请求,还必须响应中加入正确的header
Expires: Mon, 28 July 2014 23:30:00 GMT
####避免多重求值 js提取一个包含代码的字符串有4中方法
const sum = new Function('arg1', 'arg2', 'return arg1 + arg2'); console.log(sum(1, 2));
var sum1 = 1; var sum2 = 1; setTimeout('sum = sum1 + sum2', 0);
var sum1 = 1; var sum2 = 1; setInterval('sum = sum1 + sum2', 0);
以上4中方法都比直接求值要慢很多,因为每次都要创建一个新的解释器/编译器
这个自己领悟
例如下面这段代码,同时兼容ie和chrome
function addHandler(target,event,handler){ if(target.addEventListener){ target.addEventListener(event,handler,false); }esle{ target.attachEvent('on'+event,handler) } }
应该避免多次检查addEventListener是否支持。 如下所示:初始化的时候就检测是否支持IE,
const isIE = target.addEventListener; function addHandler(target,event,handler){ if(isIE){ target.addEventListener(event,handler,false); }esle{ target.attachEvent('on'+event,handler) } }
第一种消除函数中重复工作的方法是延迟加载,意味着信息在被加载之前不会做任何事情,在函数调用之前,没有必要判断该用那个方法去绑定或者取消绑定事件,采用延迟加载的函数版本如下:
function addHandler(target, event, handler) { // 复写现有函数 if (target.addEventListener) { addHandler = function (target, event, handler) { target.addEventListener(event, handler, false); }; } else { addHandler = function (target, event, handler) { target.attachEvent(event, handler, false); }; } addHandler(target, event, handler); }
这个函数实现了延迟加载的加载模式,这两个方法第一次被调用中,先决定使用哪种方法调用,随后函数会被包含正确操作的新函数覆盖,最后一步调用新函数,随后每次调用addHandler()都不会再做检测,因为检测代码以及被新函数覆盖。 调用延迟加载函数的时候,第一次事件较长,因为它必须检测接着调用另一个函数完成任务,但随后调用函数会更快,因为不需要在做检测。当一个函数在页面不会立即调用执行时,延迟加载是最好的选择。
他会在脚本预加载期间提前检测,而不会等到函数被调用,检测操作依然只有一次,只是他在过程中来的更早,例如:
const addHandler = document.body.addEventListener ? function (target, event, handler) { target.addEventListener(event, handler, false); } : function (target, event, handler) { target.attachEvent(`on${event}`, handler); };
这个例子先检查addEventListener()是否存在,然后根据结果指定选择最佳函数,如果他们存在的话,三元运算符号,返回指定函数,这个三元函数在一开始就判断了,所以称之为条件预加载, 条件预加载确保所有函数调用消耗的时间相同,其代价是需要在脚本加载时就检测,而不是在加载后。条件预加载适用于一个函数马上就要用到,并且在整个页面的生命周期中频繁出现的场合。
尽管js经常被指责运行慢,但它这个语言在某部分运行速度快的让人难以置信。
1&2 //两个操作数对应位都是1,该位返回1 1|2 //两个操作数对应位一个是1,该位返回1 1^2 //两个操作数对应位只有一个是1,该位返回1 ~1 // 遇0则返回1,反之亦然
const newArr = [1, 2, 3, 4, 5, 6].map((arr, i) => { if (i & 2) { return 'event'; } return 'odd'; }); console.log(newArr);
这样以上代码可被改写
const newArr = [1, 2, 3, 4, 5, 6].map((arr, i) => { if (i % 2) { return 'event'; } return 'odd'; }); console.log(newArr);
这个建议看我翻译的Yahoo的35条优化网站建议
The text was updated successfully, but these errors were encountered:
plh97
No branches or pull requests
第六章:快速响应的用户界面
没有比页面点击没有响应更能令用户烦心的事情了。JavaScript是单线程,js脚本在解析过程中,用户点击界面没有响应。
下面这个例子,用户点击按钮的时候,他会触发UI触发两个进程,一个考虑css有没有添加
:active
等伪类属性,如果有就改变它的样式,另一个进程是js进程,即如下位于<script>
标签内的代码,js代码创建一个新的div
元素最后将他appendChild到body里面,这个时候会引发页面重绘+重排,进程如图定时器基础(从定时器的角度去了解浏览器页面进程,GUI线程以及js线程和setTimeou线程以及事件线程组成的线程执行的先入后出的执行顺序)
对于js来说,多久运行时间算久,事实证明,超过100ms的都会引起用户的操作不适应,因此超过100ms就算久,那么应对业务需要,定时器应用而生。
下面这段代码,将在250ms后在UI队列中插入一个执行test()函数的JavaScript任务。
总之,任何情况下创造一个定时器会造成UI线程暂停,因此定时器会重置所有有关浏览器的限制,包括长时间运行的脚本定时器,调用栈也在定时器的代码中重置为0,这一特性使得定时器成为长时间运行JavaScript代码理想的跨浏览器解决方案。
setTimeout
和setInterval
几乎相同,除了前者重复添加js任务到UI队列中,。他们最主要的区别就是如果UI队列中已经存在由同一个setInterval创建的任务,那么后续任务不会被添加到UI队列中。尝试运行如下代码
进程,线程简单说明
JavaScript是单线程的,这个是由于为了防止一个dom元素被绘制的时候同时又被修改
js单线程以及css对页面的渲染,这两个呈现互斥关系,css渲染则js被暂时挂起,js运行的时候,css渲染又被挂起,他们共同在UI多线程中来回切换,按顺序执行,其中还有setTimeout的定时器线程,setTimeout是独立于js单线程之外的一个线程,因此js在由setTimeout的延迟执行的时候才不会被阻塞后面的代码继续执行,这样子的js配合另一个setTimeout线程才能实现异步函数Promisse等的能力,
一般情况下浏览器至少有三个常驻线程(前三个)
从下面这段代码了解js单线程与setTimeout线程执行关系,
为什么这样呢?
因为setTimeout已经被放入另外一个线程中执行,所以定时器的存在才不会对js单线程造成阻塞。所以setTimeout的延迟时间无论被设置成多少,他都会被放在js这个线程之后再执行。也许你会怀疑,因为某些浏览器默认setTimeout执行时间默认最少16ms,假设我将setTimeout后面的js代码执行1w次循环,浏览器耗时7s+才循环完毕。
但看上图我截图的,timer依然在他之后输出。这就说明了setTimeout必然是被放在了另外一个独立于js线程之外的线程,只有在js线程执行完毕后才回去执行setTimeout线程,(chrome的每一个网页都是一个独立的进程,因此才会在一个网页卡死后,另一个网页照样过可以流程运行打开,同时也防止了不同网页之间的相互干扰,因为不同进程之间在chrome里面被保护以至于他们之间相互隔离)
同样的道理,通过上面的例子可以看出,定时器所设置的延迟时间 的计算规则,它是从整一段js代码执行完毕之后开始计算的(包括我那段用了7s来循环一万次的代码,);定时器通过浏览器的时间戳来在js主线程代码执行完毕后延迟0s再执行。所以任何不报错代码但是执行时间很长的代码放入setTimeout中不会造成js阻塞。
事件循环,
通常发生在当你为浏览器滚动添加事件监听的时候,浏览器会高频触发事件,但是同样的道理,事件监听里面的代码也是被放入另一个事件触发线程之中,只有当你document触发屏幕滚动事件的时候才会触发这段代码,这和setTimeout有点不一致,但是和他的兄弟setInterval(间隔固定时间执行js代码)却很类似。他们都和js主线程相独立开来。
Node.js的Event Loop
hahah上面这张图好有意思,v8威武
这个另外写,你也可以看阮一峰的再谈Event Loop,或者nodejs官方介绍,也许以后我会再写吧。毕竟我自己也要去搞nodejs
web Workers
web worker 的运行环境,由以下组成
与web worker通信
他与网页通过实践接口进行通信 ,网页代码可以通过
postMessage()
方法给worker传递数据,最终的字符串在worker的onmessage事件处理器中构造完成。消息系统是网页和worker通讯的唯一途径。
worker加载外部文件,
worker内部由
importScripts()
方法加载外部文件,该方法可以同时接受多个文件。他的加载过程是阻塞的,但是由于worker在UI之外的线程运行,所以这种阻塞在这里却是优点,并不会造成UI响应,例如worker 的兼容性
// chrome 。。。。用不了,但看别人用的又没问题。。
worker 的实用价值,
原因,渲染dom的时候不能执行javascript代码,执行javascript代码的时候,UI界面会暂停响应。
那么和setTimeout相比,web worker脱离独立与UI进程,而setTimeout则被放入js接下来的UI线程,这个遵循先入后出原则,因此是会影响接下来的操作的,所以超过100ms的JSON解析都应该被放入web worker去解析,以免造成网页界面卡顿无响应现象发生。。。而任何小于100ms的代码都可以考虑放入setTimeout去执行,因为小于100ms,用户根本察觉不到,利用好setTimeout可以让js和谐运行,而运用好web worker则可以让UI线程中的队列和谐进行下去。
如果javascript代码执行时间很长,那么UI就会无响应,这就是所谓的页面卡死。Web Workers是 HTML5 提供的一个javascript多线程解决方案,我们可以将一些耗时的javascript代码交由web Worker运行而不冻结用户界面。也就是说web worker和UI界面是运行在不同线程中的。它可以被用来处理一些接近500kb的json文件数据,计算中。。。这种一般被放入另外的事件线程,要么放入setTimeout定时器线程,要么放入worker线程,因为他在UI线程之外,不影响任何web操作。
结论:js和用户界面更新在同一个进程中进行,因此一次只能处理一件事情。这就意味着js运行过程中,用户不能输入,高效管理UI进程就是要确保js不能运行太长时间。
web应用越复杂,积极主动的去管理UI进程就越重要,即使js代码再重要,也不能影响用户体验。
第七章Ajax
ajax是高性能JavaScript的基础,
数据传输
在不刷新页面前提下获取后台数据,从而更新dom内容
请求数据
五种常用技术用于向服务器请求数据:
推荐使用XHR,动态脚本注入,Multipart XHR
不推荐使用iframe,Comet
XMLHttpRequest(XHR)
这里由jquery封装的ajax,有fetch(es6),有(axios,jsonp)等package,
这里就不过多介绍。
get 和 post
get请求的数据会被缓存起来,而post则不会,只有当你get请求的参数长度超过2048个字符的时候才应该使用post,尽管post比get安全,
动态脚本注入
但是和XML请求不同的是,你不能通过它设置请求的头信息,并且,参数传递也只能是get而不是post,并且她只能是纯粹的js代码,不能是json或者XML或者其他任何数据,无论任何代码都只能通过script.onload调用他们,尽管如此,这种技术速度却非常快,但是当他是不可控的时候,就是某种hack行为,因为无论这段动态注入的代码可以让网页重定向到其他网站,将用户密码发送出去,或者追踪用户操作并将他们发送到第三方服务器上面,因此引入的如果是外部不可控的js代码请格外小心。
Ajax 性能指南
一旦你确定了选择某种数据格式来传输,(建议用Graphql),那么接下来考虑其他优化方式
缓存数据
最快的Ajax请求就是没有请求,下面两种方法都可以
设置http头
如果你希望响应被浏览器缓存,那么用get请求,还必须响应中加入正确的header
第八章 编程实践
####避免多重求值
js提取一个包含代码的字符串有4中方法
以上4中方法都比直接求值要慢很多,因为每次都要创建一个新的解释器/编译器
应该使用Objecty/Array直接量
这个自己领悟
避免重复
例如下面这段代码,同时兼容ie和chrome
应该避免多次检查addEventListener是否支持。
如下所示:初始化的时候就检测是否支持IE,
延迟加载
第一种消除函数中重复工作的方法是延迟加载,意味着信息在被加载之前不会做任何事情,在函数调用之前,没有必要判断该用那个方法去绑定或者取消绑定事件,采用延迟加载的函数版本如下:
这个函数实现了延迟加载的加载模式,这两个方法第一次被调用中,先决定使用哪种方法调用,随后函数会被包含正确操作的新函数覆盖,最后一步调用新函数,随后每次调用addHandler()都不会再做检测,因为检测代码以及被新函数覆盖。
调用延迟加载函数的时候,第一次事件较长,因为它必须检测接着调用另一个函数完成任务,但随后调用函数会更快,因为不需要在做检测。当一个函数在页面不会立即调用执行时,延迟加载是最好的选择。
条件预加载
他会在脚本预加载期间提前检测,而不会等到函数被调用,检测操作依然只有一次,只是他在过程中来的更早,例如:
这个例子先检查addEventListener()是否存在,然后根据结果指定选择最佳函数,如果他们存在的话,三元运算符号,返回指定函数,这个三元函数在一开始就判断了,所以称之为条件预加载,
条件预加载确保所有函数调用消耗的时间相同,其代价是需要在脚本加载时就检测,而不是在加载后。条件预加载适用于一个函数马上就要用到,并且在整个页面的生命周期中频繁出现的场合。
使用速度快的部分
尽管js经常被指责运行慢,但它这个语言在某部分运行速度快的让人难以置信。
位操作符
这样以上代码可被改写
第十章 构建部署高性能JavaScript应用
这个建议看我翻译的Yahoo的35条优化网站建议
The text was updated successfully, but these errors were encountered: