脚本
一般来说,只有最简单的脚本才嵌入到HTML中。更复杂的脚本存放在单独的文件中。使用独立文件的好处是浏览器会下载它,然后将它保存到浏览器的缓存中。之后,其他页面想要相同的脚本就会从缓存中获取,而不是下载它。所以文件实际上只会下载一次。这可以节省流量,并使得页面(加载)更快。
一个单独的script标签不能同时有src特性和内部包裹的代码。如果设置了src特性,script标签内容将会被忽略。
分号
在大多数情况下,换行意味着一个分号。但是“大多数情况”并不意味着“总是”!有很多换行并不是分号的例子,例如:
alert(3 +
1
+ 2);
代码输出 6,因为 JavaScript 并没有在这里插入分号。显而易见的是,如果一行以加号 "+" 结尾,那么这是一个“不完整的表达式”,不需要分号。所以,这个例子得到了预期的结果。
但存在 JavaScript 无法确定是否真的需要自动插入分号的情况,这种情况下发生的错误是很难被找到和解决的,以下就是一个错误的例子:
alert("Hello");
[1, 2].forEach(alert);
这是能正常运行的代码,但如果删除 alert 语句后的分号:
alert("Hello")
[1, 2].forEach(alert);
如果我们运行这段代码,只有第一个 Hello 会被显示出来(并且有一个报错,你可能需要打开控制台才能看到它)。并且不会再有数字被显示出来。
这是因为,JavaScript 引擎并没有假设在方括号 [...] 前有一个分号。因此,最后一个示例中的代码被视为了单个语句。对于引擎来说,它是这样的:
alert("Hello")[1, 2].forEach(alert);
在这种情况下,这样将两行代码合并到一起是不对的。我们需要在 alert 后面加一个分号,代码才能正常运行。
注释
js不支持注释嵌套!
例如:
/*
/* 嵌套注释 ?!? */
*/
alert( 'World' );
上面这一段段代码是会报错且无法执行的。
“严格模式”
- 确保 “use strict” 出现在最顶部,否则严格模式可能无法启用,只有注释可以出现在 "use strict" 的上面。
- 并没有类似于 "no use strict" 这样的指令可以使程序返回默认模式。一旦进入了严格模式,就没有回头路了。
- 使用 开发者控制台 运行代码时,请注意它默认是不启动 use strict 的。
如果想在控制台中使用“use strict”,可以尝试搭配使用 Shift+Enter 按键去输入多行代码,然后将 use strict 放在代码最顶部,就像这样:
'use strict'; <Shift+Enter 换行>
// ...你的代码
<按下 Enter 以运行>
除此之外,现代 JavaScript 支持 “class” 和 “module” —— 高级语言结构,它们会自动启用 use strict。因此,如果我们使用它们,则无需添加 "use strict" 指令。
命名变量
- 区分大小写
- 允许非英文字母,但不推荐
数据类型 Number
除了常规的数字,还包括所谓的“特殊数值(“special numeric values”)”也属于这种类型:Infinity、-Infinity 和 NaN。
Infinity 代表数学概念中的 无穷大 ∞。是一个比任何数字都大的特殊值。
NaN 代表一个计算错误。它是一个不正确的或者一个未定义的数学操作所得到的结果,比如:
alert( "not a number" / 2 ); // NaN,这样的除法是错误的
NaN 是粘性的。任何对 NaN 的进一步数学运算都会返回 NaN:
al
ert( NaN + 1 ); // NaN
alert( 3 * NaN ); // NaN
alert( "not a number" / 2 - 1 ); // NaN
所以,如果在数学表达式中有一个 NaN,会被传播到最终结果(只有一个例外:NaN ** 0 结果为 1)。
除此之外,在 JavaScript 中,“number” 类型无法安全地表示大于$ (2^{53}-1)$(即 9007199254740991),或小于$ -(2^{53}-1)$ 的整数,这时候就得用BigInt 类型。
prompt与confirm
prompt 函数接收两个参数:
result = prompt(title, [default]);
title
显示给用户的文本,default
可选的第二个参数,指定 input 框的初始值。
confirm与prompt类似,函数显示一个带有 question 以及确定和取消两个按钮的模态窗口。点击确定返回 true,点击取消返回 false。
类型比较
当对不同类型的值进行比较时,JavaScript 会首先将其转化为数字(number)再判定大小。
例如:
alert( '2' > 1 ); // true,字符串 '2' 会被转化为数字 2
alert( '01' == 1 ); // true,字符串 '01' 会被转化为数字 1
对于布尔类型值,true 会被转化为 1、false 转化为 0。
有时候,以下两种情况会同时发生:
若直接比较两个值,其结果是相等的。
若把两个值转为布尔值,它们可能得出完全相反的结果,即一个是 true,一个是 false。
例如:
let a = 0;
alert( Boolean(a) ); // false
let b = "0";
alert( Boolean(b) ); // true
alert(a == b); // true!
对于 JavaScript 而言,这种现象其实挺正常的。因为 JavaScript 会把待比较的值转化为数字后再做比较(因此 "0" 变成了 0)。若只是将一个变量转化为 Boolean 值,则会使用其他的类型转换规则。
null vs 0
当使用数学式或其他比较方法 < > <= >= 时:
null/undefined 会被转化为数字:null 被转化为 0,undefined 被转化为 NaN。
通过比较 null 和 0 可得:
alert( null > 0 ); // (1) false
alert( null == 0 ); // (2) false
alert( null >= 0 ); // (3) true
上面的结果完全打破了你对数学的认识。在最后一行代码显示“null 大于等于 0”的情况下,前两行代码中一定会有一个是正确的,然而事实表明它们的结果都是 false。
为什么会出现这种反常结果,这是因为相等性检查 == 和普通比较符 > < >= <= 的代码逻辑是相互独立的。进行值的比较时,null 会被转化为数字,因此它被转化为了 0。这就是为什么(3)中 null >= 0 返回值是 true,(1)中 null > 0 返回值是 false。
另一方面,undefined 和 null 在相等性检查 == 中不会进行任何的类型转换,它们有自己独立的比较规则,所以除了它们之间互等外,不会等于任何其他的值。这就解释了为什么(2)中 null == 0 会返回 false。
?? 与 && 或 ||
|| 返回第一个真值。
?? 返回第一个已定义的值。?? 运算符的优先级与 || 相同,
出于安全原因,JavaScript 禁止将 ?? 运算符与 && 和 || 运算符一起使用,除非使用括号明确指定了优先级。
下面的代码会触发一个语法错误:
let x = 1 && 2 ?? 3; // Syntax error
这个限制无疑是值得商榷的,它被添加到语言规范中是为了避免人们从 || 切换到 ?? 时的编程错误。
可以明确地使用括号来解决这个问题:
let x = (1 && 2) ?? 3; // 正常工作了
alert(x); // 2
函数表达式 vs 函数声明
- 函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用。一旦代码执行到赋值表达式
`
let sum =
function…`
的右侧,此时就会开始创建该函数,并且可以从现在开始使用(分配,调用等)。 - 在函数声明被定义之前,它就可以被调用。这是内部算法的缘故。当 JavaScript 准备
运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数。我们可以将其视为“初始化阶段”。
严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见。
如果我们使用函数声明,则以下代码无法像预期那样工作:
let age = prompt("What is your age?", 18);
// 有条件地声明一个函数
if (age < 18) {
function welcome() {
alert("Hello!");
}
} else {
function welcome() {
alert("Greetings!");
}
}
// ……稍后使用
welcome(); // Error: welcome is not defined
这是因为函数声明只在它所在的代码块中可见。
下面是另一个例子:
let age = 16; // 拿 16 作为例子
if (age < 18) {
welcome(); // \ (运行)
// |
function welcome() { // |
alert("Hello!"); // | 函数声明在声明它的代码块内任意位置都可用
} // |
// |
welcome(); // / (运行)
} else {
function welcome() {
alert("Greetings!");
}
}
// 在这里,我们在花括号外部调用函数,我们看不到它们内部的函数声明。
welcome(); // Error: welcome is not defined
如果想让 welcome 在 if 外可见,正确的做法是使用函数表达式,并将 welcome 赋值给在 if 外声明的变量,并具有正确的可见性。
下面的代码可以如愿运行:
let age = prompt("What is your age?", 18);
let welcome;
if (age < 18) {
welcome = function() {
alert("Hello!");
};
} else {
welcome = function() {
alert("Greetings!");
};
}
welcome(); // 现在可以了
或者我们可以使用问号运算符 ? 来进一步对代码进行简化:
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
function() { alert("Hello!"); } :
function() { alert("Greetings!"); };
welcome(); // 现在可以了
箭头函数
- 不带花括号:
(...args) => expression
——
右侧是一个表达式:函数计算表达式并返回其结果。如果只有一个参数,则可以省略括号,例如n => n*2
。 - 带花括号:
(...args) => { body }
—— 花括号允许我们在函数中编写多个语句,但是我们需要显式地return
来返回一些内容。