本章介绍“基础 JavaScript”,这是我对 JavaScript 子集的命名,它尽可能简洁,同时又能让你高效工作。当你开始学习 JavaScript 时,我建议你先用它编程一段时间,然后再学习该语言的其余部分。这样,你就不必一次性学习所有内容,避免混淆。
本节介绍 JavaScript 的一些背景知识,帮助你理解其原因。
ECMAScript 是 JavaScript 的官方名称。之所以需要一个新名称,是因为 JavaScript 是一个商标(最初由 Sun 公司持有,现在由 Oracle 公司持有)。 目前,Mozilla 是少数几家被允许正式使用 JavaScript 名称的公司之一,因为它很久以前就获得了许可。对于一般用途,适用以下规则:
JavaScript 的创建者 Brendan Eich 别无选择,只能非常快地创建这门语言(否则 Netscape 就会采用其他更糟糕的技术)。他借鉴了几种编程语言:Java(语法、原始值与对象)、Scheme 和 AWK(一等函数)、Self(原型继承)以及 Perl 和 Python(字符串、数组和正则表达式)。
JavaScript 直到 ECMAScript 3 才支持异常 处理,这解释了为什么该语言经常自动转换值并且经常静默失败:它最初无法抛出异常。
一方面,JavaScript 有一些怪癖,并且缺少很多功能(块级作用域变量、模块、对子类化的支持等)。另一方面,它有几个强大的功能,可以让你解决这些问题。在其他语言中,你需要学习语言特性。而在 JavaScript 中,你通常学习的是模式。
考虑到它的影响,JavaScript 能够实现函数式编程(高阶函数;内置 map
、reduce
等)和面向对象编程(对象、继承)相结合的编程风格也就不足为奇了。
一些语法 示例:
// Two slashes start single-line comments
var
x
;
// declaring a variable
x
=
3
+
y
;
// assigning a value to the variable `x`
foo
(
x
,
y
);
// calling function `foo` with parameters `x` and `y`
obj
.
bar
(
3
);
// calling method `bar` of object `obj`
// A conditional statement
if
(
x
===
0
)
{
// Is `x` equal to zero?
x
=
123
;
}
// Defining function `baz` with parameters `a` and `b`
function
baz
(
a
,
b
)
{
return
a
+
b
;
}
请注意等号的两种不同用法:
=
) 用于为变量赋值。===
) 用于比较两个值(请参阅 相等运算符)。要理解 JavaScript 的语法, 你需要知道它有两个主要的语法类别:语句和表达式:
语句“做事情”。程序是由一系列语句组成的。下面是一个语句的例子,它声明(创建)了一个变量 foo
var
foo
;
表达式产生值。它们是函数参数、赋值的右侧等等。下面是一个表达式的例子
3
*
7
语句和表达式之间的区别可以用 JavaScript 有两种不同的方式来实现 if-then-else
来最好地说明——要么作为语句:
var
x
;
if
(
y
>=
0
)
{
x
=
y
;
}
else
{
x
=
-
y
;
}
要么作为表达式
var
x
=
y
>=
0
?
y
:
-
y
;
你可以将后者用作函数参数(但不能将前者用作函数参数)
myFunction
(
y
>=
0
?
y
:
-
y
)
最后,在 JavaScript 期望使用语句的地方,你也可以使用表达式;例如
foo
(
7
,
1
);
整行是一个语句(所谓的 表达式语句),但 函数调用 foo(7, 1)
是一个表达式。
在 JavaScript 中,分号是可选的。但是,我建议始终包含它们,因为否则 JavaScript 可能会错误地猜测语句的结尾。 详细信息请参阅 自动分号插入。
分号用于终止语句,但不用于终止代码块。有一种情况下,你会在代码块后面看到分号:函数表达式是一个以代码块结尾的表达式。 如果这样的表达式出现在语句的最后,则后面跟着一个分号:
// Pattern: var _ = ___;
var
x
=
3
*
7
;
var
f
=
function
()
{
};
// function expr. inside var decl.
JavaScript 有两种 注释:单行注释和多行注释。单行注释以 //
开头,并以行尾终止:
x
++
;
// single-line comment
多行注释 由 /*
和 */
分隔:
/* This is
a multiline
comment.
*/
JavaScript 中的变量在 使用之前声明:
var
foo
;
// declare variable `foo`
标识符 是在 JavaScript 中扮演各种语法角色的名称。 例如,变量的名称就是一个标识符。标识符区分大小写。
粗略地说,标识符的第一个字符可以是任何 Unicode 字母、美元符号 ($
) 或下划线 (_
)。后续字符还可以是任何 Unicode 数字。因此,以下都是合法的标识符
arg0
_tmp
$elem
π
以下标识符是 保留字——它们是语法的一部分,不能用作 变量名(包括函数名和参数名):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
以下三个标识符不是保留字,但你应该像对待保留字一样对待它们
|
|
|
最后,你还应该避免使用标准全局变量的名称(请参阅 第 23 章)。 你可以将它们用于局部变量而不会破坏任何内容,但你的代码仍然会变得混乱。
JavaScript 有许多我们期望编程语言拥有的值 :布尔值、数字、字符串、数组等等。 JavaScript 中的所有值都有 属性。[1] 每个属性都有一个 键(或 名称)和一个 值。你可以将属性视为记录的字段。你使用点 (.
) 运算符来读取属性
value
.
propKey
例如,字符串 'abc'
具有属性 length
> var str = 'abc'; > str.length 3
以上代码也可以写成
> 'abc'.length 3
点运算符还用于为 属性赋值:
> var obj = {}; // empty object > obj.foo = 123; // create property `foo`, set it to 123 123 > obj.foo 123
你还可以使用它来 调用方法:
> 'hello'.toUpperCase() 'HELLO'
在上面的例子中,我们调用了值 'hello'
上的方法 toUpperCase()
。
JavaScript 对值进行了一些武断的区分:
null
和 undefined
。两者之间的一个主要区别在于它们的比较方式; 每个对象都有一个唯一的标识,并且 只(严格)等于它自己:
> var obj1 = {}; // an empty object > var obj2 = {}; // another empty object > obj1 === obj2 false > obj1 === obj1 true
相反,所有编码相同值的原始值都被认为是相同的
> var prim1 = 123; > var prim2 = 123; > prim1 === prim2 true
接下来的两节将更详细地解释原始值和对象。
以下是所有原始值(或简称 基元):
true
、false
(请参阅 布尔值)1736
、1.351
(请参阅 数字)'abc'
、"abc"
(请参阅 字符串)undefined
、null
(请参阅 undefined 和 null)原始值具有以下 特点:
比较的是“内容”
> 3 === 3 true > 'abc' === 'abc' true
属性不能 更改、添加或删除:
> var str = 'abc'; > str.length = 1; // try to change property `length` > str.length // ⇒ no effect 3 > str.foo = 3; // try to create property `foo` > str.foo // ⇒ no effect, unknown property undefined
(读取未知属性始终返回 undefined
。)
所有非原始 值都是 对象。最常见的对象类型是:
普通对象,可以通过 对象字面量 创建(请参阅 单个对象)
{
firstName
:
'Jane'
,
lastName
:
'Doe'
}
数组,可以通过 数组字面量 创建(请参阅 数组)
[
'apple'
,
'banana'
,
'cherry'
]
上面的数组有三个元素,可以通过数字索引访问。例如,'apple'
的索引为 0。
正则表达式,可以通过 正则表达式字面量 创建(请参阅 正则表达式)
/^a+b+$/
对象具有以下 特点:
大多数编程语言都有表示缺失信息的值。JavaScript 有两个这样的“非值”,undefined
和 null
:
undefined
表示“无值”。未初始化的变量为 undefined
> var foo; > foo undefined
缺失的参数为 undefined
> function f(x) { return x } > f() undefined
如果读取不存在的属性,则会得到 undefined
> var obj = {}; // empty object > obj.foo undefined
null
表示“无对象”。每当预期出现对象时(参数、对象链中的最后一个等),它都被用作非值。undefined
和 null
没有属性,甚至没有标准方法,例如 toString()
。
函数通常允许您通过 undefined
或 null
来指示缺失值。您可以通过显式检查来执行相同的操作:
if
(
x
===
undefined
||
x
===
null
)
{
...
}
您还可以利用 undefined
和 null
都被视为 false
的事实
if
(
!
x
)
{
...
}
false
、0
、NaN
和 ''
也被视为 false
(请参阅真值和假值)。
有两个运算符用于对值进行分类:typeof
主要用于原始值,而 instanceof
用于对象。
typeof
如下所示
typeof
value
它返回一个描述 value
“类型”的字符串。以下是一些示例
> typeof true 'boolean' > typeof 'abc' 'string' > typeof {} // empty object literal 'object' > typeof [] // empty array literal 'object'
下表列出了 typeof
的所有结果
操作数 | 结果 |
|
|
|
|
布尔值 |
|
数字值 |
|
字符串值 |
|
函数 |
|
所有其他正常值 |
|
(引擎创建的值) | JavaScript 引擎允许创建 |
typeof null
返回 'object'
是一个无法修复的错误,因为它会破坏现有代码。这并不意味着 null
是一个对象。
instanceof
如下所示
value
instanceof
Constr
如果 value
是由构造函数 Constr
创建的对象,则返回 true
(请参阅构造函数:对象的工厂)。以下是一些示例
> var b = new Bar(); // object created by constructor Bar > b instanceof Bar true > {} instanceof Object true > [] instanceof Array true > [] instanceof Object // Array is a subconstructor of Object true > undefined instanceof Object false > null instanceof Object false
原始布尔类型包含值 true
和 false
。以下运算符产生布尔值:
&&
(与)、||
(或)!
(非)比较运算符
===
、!==
、==
、!=
>
、>=
、<
、<=
每当 JavaScript 需要布尔值时(例如,对于 if
语句的条件),都可以使用任何值。它将被解释为 true
或 false
。以下值被解释为 false
undefined
、null
false
0
、NaN
''
所有其他值(包括所有对象!)都被视为 true
。被解释为 false
的值称为假值,被解释为 true
的值称为真值。Boolean()
作为函数调用时,会将其参数转换为布尔值。您可以使用它来测试如何解释值
> Boolean(undefined) false > Boolean(0) false > Boolean(3) true > Boolean({}) // empty object true > Boolean([]) // empty array true
JavaScript 中的二元逻辑运算符是短路的。也就是说,如果第一个操作数足以确定结果,则不会评估第二个操作数。例如,在以下表达式中,永远不会调用函数 foo()
:
false
&&
foo
()
true
||
foo
()
此外,二元逻辑运算符返回其操作数之一,该操作数可能是也可能不是布尔值。使用真值检查来确定返回哪个操作数
&&
)> NaN && 'abc' NaN > 123 && 'abc' 'abc'
||
)> 'abc' || 123 'abc' > '' || 123 123
JavaScript 有两种相等性:
==
和 !=
===
和 !==
普通相等将(太多)值视为相等(详细信息在普通(宽松)相等 (==, !=) 中解释),这可能会隐藏错误。因此,建议始终使用严格相等。
JavaScript 中的所有数字都是浮点数:
> 1 === 1.0 true
特殊数字包括以下内容:
NaN
(“非数字”)错误值
> Number('xyz') // 'xyz' can’t be converted to a number NaN
Infinity
也主要是一个错误值:
> 3 / 0 Infinity > Math.pow(2, 1024) // number too large Infinity
Infinity
大于任何其他数字(NaN
除外)。类似地,-Infinity
小于任何其他数字(NaN
除外)。这使得这些数字可用作默认值(例如,当您正在寻找最小值或最大值时)。
JavaScript 具有以下算术运算符(请参阅算术运算符)
number1 + number2
number1 - number2
number1 * number2
number1 / number2
number1 % number2
++variable
、variable++
--variable
、variable--
-value
+value
全局对象 Math
(请参阅数学)通过函数提供更多算术运算。
JavaScript 还具有用于按位运算的运算符(例如,按位与;请参阅按位运算符)。
可以直接通过字符串字面量创建字符串。这些字面量用单引号或双引号分隔。反斜杠 (\
) 对字符进行转义并生成一些控制字符。以下是一些示例:
'abc'
"abc"
'Did she say "Hello"?'
"Did she say \"Hello\"?"
'That\'s nice!'
"That's nice!"
'Line 1\nLine 2'
// newline
'Backlash: \\'
> var str = 'abc'; > str[1] 'b'
属性 length
统计字符串中的字符数:
> 'abc'.length 3
与所有原始类型一样,字符串是不可变的;如果要更改现有字符串,则需要创建一个新字符串。
字符串通过加号 (+
) 运算符进行连接,如果其中一个操作数是字符串,则将另一个操作数转换为字符串:
> var messageCount = 3; > 'You have ' + messageCount + ' messages' 'You have 3 messages'
> var str = ''; > str += 'Multiple '; > str += 'pieces '; > str += 'are concatenated.'; > str 'Multiple pieces are concatenated.'
以下部分介绍 JavaScript 中的条件语句和循环。
if
语句有一个 then
子句和一个可选的 else
子句,它们根据布尔条件执行:
if
(
myvar
===
0
)
{
// then
}
if
(
myvar
===
0
)
{
// then
}
else
{
// else
}
if
(
myvar
===
0
)
{
// then
}
else
if
(
myvar
===
1
)
{
// else-if
}
else
if
(
myvar
===
2
)
{
// else-if
}
else
{
// else
}
我建议始终使用大括号(它们表示零个或多个语句的块)。但是,如果子句只是一个语句,则不必这样做(控制流语句 for
和 while
也是如此):
if
(
x
<
0
)
return
-
x
;
以下是一个 switch
语句。fruit
的值决定执行哪个 case
:
switch
(
fruit
)
{
case
'banana'
:
// ...
break
;
case
'apple'
:
// ...
break
;
default
:
// all other cases
// ...
}
case
后的“操作数”可以是任何表达式;它通过 ===
与 switch
的参数进行比较。
for
循环具有以下格式:
for
(
⟦«
init
»⟧
;
⟦«
condition
»⟧
;
⟦«
post_iteration
»⟧
)
«
statement
»
init
在循环开始时执行。condition
在每次循环迭代之前检查;如果它变为 false
,则循环终止。post_iteration
在每次循环迭代之后执行。
此示例在控制台上打印数组 arr
的所有元素
for
(
var
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
console
.
log
(
arr
[
i
]);
}
while
循环在其条件成立时继续循环执行其主体:
// Same as for loop above:
var
i
=
0
;
while
(
i
<
arr
.
length
)
{
console
.
log
(
arr
[
i
]);
i
++
;
}
do-while
循环在其条件成立时继续循环执行其主体。由于条件在主体之后,因此主体始终至少执行一次:
do
{
// ...
}
while
(
condition
);
在所有循环中:
break
退出循环。continue
开始新的循环迭代。定义函数的一种方法是通过函数声明
function
add
(
param1
,
param2
)
{
return
param1
+
param2
;
}
前面的代码定义了一个函数 add
,它有两个参数 param1
和 param2
,并返回两个参数的和。以下是调用该函数的方法
> add(6, 1) 7 > add('a', 'b') 'ab'
另一种定义 add()
的方法是将函数表达式分配给变量 add
:
var
add
=
function
(
param1
,
param2
)
{
return
param1
+
param2
;
};
函数表达式产生一个值,因此可以用来直接将函数作为参数传递给其他函数
someOtherFunction
(
function
(
p1
,
p2
)
{
...
});
函数声明被提升——整体移动到当前作用域的开头。这允许您引用稍后声明的函数:
function
foo
()
{
bar
();
// OK, bar is hoisted
function
bar
()
{
...
}
}
请注意,虽然 var
声明也被提升(请参阅变量被提升),但它们执行的赋值却没有
function
foo
()
{
bar
();
// Not OK, bar is still undefined
var
bar
=
function
()
{
// ...
};
}
您可以在 JavaScript 中使用任意数量的参数调用任何函数;该语言永远不会报错。但是,它会将 所有参数通过特殊变量 arguments
提供。 arguments
看起来像一个数组,但没有任何数组方法:
> function f() { return arguments } > var args = f('a', 'b', 'c'); > args.length 3 > args[0] // read element at index 0 'a'
让我们使用 以下函数来探讨 JavaScript 中如何处理参数过多或过少的情况(函数 toArray()
显示在 将参数转换为数组 中)
function
f
(
x
,
y
)
{
console
.
log
(
x
,
y
);
return
toArray
(
arguments
);
}
额外的参数将被忽略(arguments
除外)
> f('a', 'b', 'c') a b [ 'a', 'b', 'c' ]
缺少的参数将 获取值 undefined
:
> f('a') a undefined [ 'a' ] > f() undefined undefined []
以下是 为参数分配默认值的常见模式:
function
pair
(
x
,
y
)
{
x
=
x
||
0
;
// (1)
y
=
y
||
0
;
return
[
x
,
y
];
}
在第 (1) 行中,如果 x
为真值(不是 null
、undefined
等),则 ||
运算符返回 x
。否则,它返回第二个操作数
> pair() [ 0, 0 ] > pair(3) [ 3, 0 ] > pair(3, 5) [ 3, 5 ]
如果您想 强制执行 参数个数(特定数量的参数),您可以检查 arguments.length
:
function
pair
(
x
,
y
)
{
if
(
arguments
.
length
!==
2
)
{
throw
new
Error
(
'Need exactly 2 arguments'
);
}
...
}
arguments
不是数组,它只是 类数组(请参阅 类数组对象和泛型方法)。它有一个属性 length
,您可以通过方括号中的索引访问其元素。 但是,您不能删除元素或在其上调用任何数组方法。因此,您有时需要将 arguments
转换为数组,这就是以下函数的作用(在 类数组对象和泛型方法 中有解释)
function
toArray
(
arrayLikeObject
)
{
return
Array
.
prototype
.
slice
.
call
(
arrayLikeObject
);
}
function
getPerson
(
id
)
{
if
(
id
<
0
)
{
throw
new
Error
(
'ID must not be negative: '
+
id
);
}
return
{
id
:
id
};
// normally: retrieved from database
}
function
getPersons
(
ids
)
{
var
result
=
[];
ids
.
forEach
(
function
(
id
)
{
try
{
var
person
=
getPerson
(
id
);
result
.
push
(
person
);
}
catch
(
exception
)
{
console
.
log
(
exception
);
}
});
return
result
;
}
try
子句包围关键代码, 如果在 try
子句内部抛出异常,则执行 catch
子句。使用前面的代码:
> getPersons([2, -5, 137]) [Error: ID must not be negative: -5] [ { id: 2 }, { id: 137 } ]
严格模式(请参阅 严格模式)启用更多警告,并使 JavaScript 成为更简洁的语言(非严格模式有时称为“草率模式”)。 要启用它,请在 JavaScript 文件或 <script>
标记中首先键入以下行:
'use strict'
;
您也可以 为每个函数启用严格模式:
function
functionInStrictMode
()
{
'use strict'
;
}
在 JavaScript 中,您在使用 变量之前通过 var
声明它们:
> var x; > x undefined > y ReferenceError: y is not defined
您可以使用单个 var
语句声明和初始化多个变量
var
x
=
1
,
y
=
2
,
z
=
3
;
但我建议每个变量使用一个语句(原因在 语法 中有解释)。因此,我将把前面的语句重写为
var
x
=
1
;
var
y
=
2
;
var
z
=
3
;
由于提升(请参阅 变量被提升),通常最好在函数的开头声明变量。
变量的作用域始终是完整的函数(而不是当前块)。 例如:
function
foo
()
{
var
x
=
-
512
;
if
(
x
<
0
)
{
// (1)
var
tmp
=
-
x
;
...
}
console
.
log
(
tmp
);
// 512
}
我们可以看到变量 tmp
不限于从第 (1) 行开始的块;它一直存在到函数结束。
每个变量声明都被 提升: 声明被移动到函数的开头,但它进行的赋值保持不变。例如,请考虑以下函数中第 (1) 行的变量声明:
function
foo
()
{
console
.
log
(
tmp
);
// undefined
if
(
false
)
{
var
tmp
=
3
;
// (1)
}
}
在内部,前面的函数是这样执行的
function
foo
()
{
var
tmp
;
// hoisted declaration
console
.
log
(
tmp
);
if
(
false
)
{
tmp
=
3
;
// assignment stays put
}
}
每个函数 都与其周围函数的变量保持连接,即使它离开了创建它的作用域也是如此。例如:
function
createIncrementor
(
start
)
{
return
function
()
{
// (1)
start
++
;
return
start
;
}
}
从第 (1) 行开始的函数离开了创建它的上下文,但与 start
的活动版本保持连接
> var inc = createIncrementor(5); > inc() 6 > inc() 7 > inc() 8
闭包 是一个函数加上与其周围作用域的变量的连接。因此,createIncrementor()
返回的是一个闭包。
有时您想引入一个新的 变量作用域——例如,为了防止变量变成全局变量。在 JavaScript 中,您不能使用块来做到这一点;您必须使用函数。但是,有一种模式可以以类似块的方式使用函数。它被称为 IIFE(立即调用函数表达式,发音为“iffy”)
(
function
()
{
// open IIFE
var
tmp
=
...;
// not a global variable
}());
// close IIFE
请确保完全按照所示键入前面的示例(注释除外)。IIFE 是一个函数表达式,在您定义它之后立即调用。在函数内部,存在一个新的作用域,防止 tmp
变成全局变量。有关 IIFE 的详细信息,请参阅 通过 IIFE 引入新的作用域。
闭包保持与外部变量的 连接,这有时不是您想要的:
var
result
=
[];
for
(
var
i
=
0
;
i
<
5
;
i
++
)
{
result
.
push
(
function
()
{
return
i
});
// (1)
}
console
.
log
(
result
[
1
]());
// 5 (not 1)
console
.
log
(
result
[
3
]());
// 5 (not 3)
第 (1) 行中返回的值始终是 i
的当前值,而不是创建函数时的值。循环结束后,i
的值为 5,这就是为什么数组中的所有函数都返回该值。如果您希望第 (1) 行中的函数接收 i
当前值的快照,您可以使用 IIFE
for
(
var
i
=
0
;
i
<
5
;
i
++
)
{
(
function
()
{
var
i2
=
i
;
// copy current i
result
.
push
(
function
()
{
return
i2
});
}());
}
本节介绍 JavaScript 的两种基本 面向对象机制:单个对象和 构造函数(它们是对象的工厂,类似于其他语言中的类)。
像所有值一样,对象也有属性。 事实上,您可以将对象视为一组属性,其中每个属性都是一个(键,值)对。键是一个字符串,值是任何 JavaScript 值。
在 JavaScript 中,您可以通过 对象字面量 直接创建普通对象
'use strict'
;
var
jane
=
{
name
:
'Jane'
,
describe
:
function
()
{
return
'Person named '
+
this
.
name
;
}
};
前面的对象具有属性 name
和 describe
。您可以读取(“获取”)和写入(“设置”)属性
> jane.name // get 'Jane' > jane.name = 'John'; // set > jane.newProperty = 'abc'; // property created automatically
函数值属性(如 describe
)称为 方法。它们使用 this
来引用用于调用它们的对象:
> jane.describe() // call method 'Person named John' > jane.name = 'Jane'; > jane.describe() 'Person named Jane'
in
运算符检查 属性是否存在:
> 'newProperty' in jane true > 'foo' in jane false
如果您读取一个不存在的属性,您将获得值 undefined
。因此,前面的两个检查也可以像这样执行:[2]
> jane.newProperty !== undefined true > jane.foo !== undefined false
delete
运算符 删除属性:
> delete jane.newProperty true > 'newProperty' in jane false
属性键可以是任何字符串。 到目前为止,我们已经在对象字面量和点运算符之后看到了属性键。但是,只有当它们是 标识符时,您才能以这种方式使用它们(请参阅 标识符和变量名)。如果要使用其他字符串作为键,则必须在对象字面量中将其引起来,并使用方括号 获取和设置属性:
> var obj = { 'not an identifier': 123 }; > obj['not an identifier'] 123 > obj['not an identifier'] = 456;
方括号还允许您 计算属性的键:
> var obj = { hello: 'world' }; > var x = 'hello'; > obj[x] 'world' > obj['hel'+'lo'] 'world'
如果提取方法,它将失去与对象的连接。 就其本身而言,该函数不再是方法 ,并且 this
的值为 undefined
(在严格模式下)。
例如,让我们回到前面的对象 jane
'use strict'
;
var
jane
=
{
name
:
'Jane'
,
describe
:
function
()
{
return
'Person named '
+
this
.
name
;
}
};
我们想从 jane
中提取方法 describe
,将其放入变量 func
中,然后调用它。但是,这不起作用
> var func = jane.describe; > func() TypeError: Cannot read property 'name' of undefined
解决方案是使用 所有函数都具有的方法 bind()
。它创建一个新函数,其 this
始终具有给定的值:
> var func2 = jane.describe.bind(jane); > func2() 'Person named Jane'
每个函数都有 其自己的特殊变量 this
。如果您在方法内部嵌套函数,这很不方便,因为您无法从函数访问方法的 this
。以下是一个示例,我们在其中使用函数调用 forEach
来迭代数组:
var
jane
=
{
name
:
'Jane'
,
friends
:
[
'Tarzan'
,
'Cheeta'
],
logHiToFriends
:
function
()
{
'use strict'
;
this
.
friends
.
forEach
(
function
(
friend
)
{
// `this` is undefined here
console
.
log
(
this
.
name
+
' says hi to '
+
friend
);
});
}
}
调用 logHiToFriends
会产生错误
> jane.logHiToFriends() TypeError: Cannot read property 'name' of undefined
让我们看看解决这个问题的两种方法。首先,我们可以将 this
存储在不同的变量中
logHiToFriends
:
function
()
{
'use strict'
;
var
that
=
this
;
this
.
friends
.
forEach
(
function
(
friend
)
{
console
.
log
(
that
.
name
+
' says hi to '
+
friend
);
});
}
或者,forEach
有第二个参数,允许您为 this
提供一个值
logHiToFriends
:
function
()
{
'use strict'
;
this
.
friends
.
forEach
(
function
(
friend
)
{
console
.
log
(
this
.
name
+
' says hi to '
+
friend
);
},
this
);
}
函数表达式通常用作 JavaScript 中函数调用中的参数。 当您从其中一个函数表达式引用 this
时,请务必小心。
到目前为止,您可能认为 JavaScript 对象 只是 从字符串到值的映射,JavaScript 的对象字面量暗示了这一点,它看起来像其他语言中的映射/字典字面量。但是,JavaScript 对象还支持一项真正面向对象的特性:继承。本节没有完全解释 JavaScript 继承的工作原理,但它向您展示了一个简单的模式来帮助您入门。如果您想了解更多信息,请参阅 第 17 章。
除了是“真正的”函数和方法之外,函数 在 JavaScript 中还扮演着另一个角色:如果通过 new
运算符调用,它们就变成了 构造函数——对象的工厂。因此,构造函数大致类似于其他语言中的类。按照惯例,构造函数的名称以大写字母开头。例如:
// Set up instance data
function
Point
(
x
,
y
)
{
this
.
x
=
x
;
this
.
y
=
y
;
}
// Methods
Point
.
prototype
.
dist
=
function
()
{
return
Math
.
sqrt
(
this
.
x
*
this
.
x
+
this
.
y
*
this
.
y
);
};
我们可以看到构造函数有两部分。首先,函数 Point
设置实例数据。其次,属性 Point.prototype
包含一个具有方法的对象。 前面的数据特定于每个实例,而后面的数据在所有实例之间共享。
要使用 Point
,我们 通过 new
运算符调用它:
> var p = new Point(3, 5); > p.x 3 > p.dist() 5.830951894845301
p
是 Point
的一个实例
> p instanceof Point true
数组字面量便于 创建数组:
> var arr = [ 'a', 'b', 'c' ];
前面的数组有三个元素:字符串 'a'
、'b'
和 'c'
。您可以通过整数索引访问它们
> arr[0] 'a' > arr[0] = 'x'; > arr [ 'x', 'b', 'c' ]
length
属性指示数组有多少个元素。 您可以使用它来追加元素和删除元素:
> var arr = ['a', 'b']; > arr.length 2 > arr[arr.length] = 'c'; > arr [ 'a', 'b', 'c' ] > arr.length 3 > arr.length = 1; > arr [ 'a' ]
in
运算符也适用于 数组:
> var arr = [ 'a', 'b', 'c' ]; > 1 in arr // is there an element at index 1? true > 5 in arr // is there an element at index 5? false
请注意,数组是对象,因此可以具有对象属性:
> var arr = []; > arr.foo = 123; > arr.foo 123
> var arr = [ 'a', 'b', 'c' ]; > arr.slice(1, 2) // copy elements [ 'b' ] > arr.slice(1) [ 'b', 'c' ] > arr.push('x') // append an element 4 > arr [ 'a', 'b', 'c', 'x' ] > arr.pop() // remove last element 'x' > arr [ 'a', 'b', 'c' ] > arr.shift() // remove first element 'a' > arr [ 'b', 'c' ] > arr.unshift('x') // prepend an element 3 > arr [ 'x', 'b', 'c' ] > arr.indexOf('b') // find the index of an element 1 > arr.indexOf('y') -1 > arr.join('-') // all elements in a single string 'x-b-c' > arr.join('') 'xbc' > arr.join() 'x,b,c'
有几种数组 方法可用于迭代元素(参见 迭代(非破坏性))。最重要的两个是 forEach
和 map
。
forEach
迭代数组并将当前元素及其索引传递给函数
[
'a'
,
'b'
,
'c'
].
forEach
(
function
(
elem
,
index
)
{
// (1)
console
.
log
(
index
+
'. '
+
elem
);
});
前面的代码产生以下输出
0. a 1. b 2. c
请注意,第 (1) 行中的函数可以随意忽略参数。例如,它可以只有参数 elem
。
map
通过将 函数应用于现有数组的每个元素来创建新数组:
> [1,2,3].map(function (x) { return x*x }) [ 1, 4, 9 ]
JavaScript 内置支持正则表达式(第 19 章 涉及教程并更详细地解释了它们的工作原理)。它们由 斜杠分隔:
/^abc$/
/
[
A
-
Za
-
z0
-
9
]
+
/
> /^a+b+$/.test('aaab') true > /^a+b+$/.test('aaa') false
> /a(b+)a/.exec('_abbba_aba_') [ 'abbba', 'bbb' ]
返回的 数组在索引 0 处包含完全匹配,在索引 1 处包含第一个组的捕获,依此类推。有一种方法(在 RegExp.prototype.exec:捕获组 中讨论)可以重复调用此方法以获取所有匹配项。
> '<a> <bbb>'.replace(/<(.*?)>/g, '[$1]') '[a] [bbb]'
replace
的第一个参数必须是带有 /g
标志的正则表达式;否则,只会替换第一个匹配项。还有一种方法(如 String.prototype.replace:搜索和替换 中所述)可以使用函数来计算替换。
Math
(参见 第 21 章)是一个具有算术 函数的对象。以下是一些示例:
> Math.abs(-2) 2 > Math.pow(3, 2) // 3 to the power of 2 9 > Math.max(2, -1, 5) 5 > Math.round(1.9) 2 > Math.PI // pre-defined constant for π 3.141592653589793 > Math.cos(Math.PI) // compute the cosine for 180° -1
JavaScript 的标准库 相对简单,但您可以使用更多内容: