JS 性能问题
当浏览器遇到<script>标签时,当前 HTML 页面无从获知 JavaScript 是否会向<p>标签添加内容,或引入其他元素,或甚至移除该标签。因此,这时浏览器会停止处理页面,先执行 JavaScript代码,然后再继续解析和渲染页面。同样的情况也发生在使用 src 属性加载 JavaScript的过程中,浏览器必须先花时间下载外链文件中的代码,然后解析并执行它。在这个过程中,页面渲染和用户交互完全被阻塞了。
对象属性和数组元素的速度都比变量慢。
代码执行过程中,主要的数据访问具有四种类型:数值、变量 < 对象的属性、数组元素。如果多次引用一个数组元素或者对象属性,可以定义一个变量来提高性能(除FF浏览器,会自动优化数组);减少任何不必要的state/属性/变量。
避免全局查找
可以将全局对象的属性(window等)存放在局部变量中。访问局部变量的速度比全局变量更快;
避免with语句
脚本放在html代码后面
JS执行从上到下;如果一段JS代码引入外部链接,那么会先下载JS文件后执行,期间其他的代码阻塞;(其他的css、image下载暂停)。所以优先把script部分放在界面底部。
优化循环的性能:循环过程中,次数不确定,需要消耗较多性能;在循环体内减少变量数量等。
for (let i = 0; i < 10; i++) {
// 这样,每次循环会创建变量、判断变量、变量增加
}
// 如果使用
let a = 10;
do {
//这里需要创建一次变量
} while (a--);
最小化访问nodeList对象
document.getElementByTagName("img");
获取 tagName、childrenNode/attributes/ 等会得到 NodeList 对象,减少这样的使用;
避免使用循环引用:
一个DOM元素具有一个属性(方法),这个方法继续操作这个DOM元素,这样会造成内存泄漏(直到浏览器关闭界面)。
字符串连接:
如果存在多个字符串链接,使用 += 不利于性能(string += a; string += b;)。
可以使用 string += a + b + c; 的方式
可以使用数组暂时存储字符串,使用array.join(’’);进行链接字符串;
instanceof 和 typeof 对比
typeof 判断一个数据是什么数据类型;
instanceof 判断一个对象是否在另一个对象的原型链上;
对象相互引用
对象相互引用会导致引用计数始终为2,所以用完对象后应将引用设为null,例子如下
let element = document.getElementById("test");
let myObject = new Object();
myObject.element = element;
element.someObject = myObject;
//....用完后需要加如下代码
myObject.element = null;
element.someObject = null;
当数据不再有用时,需要通过将值设为null来解除引用,该做法适用于大多数全局变量和全局对象属性
function createPerson(name){
let localPerson = new Object();
localPerson.name = name;
return localPerson
}
let globalPerson = createPerson("test")
//...用完后手动解除
globalPerson = null
关于与闭包相关的内存泄漏如下
function assignHandler(){
let element = document.getElementById("test");
element.onclick = function(){
alert(element.id)
}
}
//以上会导致element的引用数无法被回收,更改如下
function assignHandler(){
let element = document.getElementById("test");
let id = element.id;
element.onclick = function(){
alert(id)
}
element = null;
}
事件委托
在js中,添加到页面上的事件处理程序数量会直接关系到页面整体运行运行性能。导致这一问题的原因是多方面的。首先函数都是对象,都会占用内存;内存中对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。以下为对应的情况以及处理方法。
同类型的事件处理函数过多时,应该结合为一个,例子如下
//html代码
<ul id="myLinks">
<li id="goSomeWhere">Go somewhere</li>
<li id="sayHi">Say hi</hi>
</ul>
//分别加上事件处理-JS代码
let item1 = document.getElementById("goSomeWhere");
let item2 = document.getElementById("sayHi");
EventUtil.addHandler(item1, "click", function(event){
console.log("goSomeWhere")
}
EventUtil.addHandler(item2, "click", function(event){
console.log("sayHi");
}
//改善点即将click事件结合在一起
let list = document.getElementById("myLinks")
EventUtil.addHandler(list, "click", function(event){
event = EventUtil.getEvent(event);
let target = EventUtil.getTarget(event);
switch(target.id){
case "goSomeWhere":
console.log("goSomeWhere");
break;
case "sayHi":
console.log("sayHi");
break;
}
}
内存留有过时不用的“空事件处理程序”也是造成性能问题的主因,两种情况下会造成该问题。运用removeChild()和replaceChild()方法去除节点时;在使用innerHTML替换页面某一部分时,如果带有事件处理程序的元素被innerHTML删除了,那么原有事件处理函数极有可能无法被回收,例子如下
//例子中id为myBtn的点击事件变为了空事件处理程序
<div id="myDiv">
<input type="button" value="Click Me" id="myBtn">
</div>
<script type="text/javascript">
let btn = document.getElementById("myBtn");
btn.onclick = function(){
document.getElementById("myDiv").innerHTML = "xxxx";
};
</script>
//改善点即需要手工移除事件处理程序
<div id="myDiv">
<input type="button" value="Click Me" id="myBtn">
</div>
<script type="text/javascript">
let btn = document.getElementById("myBtn");
btn.onclick = function(){
btn.onclick = null;
document.getElementById("myDiv").innerHTML = "xxxx";
};
</script>
注意作用域
若某处循环使用全局变量时,我们可以略做修改,例子如下
//假设有多个img标签的内容,循环中引用了多次document全局变量
function updateUI(){
let imgs = document.getElementsByTagName("img")
for (let i = 0; len = imgs.length; i < len; ++i){
imgs[i].title = document.title + " image “ + i
}
let msg = document.getElementById("msg");
msg.innerHTML = "Update";
}
//改善点
function updateUI(){
let doc = document
let imgs = doc.getElementsByTagName("img")
for (let i = 0; len = imgs.length; i < len; ++i){
imgs[i].title = doc.title + " image “ + i
}
let msg = doc.getElementById("msg");
msg.innerHTML = "Update";
}
尽量少用with,因为with会增加其中执行代码的作用域链的长度
选择正确方法
首先,我们要了解JS中算法的复杂度

常数值和访问数组元素操作都是O(1)操作;对象属性查找操作是O(n)操作;
如let values = [5, 10]; let sum = values[0] + values[1]属于O(1)操作;let values = window.location.href属于O(2)操作
遇到有多次属性查询的场合,可以考虑是否能做优化,例子如下
//这里总共做了6次属性查询,其中window.location.href.substring与window.location.href.indexOf分别为3次
let query = window.location.href.subsring(window.location.href.indexOf("?"))
//改善, 第一次访问时复杂度会是O(n),但该版本只有4次属性查询,相对于原始版本节省了33%
let url = window.location.href;
let query = url.substring(url.indexOf("?"));
循环优化,这里其实用后测试循环代替前测试循环会更好,不过本地不采用,例子如下
//原有复杂度为O(n)
for (let i = 0; i < values.length; ++i){
process(values[i]);
}
//更改后复杂度为O(1)
for (let i = values.length - 1; i >= 0; --i){
process(values[i])
}
最小化语句数相关
//多个声明
let count = 5;
let color = "blue";
let values = [1, 2, 3];
//组合成一个
let count = 5,
color = ”blue",
values = [1, 2, 3]
例如插入迭代值时,例子如下
//修改前
let name = values[i];
i++;
//修改后
let name = values[i++]
使用数组和对象字面量时,例子如下
//修改前
let values = new Array();
values[0] = 123;
values[1] = 456;
values[2] = 789;
let person = new Object();
person.name = "Eric";
person.age = 20;
//修改后
let values = [123, 456, 789]
let person = {
name: "Eric",
age:20,
}
创建DOM节点最好使用innerHTML方法,因为innerHTML设置值时,后台会创建HTML解析器,然后使用内部的DOM调用来创建DOM结构,而非基于JS的DOM调用。
调用一次innerHTML,就会进行一次现场刷新,循环插入DOM结构时,应注意尽量调用少次数的innerHTML,代码如下
//错误方法,做了很多次现场刷新
let list = document.getElementById("myList"),
i;
for (i = 0; i < 10; ++i){
list.innerHTML = html+= "<li>Item " + i + "</li>"
}
//正确方法,尽管在字符串连接上有性能损失,但却只做了一次现场刷新
let list = document.getElementById("myList"),
html = "",
i;
for (i = 0; i < 10; ++i){
html += "<li>Item " + i + "</li>"
}
list.innerHTML = html
其他
如有多个if-else语句时,应尽可能转为Switch语句;用appendChild()插入元素时,应采用自上而下插入;面向对象编程时,应合理释放内存,设object为null。
转载链接:https://blog.csdn.net/weixin_41697143/article/details/84196494
https://www.cnblogs.com/tianshu/p/10555921.html
网友评论