本文主要收集平常使用到的一些JavaScript Codes和Snippets。
className
id
属性className
function foo() {
var id = 0;
return function () {
return id++;
}
}
var makeid = foo();
var i = makeid();
var j = makeid();
在函数foo
中,id
实际上是私有成员,我们通过且仅能通过闭包函数makeid()
访问它。
私有成员:私有Private成员要由构造器生成。构造器中的普通的var变量和参数都成为私有成员。
function Container(param) {
this.member = param;
var secret = 3;
var self = this;
}
这个构造器有三个私有实例变量:param
, secret
, 和self
,它们被附加到了对象上,但它们无法从外部访问,同时它们也无法被这个对象的公共方法所访问。他们只对私有方法和特权方法可见。私有方法则是构造器内部的函数,而特权方法是用this
在构造器中分配的。
如果想了解更多关于私有成员的实现请参考Private properties in JavaScript.
function foo() {
var req = new XMLHttpRequest();
req.onreadystatechange = function () { /*此处创建闭包*/
if (req.readyState == 4) {
...
}
}
}
尽管onreadystatechange
是req
的属性或是方法,但是它并不是由req
触发的。因为它需要访问它自己,所以解决方案可以是使用一个
全局变量(如window.req)或是使用闭包让这些变量的值始终保存在内存中,让它不至于在foo()
函数执行完毕就被GC
回收。
getClickHandler: function () {
var me = this;
return function(e) {
me.showTip();
...
}
}
以上编程模式,是经常会使用到的其中一种闭包写法。
闭包是指在建立函数时,绑定了当时作用域下的有效的自由变量。基于此,首先我们给闭包下个定义:
闭包就是能够读取其他函数内部变量的函数。
闭包最大用处有两个:
那么什么是自由变量呢?
自由变量是相对于函数而言,既不是本地变量也不是参数的变量,它的作用范围基本上是在函数定义的范围中。举个例子:
function Robot() {
var createTime = new Date();
this.getAge = function () { // 这里建立了Closure
var now = new Date();
var age = now - createTime;
return age;
}
}
var robot = new Robot();
alert(robot.getAge());
单单看getAge
函数的话,createTime
并没有多大的意义,对getAge
而言,createTime
是一个自由变量,同时它也是Robot
函数的本地私有成员变量,但是在getAge
所引用的函数中被关闭了,从而createTime
的生命周期得以延续,即使它是使用var
声明的,只要getAge
中所引用的Function
对象还存在,createTime
自由变量也就能继续存活。
关于闭包,Crockford曾经说过:
一个内部的函数总是可以访问这个函数外部的变量和参数,甚至在外部的函数返回之后。
var myAlerts = [];
for (var i = 0; i < 2; i++) {
myAlerts.push(
function inner() {
alert(i);
});
}
myAlerts[0](); // 2
myAlerts[1](); // 2
第一眼看过去,JavaScript新手会认为alert(i)
会弹出inner
函数定义时逐步增长的i
值,也就是分别显示0
,1
。这是我们最常犯的一种错误。inner
函数是在全局上下文中创建的,从而它的scope chain
是静态地绑定到全局上下的。当调用inner
函数时,它会去找寻标识符i
,而标识符的搜寻是沿着scope chain
来的。所以它会去inner
的scope chain
上搜救变量i
,但是,在调用inner
函数的时候,变量i
的值已经变为2
,所以每次调用inner
函数的结果都会是一样的。
JavaScript的一个很重要的特性就是它的解析器使用的是Lexical Scoping
,意味着所以内部函数都是静态地绑定到创建它们的父上下文中的。闭包只能取得包含函数中任何变量的最后一个值。也就是说,闭包所保存的是整个变量对象,而不是某个特殊的变量。
上面的例子中,所有的myAlerts
中保存的函数中能会引用到2
,也就是被关闭的变量i
的最后一个值(循环的计数器i
在循环中的内部函数中被关闭了,它的生命周期得以延续,即使它是使用var声明的,只要myAlerts
中所引用的Function
对象还存在,i
也就能继续存活)。
但是我们通过使用闭包保存变量所在的作用域解决。
var myAlerts = [];
for (var i = 0; i < 2; i++) {
myAlerts.push(
(function inner(i) {
return function () {
alert(i);
}
})(i));
}
myAlerts[0](); // 0
myAlerts[1](); // 1
var obj = new function() {
/* stuff */
};
var obj = {
/* stuff */
};
Object.create
是ECMAScript 5引入的新方法,它是new
的一个变体,它可以让你基于一个原型对象来创建一个新的对象。
var prototypeDef = {
protoBar: "protoBar",
protoLog: function () {
console.log(this.protoBar);
}
};
var propertiesDef = {
instanceBar: {
value: "instanceBar"
},
instanceLog: {
value: function () {
console.log(this.instanceBar);
}
}
}
var foo = Object.create(prototypeDef, propertiesDef);
foo.protoLog(); // logs "protoBar"
foo.instanceLog(); //logs "instanceBar"
这里有几点需要注意:
Object.creaet
中第二个参数properties
中的值会覆盖第一个参数prototype
中的同名属性Object.creaet
中第二个参数properties
的类型是数组或是对象,那么通过Object.create
创建的对象都会共享它isPrototypeOf
来检查对象的prototype
对象而不是instanceOf
我们可以解决第二个问题,首先初始化propertyArrayValue_
为null
,然后当你添加元素时才初始化该数组。
var prototypeDef = {
protoArray: [],
};
var propertiesDef = {
propertyArrayValue_: {
value: null,
writable: true
},
propertyArray: {
get: function () {
if (!this.propertyArrayValue_) {
this.propertyArrayValue_ = [];
}
return this.propertyArrayValue_;
}
}
};
var foo = Object.create(prototypeDef, propertiesDef);
var bar = Object.create(prototypeDef, propertiesDef);
foo.protoArray.push("foobar");
console.log(bar.protoArray); //logs ["foobar"]
foo.propertyArray.push("foobar");
console.log(bar.propertyArray); //logs []
function MyObj() {}
MyObj.prototype = {
foo: function () {
alert(this);
},
...etc..
};
var my1 = new MyObj();
my1.foo();
T.dom.toggle = function (element) {
element = T.dom.g(element);
element.style.display = element.style.display == "none" ? "" : "none";
return element;
};
使用display:''
,与之相对的就是display:block
。
设置elem.style.display = ''
仅仅设置或是移除了内联样式。如果用户自定义的样式存在并且已经应用到了该元素上,那么一旦内联样式被移除了,该元素仍然会使用自定义样式来渲染。(这是因为elem.style.display
已经移除了高优先级的内联样式,但是自定义的样式并没有受到影响,仍然会起作用。)因此,
style.display=...
改变目标元素的显示或隐藏状态时,不要同时使用非内联样式和内联样式(只使用内联样式)className
样式或是同时使用内联/非内联样式,可以通过className
以及display
属性来完成elem.style.display = ''
这种形式,而使用elem.style.display = 'none'|'block'
的这种形式//inline style
elem.style.xxx = ...
//classname
elem.className = ...
修改内联样式或是修改className
即可。
className
var elem = $('#id');
elem.className = 'foo';
使用className
,而不是class
,那是因为在ECMAScript 5中class是一个Future Reserved Word,而在ECMAScript 6中class
已经是一个关键词了。
事实上,并不存在DOM Level 0标准,它只存在于DOM的历史长河中。DOM Level 0通常被认为是Internet Explorer 4.0 and Netscape Navigator 4.0中所支持的
DHTML
。
DOM Level 0中的事件处理模型由Netscape Navigator引入,有二种主要的类型:
在内联事件模型中,事件处理器是作为HTML元素的属性添加的。如下所示:
<p>Hey <a href="http://www.example.com" onclick="triggerAlert('Joe'); return false;">Joe!</p>
如果想了解更多,请参考这里。
在传统事件模型中,事件处理器是通过JavaScript脚本添加或删除的。与内联模型相同的是,每一个事件一次只能绑定一个事件处理器。
element.onclick = doSomething; //添加事件处理器
element.onclick = null; // 移除事件处理器
如果想了解更多,请参考这里。
id
属性在使用id
属性时,文档中所有的元素必须都有唯一的id
。对于多个元素使用相同的id,在IE中会导致各种种样的问题。
className
var table = document.getElementById('myTable');
var rows = table.getElementsByTagName('tr');
for (var i = 0; i < rows.length; i++) {
if (i % 2) {
rows[i].className += " even";
}
}
在上面的代码中,展示了给一个table添加class以实现斑马线的效果。对于onmouseover/onmouseout
的JS事件处理函数也可以通过这种方式添加。
$('foo').related = $('bar');
给DOM对象添加指向其它相关联的DOM对象的属性,这种方式不会导致IE中的内存溢出,那是因为所有的操作都是在DOM
内完成的。
更多请参考IE的Memory Leak。
首先有个问题:
浏览器是如何渲染一个Web页面的?
从整体上来看,下面是浏览器渲染引擎在取得HTML内容之后的基本流程:
解析HTML以构建DOM树 -> 构建Render树 -> 布局Render树 -> 绘制Render树
Renderer
或Render object
,而Gecko叫frame
),但是Render树并不会包含一些不可见的DOM元素(包含<head>
元素或是拥有display:none
样式的元素)。每个Renderer
都包含与之对应的DOM
对象和计算完成的样式信息(如颜色,大小等等),它们将按照正确的顺序显示到屏幕上Renderer
,还需要计算它在屏幕上的确切坐标,这一步叫做Layout
Painting
,即遍历Render树,并使用UI后端层在浏览器窗口中绘制每个节点修改元素样式(如background-color
, border-color
, visibility
)时,如果并不影响元素在页面中的位置,那么浏览器只是会使用新的样式信息重绘该元素。
除了重绘外,另外一些改变会影响文档的内容,结构或是元素位置,那么就会发生重绘(Webkit叫Layout,Gecko叫Reflow)。通常如下的一些修改会触发重新定位或回流:
display:none
),移动元素或是修改元素出现顺序:hover
, :checked
, :first-child
等等看一个简单来自Google Speedtracer的例子:
// This line invalidates layout.
elementA.className = 'foo';
// This line requires layout to be up to date.
var aWidth = elementA.offsetWidth;
// Invalidates layout again.
elementB.className = 'bar';
// Requires layout to be up to date
var bWidth = elementB.offsetWidth;
通常来说,浏览器会尽可能地把重绘限制在更新的元素的那块区域。比如某个元素的重绘只会影响它后面的元素。 所以,我们可以通过批量读取和批量设置来解决。
// This line invalidates layout.
elementA.className = 'foo';
elementB.className = 'bar';
// This line requires layout to be up to date.
var aWidth = elementA.offsetWidth
// Second Layout pass not needed.
var bWidth = elementB.offsetWidth;
参考:
我们可以通过x.style
来访问内联样式,如下所示。另外,内联样式会覆盖掉其它的样式,除非使用!important
来提高指定样式规则的权重。
element.style.color
color
只在下列两种情况下是有效的:
element
的内联样式定义上设置的element.style.color = 'red';
否则,color
都是无效的。
<a href='javascript:showImg()'>
<img title='my image' name='sunset'>
比如,在一个简单的slide展示中,鼠标滑过一个链接时,需要根据一些信息在图片显示区域展示相应的一张图片。那么img标签的title和name属性就可以帮你很容易的达到目的。 当然,其它一些自定义的属性也可以使用的。
另外一个例子是:
<input type=text pattern='^\w+$' required=true />
页面加载时,JS代码可以遍历所有的表单字段,并且如果pattern和required属性存在的话,那么就添加一个validate函数,该函数主要负责在表单提交前,根据正则表达式来验证文本框中的内容。
---|
-----|
--------|
-----------|
一个进度条的简单动画实现:我们可以通过逐渐增加div
或img
的宽度来实现。
function progress() {
var img = $('img');
if(img.width < 200) {
img.width += 5;
// Don't autosize height along with width
img.height = 5;
} else {
img.width = origwidth;
}
}
setInterval('progress()', 500);
淡入/淡出效果一般用于从页面中添加/删除某个元素时。
function fade(el) {
var b = 155;
var el = $('div');
function f() {
el.style.background = 'rgb(255,255,' + (b += 4) + ')';
if (b < 255) {
setTimeout(f, 40);
}
};
f();
}
fade();
更多的可以参考这里。
document.write("< img src='foo.jpg?" + Math.random() + "' />");
我们可以修改URL,添加了一个伪随机数,这是一种常用的手段,常常用于禁止浏览器缓存图片。
更多的信息请参考quirksmode。
DIV
元素节点document.createDocumentFragment():创建一个空的DOM文档碎片。我们可以利用这个API,先创建一个DOM文档碎片,然后在它上面添加新的元素节点,之后将它作为一个整体插入DOM中,以减少Layout/Reflow.
getElementsByTagName()
方法,它将返回包含文档中所有子元素的集合,元素排列的顺序就是它们在文档中的顺序element
中带有指定标签名的元素对象的集合我们常用的一些JavaScript库,如jQuery, Prototype等等,已经实现了通过CSS2/CSS3选择器返回元素集合,所以除了这些原生的方法外,我们还可以有更多的选择。
`nodeType’包含一个表示元素类型的数字。我们经常用到的如下所示:
对文本节点来说,nodeValue
表示真正的文本。value of text for a Text node (useful for #text). Null for most other nodes (including element nodes) devedge link
<p>I am a JavaScript hacker.</p>
var x = [the paragraph];
var text = x.firstChild.nodeValue;
而对于属性节点来说,nodeValue
则表示的是属性值。
除此之外,对于document
和其它元素节点来说,它会返回null。
nodeName
是最有用的一个属性。与它的名字一样,它包含了节点的名字。元素节点的名字始终与标签名相同,属性节点的名字始终与属性名字相同,文本节点的名字始终是#text
,而文档节点的名字始终是#document
。
只是有一点我们需要注意:对于HTML元素节点来说,不管你在HTML中写的是大写或是小写,nodeName
都会返回大写的标签名。
返回节点上你需要查询的属性的值。
#### node.setAttribute(name, value)
设置节点上某个指定的属性的新的值。
<img src='sample.png' id='test' />
var imgEl = document.getElementById('test');
alert(imgEl.getAttribute('src'));
imgEl.setAttribute('src', 'sample.png');
var node = ..some_element..
node.parentNode.removeChild(node);
var newItem = document.getElementsByTagName('p')[0];
var existingItem = document.getElementsByTagName('h1')[0];
newItem.parentNode.insertBefore(newItem, existingItem);
insertBefore()
返回的是被插入节点的一个引用。
var x = y.insertBefore(newItem, existingItem);
那么现在x
就包含对newItem
的一个引用。
var node = ...
var newNode = node.cloneNode(true|false); // true=deep copy, else shallow
需要注意的是,克隆节点时,并不会同时克隆事件处理函数。
replaceChild()
方法可以允许你将一个节点替换为另外一个节点。如果被插入的节点已经在DOM中了,那么它首先会从当前的DOM中位置移除掉。并且插入的节点和被替换的节点都会保持它们的所有子节点不变。
var newNode = document.getElementsByTagName('h1')[0];
var oldNode = document.getElementsByTagName('p')[1];
newNode.parentNode.replaceChild(newNode, oldNode);
replaceChild()
返回的是被替换的节点的一个引用。
var x = y.replaceChild(newNode, oldNode);
那么现在x
就包含对oldNode
的一个引用。