动态插入 DOM 元素的最佳方式

假设有一个 DOM 元素,要利用 JavaScript 动态插入到文档中,通常这样写:

document.body.appendChild(elem);

这是最常用的写法,我经常都是这么写的,但同时我也发现有很多不同的写法,比如插入到 <head> 中 ,或插入到第一个 <script> 前面,还有插入到 document.documentElement.firstChild 的。

Paul Irish 提供了一个最佳实践,写法是这样的:

var ref = document.getElementsByTagName('script')[0];
ref.parentNode.insertBefore(yourElem, ref);

可以看出用的是第二种写法。那其他方法有什么缺点呢?

首先说 append 到 <body> 中的方法,大多数情况下,这种方法很有效,但如果是用来写类库,或者组件的话,需要考虑到各种各样的环境。如果代码被放进 <head> 里,那么执行的时候,document.body 的值是 null。于是就出错了。

下一个思路便是插进 <head> 里,问题是有些网页里缺少 <head> 元素,一般来说,浏览器会自动添加一个缺失的 <head>,但在 Opera、Safari 等的一些旧版本里不添加,所以仍会有出错的情况。
阅读全文 »

JavaScript 位操作符

位操作符即按内存中表示数值的位来操作数值。默认情况下,JavaScript 中的所有整数都是有符号整数。

如果对非数值应用位操作符,会先使用 Number() 函数将该值转换为一个数值,然后再应用位操作。

按位非(NOT)

~ 表示,结果返回数值的反码,也就是操作数的负值减 1。

var num = ~3;
alert(num);  //-4

按位与(AND)

& 表示。有两个操作数,按位与操作就是将两个数值的每一位对齐,然后在相同位置上执行 AND 操作。AND 操作只有两个对应位都是 1 时返回 1,否则返回 0。

var num = 25 & 3;
alert(num); //1

按位或(OR)

| 表示,有两个操作数,在每一位上执行 OR 操作。OR 操作当两数都是 0 时返回 0,其他情况下返回 1。

var num = 25 | 3;
alert(num); //27

按位异或(XOR)

^ 表示,也有两个操作数,在每一位上执行异或操作。异或的规则是,两数不同时返回 1,相同时返回 0。

var num = 25 ^ 3;
alert(num); //26

阅读全文 »

用 JavaScript 选择文本框文本

要选中文本框内的文本,最常用的方法是 select()<input><textarea> 元素都支持,此时文本框会获得焦点,并且文本全部被选中。如果要选择部分的文本,则可以用下面的办法。

现代浏览器

在现代浏览器中 <input><textarea> 支持一个 setSelectionRange 方法和两个属性:selectionStartselectionEnd,都可以用来选中文本。setSelectionRange 接受两个值:选区起点和选区终点的后一个位置,用法类似字符串的 slice

selectionStartselectionEnd 属性始终保存了选区的起点和终点索引,它们既可读也可写,分别设置这两个值以后,效果跟调用了 setSelectionRange 是一样的。

如果设置的起点和终点值相同,则会把光标移动到相应位置。

支持此方法的浏览器有 IE9+、Chrome、Firefox、Opera、Safari。

IE6-8

IE6-8 不支持上面的方法和属性,需要用 createTextRange 方法创建一个范围,代码如下:

var field = document.getElementsByTagName('input')[0];  // input 元素
var range = field.createTextRange();  // 创建范围
range.collapse(true);  // 折叠范围
range.moveStart("character", start);  // 移动起点
range.moveEnd("character", end);  // 移动终点
range.select();  // 选择文本

阅读全文 »

JavaScript 浮点运算的误差问题

JavaScript 中,有浮点数值参与运算的时候,经常会产生误差。比如你测试 0.1 + 0.2 == 0.3,会得到 false。这是因为 JavaScript 采用了 IEEE 754 标准定义的格式来存储数字,采用了这种格式的编程语言都会存在这个问题,并不是 JavaScript 特有。

比如计算 0.1 + 0.2,你其实会得到 0.30000000000000004。所以在 JavaScript 中有浮点数参与运算时,应该尽量避免 0.1 + 0.2 == 0.3 这样的测试。

要解决这个问题,一种方法就是利用 toFixedtoFixed 是 Number 类型的一个原型方法,可以用字符串表示数字,同时指定小数位数。传入的参数可以是 0 到 20 间的整数,表示小数部分的长度。

(0.1).toFixed(4); // "0.1000"

toFixed 方法会自动进行四舍五入以符合指定的小数位数,因此正好可以用来解决浮点运算的误差问题。首先对数值调用 toFixed 方法,限制小数部分长度,比如 10,此时误差基本已经修正,然后再用 parseFloat 方法或加号操作符转换回数字。示例:

(0.1 + 0.2).toFixed(10); // "0.3000000000"
parseFloat((0.1 + 0.2).toFixed(10)); // 得到 0.3
+((0.1 + 0.2).toFixed(10)); // 功能同上
+((0.1 + 0.2).toFixed(10)) == 0.3 // 返回 true

JavaScript 闭包引起的内存泄漏(IE)

在 IE8 以及更早的版本中,由于两种垃圾收集算法并存,所以会发生循环引用的问题,引发内存泄漏,例如下面的代码:

function assignHandler() {
    var element = document.getElementById('someElement');
    element.onclick = function() {
        alert(element.id);
    };
}

以上代码中创建了一个匿名函数(闭包),并赋给 element 作为事件处理程序,因此 element 引用了这个匿名函数,而匿名函数中又引用了 element 对象,导致了循环引用,这两个对象被引用次数永远无法减少到 0,而由于老版本 IE 对 BOM 和 DOM 对象都采用引用计数的垃圾收集机制,所以这两个对象占用的内存就永远得不到回收。

可以通过改变代码的写法解决这个问题:
阅读全文 »

七个 JavaScript 基本技巧

本文翻译自Tech.Pro

JavaScript 本质上是门简单的语言,而通过一些设计模式来得到进化,我们也经常将这些模式融入到 JavaScript 框架中来加速 Web 应用的开发。许多刚入门的开发者却太依赖框架,忽视了一些非常有用的 JavaScript 技巧,在此列出 7 个技巧,它们非常适合用来处理一些基本任务:

1.String.prototype.replace: /g/i 标志

许多 JavaScript 新手都很纳闷为什么字符串的 replace 方法不替换所有匹配的子串,而是只替换第一个。当然,有经验的人知道这里应该用一个正则表达式并加上全局标志(/g):

// 错误
var str = "David is an Arsenal fan, which means David is great";
str.replace("David", "Darren"); // "Darren is an Arsenal fan, which means David is great"
// 正确
str.replace(/David/g, "Darren"); // "Darren is an Arsenal fan, which means Darren is great"

另外一个错误就是当大小写都要匹配的时候没有加上 /i 标志:

str.replace(/david/gi, "Darren") // "Darren will always be an Arsenal fan, which means Darren will always be great"

每一个开发者都在这里犯过错——所以要学会这两个标志!

2.类数组对象与 Array.prototype.slice

数组的 slice 方法主要是用来从一个数组中截取一段下了,而许多人没有注意到它也可以用来将类数组的对象比如 argumentsNodeList 等对象转换为真的数组:

var nodesArr = Array.prototype.slice.call(document.querySelectorAll("div")); // 所有 DIV 元素组成的数组
var argsArr = Array.prototype.slice.call(arguments); // 把 arguments 对象转换为真正的数组
// 注:这个技巧不适合 IE8 以及之前的版本,会报错,因为它们的 NodeList 对象是利用 COM 对象实现的,不是真正的 JavaScript 对象

你也可以调用 slice 来克隆一个对象:

var clone = myArray.slice(0);

Array.prototype.slice 在 JavaScript 中是一个不折不扣的好东东。
阅读全文 »

new Date 与 new Date()

今天在某个地方看到了 new Date 的写法,也就是创建对象时,省略了后面的一对括号,这种写法不太常见,我也很疑惑,到底跟 new Date() 有没有区别?于是就去研究了一下。

答案就是:没有区别,这是 new 运算符的一种特殊情况,如果创建对象时不需要传递参数,那么括号就可以省略。不过 JSLint 遇到这种写法还是会报错的。

如果后面紧跟着就调用该对象的方法,那么就不同了:

var o = new Date().toString();
var p = new Date.toString();

后一种写法会报错,因为 Date.toString 被当成了构造函数的名字,而它不是个构造函数。

所以老这么写也是不太安全的,我觉得平时还是尽量加上括号比较好一点,就像别人所说,这只是一个语法糖(syntactic sugar)而已。

参考:Stack Overflow

为什么 10.toString() 会报错?

在 JavaScript 里,Number 类型有很多方法,包括 toString()toFixed() 等等,通常调用时都会先定义一个变量,例如

var num = 10;
num.toString();  // "10"

如果你尝试直接在一个数字字面量上调用,比如 10.toString(),就会抛出一个异常:“SyntaxError: identifier starts immediately after numeric literal”。以前我一直以为是数字字面量不能调用方法,也没有去深究,今天正好做一套测试题的时候发现了 10..toString() 这种写法,就去查了一下。(这套JavaScript测试题挺古怪的,大家也可以去试试,考的都是平时不会用到的写法, 但是可以加深在某些方面的理解。)
阅读全文 »

JavaScript 中的“变量声明提升”

本文翻译自Nettuts+

什么是提升(hoisting)?

假设有如下的代码:

var myvar = 'my value';
alert(myvar); // my value

显而易见,弹出窗口会显示 “my value” 。我们再建立一个立即执行的函数,显示同样内容:

var myvar = 'my value';
(function() {
  alert(myvar); // my value
})();

结果也很明显,现在,我们在这个匿名函数内建立一个同名的局部变量:

var myvar = 'my value';
(function() {
  alert(myvar); // undefined
  var myvar = 'local value';
})();

现在,弹出窗口为什么显示 undefined?我们声明的局部变量在 alert 之后,为什么对结果产生了影响?
阅读全文 »

setTimeout(0) 的作用

大家都知道 JavaScript 中的 setTimeout() 可用来延迟执行一段代码,如:

setTimeout( function() {
  alert("Hello World");
}, 1000) //延时1秒

今天在网上看到了 setTimeout(fn, 0) 的用法,感到有些疑惑,不明白它和直接执行 fn() 有什么区别,遂搜集了一下相关资料,顺便分享下。

先看一段代码:

function a() {
  setTimeout( function(){
    alert(1)
  }, 0);
  alert(2);
}
a();

代码中的 setTimeout 设为 0,也就是延迟 0ms,看上去是不做任何延迟立刻执行,即依次弹出 “1”、“2”。但实际的执行结果确是 “2”、“1”。其中的原因得从 setTimeout 的原理说起:

JavaScript 是单线程执行的,也就是无法同时执行多段代码,当某一段代码正在执行的时候,所有后续的任务都必须等待,形成一个队列,一旦当前任务执行完毕,再从队列中取出下一个任务。这也常被称为 “阻塞式执行”。所以一次鼠标点击,或是计时器到达时间点,或是 Ajax 请求完成触发了回调函数,这些事件处理程序或回调函数都不会立即运行,而是立即排队,一旦线程有空闲就执行。假如当前 JavaScript 进程正在执行一段很耗时的代码,此时发生了一次鼠标点击,那么事件处理程序就被阻塞,用户也无法立即看到反馈,事件处理程序会被放入任务队列,直到前面的代码结束以后才会开始执行。如果代码中设定了一个 setTimeout,那么浏览器便会在合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入队列,但不是立即执行,仍然要等待前面代码执行完毕。所以 setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。

阅读全文 »

flight