在 JavaScript 中,有三种包含字符串的方式。
- 双引号:
"Hello"
. - 单引号:
'Hello'
. - 反引号:
`Hello`
.
双引号和单引号都是“简单”引用,在 JavaScript 中两者几乎没有什么差别。
反引号是 功能扩展 引号。它们允许我们通过将变量和表达式包装在 ${…}
中,来将它们嵌入到字符串中。例如:
let name = "John";
// 嵌入一个变量
alert( `Hello, ${name}!` ); // Hello, John!
// 嵌入一个表达式
alert( `the result is ${1 + 2}` ); // the result is 3
${…}
内的表达式会被计算,计算结果会成为字符串的一部分。可以在 ${…}
内放置任何东西:诸如名为 name
的变量,或者诸如 1 + 2
的算数表达式,或者其他一些更复杂的。
需要注意的是,这仅仅在反引号内有效,其他引号不允许这种嵌入。
typeof null
的结果是"object"
。这是官方承认的typeof
的行为上的错误,这个问题来自于 JavaScript 语言的早期,并为了兼容性而保留了下来。null
绝对不是一个object
。null
有自己的类型,它是一个特殊值。typeof alert
的结果是"function"
,因为alert
在 JavaScript 语言中是一个函数。我们会在下一章学习函数,那时我们会了解到,在 JavaScript 语言中没有一个特别的 “function” 类型。函数隶属于object
类型。但是typeof
会对函数区分对待,并返回"function"
。这也是来自于 JavaScript 语言早期的问题。从技术上讲,这种行为是不正确的,但在实际编程中却非常方便。
我们也可以使用 Number(value)
显式地将这个 value
转换为 number 类型。
number 类型转换规则:
值 | 变成…… |
---|---|
undefined | NaN |
null | 0 |
true 和 false | 1 and 0 |
string | 去掉首尾空格后的纯数字字符串中含有的数字。如果剩余字符串为空,则转换结果为 0 。否则,将会从剩余字符串中“读取”数字。当类型转换出现 error 时返回 NaN 。 |
alert( Number(" 123 ") ); // 123
alert( Number("123z") ); // NaN(从字符串“读取”数字,读到 "z" 时出现错误)
alert( Number(true) ); // 1
alert( Number(false) ); // 0
请注意 null
和 undefined
在这有点不同:null
变成数字 0
,undefined
变成 NaN
。
用"+"也可以实现Number()的效果:
alert( +" 123 " ); // 123
alert( +"123z" ); // NaN(从字符串“读取”数字,读到 "z" 时出现错误)
alert( +true ); // 1
alert( +false ); // 0
明显使用“+”更简洁。
我们经常会有将字符串转化为数字的需求。比如,如果我们正在从 HTML 表单中取值,通常得到的都是字符串。如果我们想对它们求和,该怎么办?
二元运算符加号会把它们合并成字符串:
let apples = "2";
let oranges = "3";
alert( apples + oranges ); // "23",二元运算符加号合并字符串
如果我们想把它们当做数字对待,我们需要转化它们,然后再求和:
let apples = "2";
let oranges = "3";
// 在二元运算符加号起作用之前,所有的值都被转化为了数字
alert( +apples + +oranges ); // 5
// 更长的写法
// alert( Number(apples) + Number(oranges) ); // 5
一元运算符优先级高于二元运算符,这里apples和oranges前的两个加号作为一元运算符处理,优先级比中间的加号要高(中间的加号做两个数的求和,为二元运算符)。所以这里会先把两个字符串转换为数字再做加法运算。
幂运算:2 ** 4 表示2的4次方 4 ** (1/2)表示4的1/2次方即4的开方
布尔转换(可以使用Boolean(),它在做 if 判断是会发生):
- 数字
0
、空字符串""
、null
、undefined
和NaN
都会被转换成false
。因为它们被称为“假值(falsy)”值。注意任何非空字符串都会转成true,例如“0” (字符0),“ ”(字符空格)等。 - 其他值被转换为
true
,所以它们被称为“真值(truthy)”。
如果是值的比较,请参考下面内容
值的比较
数字比较略,
在比较字符串的大小时,JavaScript 会使用“字典(dictionary)”或“词典(lexicographical)”顺序进行判定。
换言之,字符串是按字符(母)逐个进行比较的。并且比较的是Unicode 编码顺序。
不同类型的比较:
当对不同类型的值进行比较时,JavaScript 会首先将其转化为数字(number)再判定大小。
注意:在转换时 null 转换成 0 ,undefined 转换成 NaN,NaN与任何数字比较都是返回false。null和undefined在做==判断时不发生转换,且null == undefined返回true,与其他任何值相比都返回false。另外 null === undefined 返回false。
alert( null > 0 ); // (1) false
alert( null == 0 ); // (2) false
alert( null >= 0 ); // (3) true
alert( undefined > 0 ); // false (1)
alert( undefined < 0 ); // false (2)
alert( undefined == 0 ); // false (3)
5 > 4 → true
"apple" > "pineapple" → false
"2" > "12" → true
undefined == null → true
undefined === null → false
null == "\n0\n" → false
null === +"\n0\n" → false
关于if
可以用三目运算代替if...else,例如:
let message;
if (login == 'Employee') {
message = 'Hello';
} else if (login == 'Director') {
message = 'Greetings';
} else if (login == '') {
message = 'No login';
} else {
message = '';
}
可以写成:
let message = (login == 'Employee') ? 'Hello' :
(login == 'Director') ? 'Greetings' :
(login == '') ? 'No login' :
'';
虽然if....else更长但是可读性强,可以自己取舍。
逻辑运算符
|| (或)
除了基本用法,我们可以利用或运算来提取第一个真值
result = value1 || value2 || value3;
如果不存在真值,就返回该链的最后一个值
alert( 1 || 0 ); // 1(1 是真值)
alert( null || 1 ); // 1(1 是第一个真值)
alert( null || 0 || 1 ); // 1(第一个真值)
alert( undefined || null || 0 ); // 0(都是假值,返回最后一个值)
实际运用:
例如,我们有变量 firstName
、lastName
和 nickName
,都是可选的(即可以是 undefined,也可以是假值)。
我们用或运算 ||
来选择有数据的那一个,并显示出来(如果没有设置,则用 "Anonymous"
):
let firstName = "";
let lastName = "";
let nickName = "SuperCoder";
alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder
短路求值(Short-circuit evaluation)。
或运算符 ||
的另一个用途是所谓的“短路求值”。
这指的是,||
对其参数进行处理,直到达到第一个真值,然后立即返回该值,而无需处理其他参数。
如果操作数不仅仅是一个值,而是一个有副作用的表达式,例如变量赋值或函数调用,那么这一特性的重要性就变得显而易见了。
在下面这个例子中,只会打印第二条信息:
true || alert("not printed");
false || alert("printed");
在第一行中,或运算符 ||
在遇到 true
时立即停止运算,所以 alert
没有运行。
有时,人们利用这个特性,只在左侧的条件为假时才执行命令。
下面这段代码会输出什么?
alert( alert(1) || 2 || alert(3) );
答案:首先是 1
,然后是 2
。
对 alert
的调用没有返回值。或者说返回的是 undefined
。
- 第一个或运算
||
对它的左值alert(1)
进行了计算。这就显示了第一条信息1
。 - 函数
alert
返回了undefined
,所以或运算继续检查第二个操作数以寻找真值。 - 第二个操作数
2
是真值,所以执行就中断了。2
被返回,并且被外层的 alert 显示。
这里不会显示 3
,因为运算没有抵达 alert(3)
。
&&(与)
基本用法略。
与运算寻找第一个假值
result = value1 && value2 && value3;
和或运算相反,这里返回的是第一个假值,如果没有假值,则返回最后一个值。
alert( 1 && 2 && null && 3 ); // null
alert( 1 && 2 && 3 ); // 3,最后一个值
与运算 &&
的优先级比或运算 ||
要高。
所以代码 a && b || c && d
跟 &&
表达式加了括号完全一样:(a && b) || (c && d)
。
这段代码将会显示什么?
alert( alert(1) && alert(2) );
答案:1,然后 undefined。
调用 alert
返回了 undefined
(它只展示消息,所以没有有意义的返回值)。
因此,&&
计算了它左边的操作数(显示 1
),然后立即停止了,因为 undefined
是一个假值。&&
就是寻找假值然后返回它,所以运算结束。
!(非)
感叹符号 !
表示布尔非运算符。
逻辑非运算符接受一个参数,并按如下运作:
- 将操作数转化为布尔类型:
true/false
。 - 返回相反的值。
两个非运算 !!
有时候用来将某个值转化为布尔类型:
alert( !!"non-empty string" ); // true
alert( !!null ); // false
非运算符 !
的优先级在所有逻辑运算符里面最高,所以它总是在 &&
和 ||
之前执行。
??(
空值合并运算符)
空值合并运算符 ??
提供了一种从列表中选择第一个“已定义的”值的简便方式。
它被用于为变量分配默认值:
// 当 height 的值为 null 或 undefined 时,将 height 的值设置为 100
height = height ?? 100;
-
??
运算符的优先级非常低,仅略高于?
和=
,因此在表达式中使用它时请考虑添加括号。 -
如果没有明确添加括号,不能将其与
||
或&&
一起使用。
循环
我们可以利用continue来减少代码嵌套:
for (let i = 0; i < 10; i++) {
if (i % 2) {
alert( i );
}
}
可以改写成:
for (let i = 0; i < 10; i++) {
//如果为真,跳过循环体的剩余部分。
if (i % 2 == 0) continue;
alert(i); // 1,然后 3,5,7,9
}
函数
函数声明方式如下所示:
function name(parameters, delimited, by, comma) {
/* code */
}
- 作为参数传递给函数的值,会被复制到函数的局部变量。
- 函数可以访问外部变量。但它只能从内到外起作用。函数外部的代码看不到函数内的局部变量。
- 函数可以返回值。如果没有返回值,则其返回的结果是
undefined
。如alter() - 函数的参数可以有默认值
function showMessage(from, text = "no text given") { alert( from + ": " + text ); } showMessage("Ann"); // Ann: no text given
这里text在不给参数的情况下就会使用我们设置的默认值"no text given"
为了使代码简洁易懂,建议在函数中主要使用局部变量和参数,而不是外部变量。
与不获取参数但将修改外部变量作为副作用的函数相比,获取参数、使用参数并返回结果的函数更容易理解。
函数命名:
- 函数名应该清楚地描述函数的功能。当我们在代码中看到一个函数调用时,一个好的函数名能够让我们马上知道这个函数的功能是什么,会返回什么。
- 一个函数是一个行为,所以函数名通常是动词。
- 目前有许多优秀的函数名前缀,如
create…
、show…
、get…
、check…
等等。使用它们来提示函数的作用吧。
不能在return和返回值之间加空行
对于 return
的长表达式,可能你会很想将其放在单独一行,如下所示:
return
(some + long + expression + or + whatever * f(a) + f(b))
但这不行,因为 JavaScript 默认会在 return
之后加上分号。上面这段代码和下面这段代码运行流程相同: