对象
JavaScript对象只是简单的名值对(键值对)的集合。正因如此,它们和下面的是类似的:
Python中的字典
Perl和ruby中的Hashes
C和c++中的hashtables
Java中的HashMaps
PHP中的关联数组
(perl、ruby、c、c++和java中的相似结构翻译出来都是哈希表,但在不同不语言中它们有不同的意义,这里就不翻译了,学过的应该一眼就能看出来)
这个数据结构被如此广泛的使用,已经说明了它功能的多样性。由于 JavaScript 中一切都是对象(核心类型除外),任何 JavaScript 程序自然都会涉及到大数据量哈希表的查询。太好了,它们都非常快。“name”部分是一个JavaScript字符串,”value”部分可以是任意的JavaScript值-包括更多对象。这样就允许你构建任意复杂的数据结构了。
有两个基本的方式创建一个空的对象:
var obj = new Object();
另一种是
var obj = {};
它们在语义上是等价的;第二种方式被称作对象文本特性,它使用起来更加方便。这个特性也是JSON格式的核心,并且应该始终优先使用。
对象创建之后,对象的属性也可以通过下面两种方式之一来访问obj.name = "Simon";
var name = obj.name;
obj["name"] = "Simon";
var name = obj["name"];
这同样是语义上等价的,第二种方式更有优势一些,因为属性名是作为一个字符串被传递的,意味着它可以在运行时被计算出来的,然而,这个方式也会阻止JavaScript使用一些引擎和优化。这种方式也可以用在当属性名是保留字的时候获取和设置属性值
obj.for = "Simon"; // Syntax error, because 'for' is a reserved word
obj["for"] = "Simon"; // works fine
对象文本特性可以被用来初始化整个对象(如下这种稍微复杂一点的初始化方式):
var obj = {
name: "Carrot",
"for": "Max",
details: {
color: "orange",
size: 12
}
}
通过链式方式来访问属性:
> obj.details.color
orange
> obj["details"]["size"]
12
数组
JavaScript中的Array实际上是一个特殊类型的对象。它们表现方式和常规对象很是相像(数字属性实际上可以通过[]方式来访问)。但是它们有一个神奇的属性,length。这个值总比数组的最大的索引值大1.
以前创建数组的方式如下:
> var a = new Array();
> a[0] = "dog";
> a[1] = "cat";
> a[2] = "hen";
> a.length
3
一种更方便的方式是使用一个数组直接量:
> var a = ["dog", "cat", "hen"];
> a.length
3
在数组文本的最后留一个逗号在不同的浏览器上会有不同的导致不同的结果,所以千万别这么做。
注意 array.length 并不一定是这个数组中所有项的总数,想想下面这个例子:> var a = ["dog", "cat", "hen"];
> a[100] = "fox";
> a.length
101
记住-数组的长度是比最高位索引值大1.
如果你查询一个不存在的数组索引,返回的是undefined:
> typeof a[90]
undefined
为防止上面这种情况,可以使用如下方式遍历一个数组:
for (var i = 0; i < a.length; i++) {
// Do something with a[i]
}
这种方式的效率稍微有些低,因为每次循环都会去查询
length
属性。下面是一种改进方式
for (var i = 0, len = a.length; i < len; i++) {
// Do something with a[i]
}
一个更好的方式是:
for (var i = 0, item; item = a[i++];) {
// Do something with item
}
这里我们设置了两个变量, for循环中间的赋值语句也是在测试是否为真值---如果是,则循环继续。由于i每次都自增长,数组中的项会依次被赋值给item。当遇到一个字面值为假值的item时,循环结束。
注意,这个小窍门只能适用于那些数据项中不含有假值的数组(比如对象数组或者DOM节点),如果遍历一个可能含有0的数字数组,或者一个可能含有空串的字符串数组,则需要使用i、len的方式。
另一种遍历的方式是用for..in循环。注意如果(在遍历过程中)有人往Array.prototype里面添加新的属性,这些新的属性也会被遍历到。
for (var i in a) {
// Do something with a[i]
}
如果你想往数组中添加一个元素,最安全的做法如下
a[a.length] = item;
由于a.length是比最大索引大1,你可以保证是在数组的尾部的空位上进行赋值。
数组还提供许多其他方法:
Method name | Description |
a.toString() |
|
a.toLocaleString() |
|
a.concat(item[, itemN]) | Returns a new array with the items added on to it. |
a.join(sep) |
|
a.pop() | Removes and returns the last item. |
a.push(item[, itemN]) | Push adds one or more items to the end. |
a.reverse() |
|
a.shift() |
|
a.slice(start, end) | Returns a sub-array. |
a.sort([cmpfn]) | Takes an optional comparison function. |
a.splice(start, delcount[, itemN]) | Lets you modify an array by deleting a section and replacing it with more items. |
a.unshift([item]) | Prepends items to the start of the array. |
函数
和对象一样,函数是理解JavaScript的核心组件。最基本最简单的函数形式如下:
function add(x, y) {
var total = x + y;
return total;
}
这个例子演示了了解基本函数需要的所有方面,一个JavaScript函数可以传入0个或者多个参数。函数体包含任意数量的语句,也可以在函数体中定义作用域在函数内的变量。Return语句可以用在任意地方,用来返回一个值,并结束函数。如果没有使用return语句,或者return后面没有给定一个值,JavaScript会返回undefined。
命名参数更像是一个指引。你可以不传入预期的参数而去调用一个函数,这种情况下,参数被赋值为undefined。
> add()
NaN // You can't perform addition on undefined
你也可以传入比函数期望多的参数:
> add(2, 3, 4)
5 // added the first two; 4 was ignored
这个看起来有点傻,但是函数可以在内部通过arguments访问附加的变量,arguments是一个数组对象,里面存放了传递给这个函数的所有参数,下面我们重写add方法来处理尽可能多的参数。
function add() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum;
}
> add(2, 3, 4, 5)
14
然而,这样写确实不比写2+3+4+5更有用。接下来我们实现一个求平均值的方法:
function avg() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum / arguments.length;
}
> avg(2, 3, 4, 5)
3.5
这样就相当有用了,但是引入了一个新的问题,avg函数处理一个逗号分隔的参数列表---但如果你想要求一个数组的平均值怎么办?只需要像下面一样重写方法就可以了:
function avgArray(arr) {
var sum = 0;
for (var i = 0, j = arr.length; i < j; i++) {
sum += arr[i];
}
return sum / arr.length;
}
> avgArray([2, 3, 4, 5])
3.5
如果能够重用我们已经写好的函数就好了。幸运的是,JavaScript允许你调用一个方法并且传入任意的参数数组,通过使用任意函数对象来执行apply函数。
> avg.apply(null, [2, 3, 4, 5])
3.5
Apply函数的第二个参数就是作为参数的数组,第一个参数稍后再讨论。这里强调一个事实,函数也是对象。
JavaScript允许创建匿名函数:
var avg = function() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum / arguments.length;
}
这个语义上等价于函数avg的格式。它是非常强大的,因为它允许你把一个完整的函数定义放在任意可以放置表达式的地方。这样就可以实现各种各样的高明的技巧。下面是一种隐藏本地变量的方式,就像c语言中的作用域。
> var a = 1;
> var b = 2;
> (function() {
var b = 3;
a += b;
})();
> a
4
> b
2
JavaScript允许递归调用。这个对处理树结构特别有用,比如你在浏览器DOM中得到的数据。
function countChars(elm) {
if (elm.nodeType == 3) { // TEXT_NODE
return elm.nodeValue.length;
}
var count = 0;
for (var i = 0, child; child = elm.childNodes[i]; i++) {
count += countChars(child);
}
return count;
}
这里强调匿名函数有一个潜在的问题:如果匿名函数没有名字,那怎么递归调用它们呢?答案取决于arguments对象,它除了表示参数列表外,还提供一个叫做arguments.callee的属性,arguments.callee是不赞成使用的,在严格模式下甚至不准使用。相反,你应该使用命名的匿名函数,也叫做IIFEs直接调用方法表达式,看下面的例子:
var charsInBody = (function counter(elm) {
if (elm.nodeType == 3) { // TEXT_NODE
return elm.nodeValue.length;
}
var count = 0;
for (var i = 0, child; child = elm.childNodes[i]; i++) {
count += counter(child);
}
return count;
})(document.body);
像上面给匿名函数一个名字,这个名字(或者至少应该是)仅仅在函数自己的作用域内有效。这样既允许浏览器引擎做更多的优化,也使代码更具有可读性