高效的 JavaScript

ECMAScript

避免使用eval或Function构造器

首先废话一句,根本没用过并且也不知道eval和Function是什么鬼……借由此契机,我终于给js权威指南开封了,2333……

  • eval(code)
    执行一段字符串中的JavaScript代码。code可包含一条或多条js语句,最后一条语句成为eval的返回值;若无返回值(console.log(…)),则返回undefined。
  • Function() - 构造函数
    1
    2
    3
    4
    >   var f = new Function("x", "y", "return x*y;");
    > // 等同于
    > var f = function(x, y) { return x*y; };
    >

可以传入大于等于1个字符串,最后一个字符串为函数体,前面的字符串则为函数形参名称。
每次调用Function()都会解析函数体,创建新的匿名函数对象。
Function()在全局作用域执行,读取全局变量。

先大致翻阅了解了一下,可以哪天再开个文研究研究~回归正题:

从以上了解就能感受到,性能的影响在于每次调用时的解析。
eval被调用时直接在上下文档中进行解释,因此就无法优化相关上下文,使浏览器在运行时需要解释得内容变多。Function稍微好些,它不影响周围代码的使用,但仍运行缓慢,如果在循环中使用Function就是灾难。

解决建议:

  1. 改写eval
    简而言之,eval几乎不用存在,想用前先研究一下有没有其他方法效果相同?
  2. 使用function替代Function
    function(){...}的使用效果与new Function("...")完全一致

可见,本宝宝发现自己根本没见识过这两个东东也是有道理的,呵呵…

不要使用with

666,这个也没用过。

with会在引用变量时为脚本引擎构造一个额外的作用域,这个作用域不能被编译期获知,所以编译器不会像优化普通的作用域(比如由函数创建的作用域)那样优化它,从而影响性能。

with的使用举例:

1
2
3
4
5
with(test.information.settings.files) {
primary = 'names';
secondary = 'roles';
tertiary = 'references';
}

更有效率的做法 —— 使用普通变量来引用对象,然后通过这个变量来访问其属性:

1
2
3
4
var testObject = test.information.settings.files;
testObject.primary = 'names';
testObject.secondary = 'roles';
testObject.tertiary = 'references';

显然这么搞我更熟悉,2333……

不要在要求性能的函数中使用 try-catch-finally

由于还没在js中用过这个,但前一阵子在python中有了体验:写在一个循环里,销魂啊,循环了二十次左右后pythonw.exe直接崩了……后来查了资料,改用if判断提前规避错误了……

try-catch-finally在运行时会在当前作用域创建一个新变量,每次catch子句运行的时候,这个变量会引用捕捉到的异常对象。这个变量在catch子句开始的时候创建,并在这个子句结束的时候销毁,即在脚本运行时创建和销毁,所以带来了性能问题。

异常处理应尽可能地放在更高层次的脚本中,使异常可能不会频繁发生;或者可以先检查操作是否可行,以避免异常发生。(啊,这不就是本宝宝自己想出来的if判断,本宝宝真机智~)

虽然不够应景,但耐不住感触深啊。pia一下我当时老崩的python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import arcpy
from arcpy import env
from arcpy.sa import *
from arcpy import env
env.workspace = "E:/weixy/10硕士毕设/数据"
arcpy.CheckOutExtension("Spatial")
day = 1
year = 2001
while year <= 2014:
if day < 10:
day_string="%i"%year+"00"+"%i"%day
elif day < 100:
day_string="%i"%year+"0"+"%i"%day
else:
day_string="%i"%year+"%i"%day
try:
inRaster = "/2001-2015白天地表温度/MOD11A2_"+day_string+".LST_Day_1km.tif"
outExtractByMask = ExtractByMask(inRaster, inRasterborder)
FinalResult = Con(outExtractByMask, outExtractByMask * 0.02, "", "VALUE <> 0")
FinalResult.save("E:/weixy/10硕士毕设/数据/青海湖2001-2014白天地表温度/MOD11A2_"+day_string+".LST_Day_1km.tif")
except:
print day_string
else:
del FinalResult
finally:
day = day + 8
if day > 365:
year = year + 1
day = 1
arcpy.CheckInExtension("Spatial")
print 'finished'

这个是改进后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import os, re
import arcpy
from arcpy.sa import *
from arcpy import env
env.workspace = "H:/原始数据与预处理数据/MOD15A2/2001-2016 LAI/"
arcpy.CheckOutExtension("Spatial")
inRasterborder = "E:/weixy/10硕士毕设/数据/qhhl坐标.shp"
# 生成特定日期的编号
def dayString(day, year):
if day < 10:
day_string="%i"%year+"00"+"%i"%day
elif day < 100:
day_string="%i"%year+"0"+"%i"%day
else:
day_string="%i"%year+"%i"%day
return day_string
# 下一日期
def nextDay(day_string):
day = int(day_string) % 1000
year = int(day_string) / 1000
day = day + 8
if day > 365:
year = year + 1
day = 1
return dayString(day, year)
# main
try:
day_string = dayString(1, 2001)
while int(day_string) < 2017001:
sta = 0
# 遍历数据文件夹寻找该文件
for theFile in os.listdir('H:/原始数据与预处理数据/MOD15A2/2001-2016 LAI'):
if re.search(day_string, theFile) != None:
sta = 1
# 找到该日的图像,进行GIS处理
inRaster = "MOD15A2_"+day_string+".Lai_1km.tif"
outExtractByMask = ExtractByMask(inRaster, inRasterborder)
FinalResult = Con(outExtractByMask, outExtractByMask * 0.1, "", "VALUE <= 100")
FinalResult.save("H:/原始数据与预处理数据/青海湖2001-2016叶面积指数/MOD15A2_"+day_string+".Lai_1km.tif")
# 指向下一日
day_string = nextDay(day_string)
break
# 始终没有找到图像
if sta == 0:
print "lost" + day_string
day_string = nextDay(day_string)
else:
sta = 0
except:
print arcpy.GetMessages()
else:
del FinalResult # 删除变量占用
finally:
arcpy.CheckInExtension("Spatial")
print 'finished'

隔离eval和with的使用

前文说了尽可能的少用或不用eval和with,但在必需使用它们时,就要注意了:

  • 不要在循环中重复执行它们,或者其他的反复调用它们
  • 它们只适合在执行一次或很少几次的代码中使用
  • 把它们与其它代码隔离开来,这样就不会影响到其它代码的性能
  • 可以把它们放在一个顶层函数中,或者只运行一次并把结果保存下来,以便稍后可以使用其结果而不必再运行这些代码

另外某些浏览器解析try-catch-finally结构时会对性能产生影响,包括 Opera,所以最好以同样的方式对其进行隔离。

尽量不用全局变量

我就是走全局变量的懒人,要改!

全局变量对性能的影响:

  • 如果代码在函数或另一个作用域中引用全局变量,脚本引擎会依次通过每个作用域直到全局作用域进行查询,而局部变量找起来就会快得多。
  • 全局作用域中的变量存在于脚本的整个生命周期,而局部变量会在离开局部作用域的时候被销毁,它们占用的内存可以被垃圾收集器回收。
  • 全局作用域由window对象共享,也就是说它本质上是两个作用域而不是一个。在全局作用域中,变量总是通过其名称来定位,而不是像局部变量那样经过优化,通过预定义的索引来定位。这最终导致脚本引擎需要花更多时间来找到全局变量。

*函数通常也在全局作用域中创建。因此一个函数调另一个函数,另一个函数再接着调其它函数,也会增加脚本引擎的运行时间。

低效率版:

1
2
3
4
5
6
7
var i, s = '';
function testfunction() {
for(i = 0; i < 20; i++) {
s += i;
}
}
testfunction();

高效率版(快30%左右):

1
2
3
4
5
6
7
function testfunction() {
var i, s = '';
for(i = 0; i < 20; i++) {
s += i;
}
}
testfunction();

注意对象的隐式转换

额,这点也是我的恶习……从没注意过,甚至一度不解过为什么要有new的形式

字面量(如字符串、数、布尔值等),在ECMAScript中有两种表现形式:作为值创建、作为对象创建。例如:var oString = 'some content';:创建了一个字符串值;var oString = new String('some content');:创建了等价的字符串对象。

所有属性和方法都是在字符串对象而不是值上定义的。
如果对字符串值调用属性和方法,ECMAScript引擎会用相同的字符串值隐式地创建一个新的字符串对象,然后才调用属性或方法。并且,这个对象仅用于这一次需求,如果下次再对字符串值调用某个方法,会再次类似地创建一个新的字符串对象。

低效率版。每次访问length属性和调用charAt方法的时候都会创建对象,脚本引擎总共会创建21个新的字符串对象(terrible…):

1
2
3
4
var s = '0123456789';
for(var i = 0; i < s.length; i++) {
s.charAt(i);
}

高效率版。只创建了一个对象:

1
2
3
4
var s = new String('0123456789');
for(var i = 0; i < s.length; i++) {
s.charAt(i);
}

在要求性能的函数中避免使用 for-in

不太习惯for-in循环,但一直以为这是一种高级的写法,如今看来,哈哈~

for-in循环需要脚本引擎为所有可枚举的属性创建一个列表,然后检查其中的重复项,之后才开始遍历。所以当脚本本身已经知道需要遍历哪些属性的时候(如所需遍历属性名称为有序数字的情况),使用简单的for循环更为合适。比如数组、伪数组(如由DOM创建的NodeList)

低效率版:

1
2
3
4
var oSum = 0;
for(var i in oArray) {
oSum += oArray[i];
}

高效率版:

1
2
3
4
5
var oSum = 0;
var oLength = oArray.length;
for(var i = 0; i < oLength; i++) {
oSum += oArray[i];
}

使用累加形式连接字符串

再次,膝盖中一箭……

字符串连接非常消耗性能。使用+运算符不会直接把结果赋值给变量,而会在内存中创建一个新的字符串用于保存结果,再将这个新的字符串赋值给变量。

低效率版:a += 'x' + 'y';。这段代码首先在内存中创建一个临时的字符串保存连接的结果’xy’,然后将它连接到a的当前值,再将最终的连接结果赋值给a。

高效率版。因为每次都是直接赋值,所以不会使用临时字符串,运行速度会快20%,并且消耗内存更少:

1
2
a += 'x';
a += 'y';

基本运算比调用函数更快

比如:直接通过数组的尾部索引添加元素 比 对数组调用push方法 更佳;简单的数学计算 比 调用Math对象的方法 更佳。

低效率版:

1
2
arr.push(v);
var min = Math.min(a,b);

高效率版:

1
2
arr[arr.length] = v;
var min = a < b ? a : b;

为setTimeout()和setInterval()传入函数而不是字符串

即,传入定义好的函数的函数名:setInterval(f1, 1000);
或者匿名函数:setInterval(function(){...}, 1000);
不要用setInterval('f1()', 1000);这类的字符串形式。

DOM

总的来说,有三个主要因素会导致DOM的性能不佳。

  • 脚本进行了大量的DOM操作,比如通过收到的数据创建一棵树。
  • 脚本触发了太多重排或者重绘。
  • 脚本使用了低性能的方法来定位DOM树中的节点。

重绘和重排

重绘:
某元素从可见变为不可见,或者反之,但没有改变文档布局。例如:为某个元素添加轮廓线,改变背景色或者改变visibility样式等。
重绘对性能的消耗在于:它需要引擎搜索所有元素来决定什么是可见的,什么应该显示出来。

重排(更耗性能):
对DOM树进行了改动操作,或者某个元素样式变动时改变了文档布局。例如:改变元素的className属性(这个为什么会重排?),改变浏览器窗口的大小。
重排对性能的消耗在于:它相当于重新布局整个页面(父元素的重排会引起子元素的重排,某个重排元素之后的元素也需要重新计算新的布局位置,子孙元素大小的改变也会导致祖先元素的重排……),即:牵一发而动全身

以下是对一些重排或重绘操作的优化建议:

1. 将重排数量降到最低

首先必须承认,重排是不能完全避免的,比如动画。所以要保证脚本跑得飞快,就必须在保证相同整体效果的前提下将重排保持在最低限度。
浏览器可以选择在脚本线程完成后进行重排,显示变化。Opera会等到发生了足够多的变化,经过了一定的时间,或者脚本线程结束,再重排。也就是说,如果在同一个线程中发生的变化足够快,它们就只会触发一次重排。然而,Opera运行在不同速度的设备上,这种现象并不保证一定会发生。
有些元素在重排时,显示速度慢于其它元素,要注意规避。比如,重排一个table需要3倍于等效块元素显示的时间。

2. 最小重排

一般的重排会影响到整个文档,文档中需要重排的东西越多,重排花的时间就越长。
然而,绝对定位(absolute)和固定定位(fixed)的元素不会影响主文档的布局,所以对它们的重排不会引起其它部分的连锁反应。文档中在它们之后的内容可能需要重绘来呈现变化,但这也远比一个完整的重排好得多。
因此,动画不需要应用于整个文档,它最好只应用在一个固定位置的元素上。

3. 关于修改文档树

修改DOM树(添加新的节点、改变文本节点的值或者修改各种属性)会导致重排,而多次连续地改变可能导致多次重排。
因此,最好在一段未显示出来的DOM树片段上进行多次改变,然后用一个单一的操作把改变应用在文档的DOM中:

1
2
3
4
5
6
7
8
9
var docFragm = document.createDocumentFragment();
var elem, contents;
for(var i = 0; i < textlist.length; i++) {
elem = document.createElement('p');
contents = document.createTextNode(textlist[i]);
elem.appendChild(contents);
docFragm.appendChild(elem);
}
document.body.appendChild(docFragm);

修改文档树也可以通过克隆实现(注意如果元素中包含任何形式的控制,或者其本身或子元素存在事件响应,则不能使用这个方法,因为这些附着关系不会被克隆):

1
2
3
4
5
6
7
8
9
10
11
var original = document.getElementById('container');
var cloned = original.cloneNode(true);
cloned.setAttribute('width', '50%');
var elem, contents;
for(var i = 0; i < textlist.length; i++) {
elem = document.createElement('p');
contents = document.createTextNode(textlist[i]);
elem.appendChild(contents);
cloned.appendChild(elem);
}
original.parentNode.replaceChild(cloned, original);

4. 修改不可见的元素

如果某个元素的display样式设置为none,就不会对其进行重绘,哪怕它的内容发生改变 —— 这是一种优势。
因此,如果需要对某个元素或者它的子元素进行改变,而且这些改变又不能合并在一个单独的重绘中,那就可以先设置这个元素的样式为display:none,然后改变它,再把它设置为普通的显示状态。

1
2
3
4
5
6
var posElem = document.getElementById('animation');
posElem.style.display = 'none';
posElem.appendChild(newNodes);
posElem.style.width = '10em';
... // 其他变化
posElem.style.display = 'block';

不过这会造成两次额外的重排,一次是在隐藏元素的时候,另一次是它再次显示出来的时候,所以需要权衡什么时候需要使用这种方法,什么时候不用。
另外,这样做也可能意外导致滚动条跳跃,不过把这种方式应用于固定位置的元素就不会导致难看的效果。

5. 关于测量元素

一般而言,浏览器会缓存一些变化,然后在这些变化都完成之后只进行一次重排。
但是,测量元素会导致浏览器强制重排(如,使用offsetWidth这样的属性,或者getComputedStyle这样的方法)。一旦调用,就改变了浏览器的缓存,从而触发重排,即使这不会引起明显的重绘。
因此,如果这些测量数据需要反复使用,建议仅测量一次,然后将结果保存起来以备后用。

6. 多项样式的一起改变

如果需要对某个元素一次性改变多个CSS样式,不宜一个个地去指定样式,可以使用以下两种方式:
1.如果需要变化的样式是已知的,则可以定义一个包含这些样式变化的class,通过修改元素的class实现。
2.如果变化的样式是未知的,例如动画,则可以为元素定义一个新的样式属性,通过style对象的cssText属性实现,或者通过setAttribute实现。

1
2
3
4
5
6
7
8
9
var posElem = document.getElementById('animation');
var newStyle = 'background: ' + newBack + ';' +
'color: ' + newColor + ';' +
'border: ' + newBorder + ';';
if(typeof(posElem.style.cssText) != 'undefined') {
posElem.style.cssText = newStyle;
} else {
posElem.setAttribute('style', newStyle);
}

平滑度换速度

开发者总是希望通过使用更小的间隔时间和更小的变化,让动画尽可能平滑。
但是,10ms几乎已经是浏览器能在不100%占用大多数台式机CPU的情况能实现的最小时间间隔。对于多数浏览器来说,每秒进行100次重排实在太多了。对于低功耗计算机或低功耗设备上的浏览器,这样的动画只会给人以缓慢和卡顿的感觉。
所以有必要权衡平滑度与速度的关系,适当地使用动画的平滑度来换取速度。比如将时间间隔改变为50ms,动画每次移动5个像素,这样需要的处理能力更少,也会让动画在低功耗处理器上运行起来快得多。

关于遍历检索特定节点

这里涉及到了以前不曾关注的DOM遍历问题,有必要完整学习一下。(一些值得注意的方法:DOM2 Traversal TreeWalker、XPath)

1. 避免检索大量节点

在试图找到某个特定节点,或者某个节点的子集时,应该使用内置的方法和DOM集合来缩小搜索范围,使之在尽可能少的节点内进行搜索。

这里讨论的是js的检索,但是一般实践中我用jquery更多,不知道$选择器是怎样操作的,写全完整路径更优还是模糊范围更优?

2. 通过XPath提升检索速度

假设需要在一个包含了上千元素的文档中获取h2-h4元素,它们散落在文档各处,没有任何适当的结构,所以不能用递归来获得正确的顺序。
传统的DOM遍历方法,因为文档中元素过多,一个一个遍历会导致显著的延迟:

1
2
3
4
5
6
var allElements = document.getElementsByTagName('*');
for(var i = 0; i < allElements.length; i++) {
if(allElements[i].tagName.match(/^h[2-4]$/i)) {
// …
}
}

使用XPath可以优化查询引擎,查询速度甚至可以提升高达两个数量级:

1
2
3
4
5
var headings = document.evaluate('//h2|//h3|//h4', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
var oneheading;
while(oneheading = headings.iterateNext()) {
// …
}

*可以通过if( document.evaluate )来判断浏览器是否支持XPath。

3. 避免在遍历DOM的时候进行修改

对于某些类型的DOM集合,如果你的脚本在检索它的时候改变了相关元素,集合会立即发生变化而不会等你的脚本运行结束。如:childNodes集合,以及getElementsByTagName返回的节点列表。
对于这样的集合,如果在检索时又向里添加元素,那可能会导致一个无限循环,因为在到达终点前不断的往集合内添加项。
另外,更重要的是:这些集合原可以被优化以提升性能,它们能记住长度和脚本引用的最后一个索引,以便在增加索引的时候,能迅速引用下一个节点。但是如果你修改了DOM树的任意部分(哪怕它不在集合中),集合就必须重新寻找新的条目。这样做的话,它就不能记住最后的索引或长度,因为这些可能已经变化,之前所做的优化也就失效了。

解决方案:

先建立一个静态元素列表用于修改,然后遍历这个静态列表来进行修改,以此避免对getElementsByTagName返回的列表进行修改:

1
2
3
4
5
6
7
8
9
10
var allPara = document.getElementsByTagName('p');
var collectTemp = [];
for(var i = 0; i < allPara.length; i++) {
collectTemp[collectTemp.length] = allPara[i];
}
for(i = 0; i < collectTemp.length; i++) {
collectTemp[i].appendChild(document.createTextNode(i));
}
// 这里难道没有一个再把collectTemp赋值回allPara的操作吗?不然改动怎么生效?
collectTemp = null;

4. 在脚本中用变量缓存检索到的DOM值

DOM返回的某些值是不缓存的,它们会在再次调用的时候重新计算(如getElementById方法)。因此建议用变量保存返回值,以便后续的多次使用(命令运行的速度会快五到十倍)。

1
2
3
4
5
6
var sample = document.getElementById('test');
sample.property1 = 'value1';
sample.property2 = 'value2';
sample.property3 = 'value3';
sample.property4 = 'value4';
...

文档加载

避免在多个文档间保持同一个引用

如果一个文档访问了另一个文档的节点或者对象,应该避免在脚本使用完它们之后仍然保留它们的引用。如果某个引用保存在当前文档的全局变量中,或者保存在某个长期存在的对象的属性中,通过将其设置为null,或者通过delete来清除它。

1
2
3
4
5
6
7
8
9
var remoteDoc = parent.frames['sideframe'].document;
var remoteContainer = remoteDoc.getElementById('content');
var newPara = remoteDoc.createElement('p');
newPara.appendChild(remoteDoc.createTextNode('new content'));
remoteContainer.appendChild(newPara);
// 清除引用
remoteDoc = null;
remoteContainer = null;
newPara = null;

原因:
如果另一个文档已经销毁(比如原来显示在弹出窗中而现在这个窗口关闭了),当前文档中保存的引用通常仍然会使其DOM树或者脚本环境在RAM中存在,哪怕文档本身已经不在加载状态了。
在框架页面,内联框架页面或object元素中同样存在这个问题(额,啥框架,比如说?还是不太懂这方面的啊)

关于浏览器的快速历史导航功能

Opera(以及很多其它浏览器)默认使用快速历史导航,即:当用户在浏览器历史上前进或回退的时候,页面的状态及其中的脚本其实都被保存了。当用户回到某个页面的时候,它会像从未离开过一样继续运行,文档不会再次加载和初始化。
快速历史导航有助于浏览器实现对用户的快速响应,使加载缓慢的Web应用在导航过程中表现得更好。因此,我们要做的就是使脚本尽量避免做出会导致这种行为失败的事情。例如:在表单提交时禁用表单控件、菜单项被点击之后就不再有效、离开页面时的淡出效果使内容模糊不清或不可见。
使用onunload监听器是比较简单的解决办法,可以通过它重置淡出效果,或者使表单控件变为可用:

1
2
3
window.onunload = function () {
document.body.style.opacity = '1';
};

但是,某些浏览器(Firefox、Safari)使用unload监听器会导致快速历史导航失效。此外,禁用提交按钮在Opera中也会导致快速历史导航失效。

使用XMLHttpRequest

该方法能有效减少从服务器接收的内容,同时避免页面加载带来的脚本环境的破坏和再造。最初,页面以正常的方式加载,之后再通过XMLHttpRequest来加载最小需求的新内容。这会让JavaScript环境保持下来。
这里的js环境指执行环境(Execution context,EC),或称执行上下文,可以深扒一下

不过需要注意的是,这并非对所有项目都适用,而且这种访求可能会导致问题——它完全打破了历史导航,虽然可以通过将信息保存在内联框架中来伪造历史,但这违背了使用XMLHttpReqest的首要目的。因此,请谨慎地,只在它所造成的变化不需要回退的时候使用它。
这种方法也有可能对辅助设备(什么是辅助设备?)造成混乱,因为辅助设备感受不到页面上的DOM的变化。所以最好在确保不会出现问题的情况下使用这个方法。

对于不允许JavaScript,或者浏览器不支持XMLHttpReqeust的情况,可以使用一个正常的链接,指向新页面;然后为这个链接添加事件处理函数,在链接被点击的时候检查是否支持XMLHttpReqest;如果支持,则加载新数据并阻止链接的默认行为。

XMLHttpReqest获得的数据加载完成,替换了页面的某些内容后,就可以销毁请求对象,以允许垃圾回收释放内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
document.getElementById('nextlink').onclick = function() {
// 判断是否支持XMLHttpReqeust
if(!window.XMLHttpRequest) {
return true;
}
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if( request.readyState != 4 ) {
return;
}
var useResponse = request.responseText.replace(/^[\w\W]*<div id="container">|<\/div>\s*<\/body>[\w\W]*$/g , '');
document.getElementById('container').innerHTML = useResponse;
// 销毁对象
request.onreadystatechange = null;
request = null;
};
request.open('GET', this.href, true);
request.send(null);
return false;
}

动态创建script元素

最好不要加载当前页面不使用的脚本,可以通过动态加载脚本的方式,在实际用到的时候才创建脚本元素。

理论上,在页面加载完成之后,可以通过script元素来加载额外的脚本并通过DOM添加到文档中,但是实际上可能是在浏览器上请求而不是立即加载脚本。
另外,记得用转义斜杠以免过早结果当前脚本:<\/script>

具体动态加载方式再开坑

location.replace()控制历史记录

有时我们需要使用脚本来改变页面地址。最典型的做法是给location.href赋予一个新地地址。但这样做会添加一个历史记录,同时加载一个新的页面,这和激活一个普通的链接一样。

在某些情况下,并不希望出现一条额外的历史记录,因为用户不需要回到之前的页面。如果在内存特别重要的环境下,这样做就非常有用。
当前页面使用的内存可以通过替换历史记录来得到重新利用,使用location.replace('newpage.html')方法就可以做到。
但请注意,该页可能仍然保留在缓存中,并可能在那里使用内存,但不会用到像保存在历史记录里那么多。

最后的感慨:当初简单学习了一下js,就用jquery库了,文中很多关于DOM的操作都是没有接触过的,看的时候往往理解不能很深;推及到自己实际使用吧,又因为jquery的集成不能得知其中详细,感觉很是不痛快,要补!

致谢:
(高效的JavaScript)[http://www.zcfy.cc/article/dev-opera-efficient-javascript-2320.html]