(现代JavaScript 之JavaScript 基础知识 第一部分 第二章 易错总结)
2.1 Hello, world!
1. 现代 JavaScript 中已经不这样使用了。这些注释是用于不支持
<script>
标签的古老的浏览器隐藏 JavaScript 代码的。由于最近 15 年内发布的浏览器都没有这样的问题,因此这种注释能帮你辨认出一些老掉牙的代码。
<script type=…> <script language=…>
2. 一个单独的
<script>
标签不能同时有 src 特性和内部包裹的代码。
<script src=\"file.js\">alert(1); // 此内容会被忽略,因为设定了 src</script>
2.2 代码结构
1. 在大多数的编辑器中,一行代码可以使用<kbd>Ctrl+/</kbd> 热键进行单行注释,<kbd> Ctrl+Shift+/ </kbd>的热键可以进行多行注释
2. 不要在
/*...*/
内嵌套另一个
/*...*/
。
2.3 现代模式,"use strict"
1. 没有办法取消
\"use strict\"
没有类似于
\"no use strict\"
这样的指令可以使程序返回默认模式。一旦进入了严格模式,就没有回头路了。
2. 当你使用 开发者控制台 运行代码时,请注意它默认是不启动
\"use strict\"
的。你可以尝试搭配使用 <kbd>Shift+Enter</kbd> 按键去输入多行代码,然后将
\"use strict\"
放在代码最顶部。
3. 目前我们欢迎将 "use strict"; 写在脚本的顶部。稍后,当你的代码全都写在了 class 和 module 中时,你则可以将
\"use strict\"
; 这行代码省略掉。
2.4 变量
1. var 关键字与 let 大体 相同,也用来声明变量,但稍微有些不同,也有点“老派”。
2. 额外声明一个变量绝对是利大于弊的。现代的 JavaScript 压缩器和浏览器都能够很好地对代码进行优化,所以不会产生性能问题。为不同的值使用不同的变量可以帮助引擎对代码进行优化。
2.5 数据类型
8 种基本的数据类型
- Number 类型
特殊数值:Infinity、-Infinity 和 NaN,数学运算是安全的 - BigInt 类型
“number” 类型无法表示大于 (253-1)(即 9007199254740991),或小于 -(253-1) 的整数。这是其内部表示形式导致的技术限制。可以通过将 n 附加到整数字段的末尾来创建 BigInt 值。 - String 类型
反引号:let phrase = ``can embed another ${str}``;
- Boolean 类型(逻辑类型)
用于 true 和 false。 - “null” 值
JavaScript 中的 null 仅仅是一个代表“无”、“空”或“值未知”的特殊值。 - “undefined” 值
undefined 的含义是 未被赋值。如果一个变量已被声明,但未被赋值,那么它的值就是 undefined - object 类型
用于更复杂的数据结构。 - symbol 类型
用于唯一的标识符。
typeof 运算符
- 两种形式:typeof x 或者 typeof(x)。
- typeof null 会返回 "object" —— 这是 JavaScript 编程语言的一个错误,实际上它并不是一个 object。
2.6 交互:alert、prompt 和 confirm
alert
alert(\"Hello\");
弹出的带有信息的小窗口被称为 模态窗。
prompt
result = prompt(title, [default]);
title 显示给用户的文本default 可选的第二个参数,指定 input 框的初始值。(不是必须的)
confirm
result = confirm(question);
点击确定返回 true,点击取消返回 false
2.7 类型转换
字符串转换
字符串转换最明显。false 变成 "false",null 变成 "null" 等。
数字型转换
值 | 变成…… |
---|---|
undefined | NaN |
null | 0 |
true 和 false | 1 and 0 |
string | 去掉首尾空格后的纯数字字符串中含有的数字。如果剩余字符串为空,则转换结果为 0。否则,将会从剩余字符串中“读取”数字。当类型转换出现 error 时返回 NaN。 |
请注意 null 和 undefined 在这有点不同:null 变成数字 0,undefined 变成 NaN。
布尔型转换
转换规则如下:
- 直观上为“空”的值(如 0、空字符串、null、undefined 和 NaN)将变为 false。
- 其他值变成 true。
- 对 "0" 和只有空格的字符串(比如:" ")进行布尔型转换时,输出结果为 true。
请注意:包含 0 的字符串 "0" 是 true
2.8 基础运算符,数学
数字转化,一元运算符 +
但是如果运算元不是数字,加号 + 则会将其转化为数字。
// 转化非数字alert( +true ); // 1alert( +\"\" ); // 0
2.9 值的比较
一个有趣的现象
let a = 0;alert( Boolean(a) ); // falselet b = \"0\";alert( Boolean(b) ); // truealert(a == b); // true!
对 null 和 undefined 进行比较
当使用严格相等 === 比较二者时
它们不相等,因为它们属于不同的类型。
alert( null === undefined ); // false
当使用非严格相等 == 比较二者时
JavaScript 存在一个特殊的规则,会判定它们相等。它们俩就像“一对恋人”,仅仅等于对方而不等于其他任何的值(只在非严格相等下成立)。
alert( null == undefined ); // true
当使用数学式或其他比较方法 < > <= >= 时:
null/undefined 会被转化为数字:null 被转化为 0,undefined 被转化为 NaN。
alert( null > 0 ); // (1) falsealert( null == 0 ); // (2) falsealert( null >= 0 ); // (3) true
特立独行的 undefined
alert( undefined > 0 ); // false (1)alert( undefined < 0 ); // false (2)alert( undefined == 0 ); // false (3)
值的比较练习题
5 > 4 → true\"apple\" > \"pineapple\" → false\"2\" > \"12\" → trueundefined == null → trueundefined === null → falsenull == \"\\n0\\n\" → falsenull === +\"\\n0\\n\" → false
2.11 逻辑运算符
||(或)
result = value1 || value2 || value3;
一个或运算 || 的链,将返回第一个真值,如果不存在真值,就返回该链的最后一个值。
&&(与)
result = value1 && value2 && value3;
与运算返回第一个假值,如果没有假值就返回最后一个值。
与运算 && 的优先级比或运算 || 要高。
!(非)
两个非运算 !! 有时候用来将某个值转化为布尔类型:
alert( !!\"non-empty string\" ); // truealert( !!null ); // false
下面的代码将会输出什么?
alert( alert(1) || 2 || alert(3) );
答案:首先是 1,然后是 2.
与运算连接的 alerts 的结果是什么?
alert( alert(1) && alert(2) );
答案:1,然后 undefined。
或运算、与运算、或运算串联的结果
alert( null || 2 && 3 || 4 );
答案:3。
2.12 空值合并运算符 \’??\’
a ?? b 的结果是:
- 如果 a 是已定义的,则结果为 a,
- 如果 a 不是已定义的,则结果为 b。
重写 result = a ?? b
result = (a !== null && a !== undefined) ? a : b;
举例
let firstName = null;let lastName = null;let nickName = \"Supercoder\";// 显示第一个已定义的值:alert(firstName ?? lastName ?? nickName ?? \"Anonymous\"); // Supercoder
与 || 比较
它们之间重要的区别是:
- || 返回第一个 真 值。
- ?? 返回第一个 已定义的 值。
如果没有明确添加括号,不能将其与 || 或 && 一起使用。
2.13 循环:while 和 for
普通 break 只会打破内部循环
标签 是在循环之前带有冒号的标识符:
outer: for (let i = 0; i < 3; i++) {for (let j = 0; j < 3; j++) {let input = prompt(`Value at coords (${i},${j})`, \'\');// 如果是空字符串或被取消,则中断并跳出这两个循环。if (!input) break outer; // (*)// 用得到的值做些事……}}
我们还可以将标签移至单独一行:
outer:for (let i = 0; i < 3; i++) { ... }
continue
指令也可以与标签一起使用。在这种情况下,执行跳转到标记循环的下一次迭代。
只有在循环内部才能调用 break/continue,并且标签必须位于指令上方的某个位置。
break label; // 无法跳转到这个标签label: for (...)
易错题
以下两个循环的 alert 值是否相同?
前缀形式 ++i: 从 1 到 4
let i = 0;while (++i < 5) alert( i );
后缀形式 i++:从 1 到 5
let i = 0;while (i++ < 5) alert( i );
2.14 "switch" 语句
共享同一段代码的几个 case 分支可以被分为一组:
let a = 3;switch (a) {case 4:alert(\'Right!\');break;case 3: // (*) 下面这两个 case 被分在一组case 5:alert(\'Wrong!\');alert(\"Why don\'t you take a math class?\");break;default:alert(\'The result is strange. Really.\');}
2.15 函数
默认值
如果未提供参数,那么其默认值则是 undefined。
如果我们想在本示例中设定“默认”的 text,那么我们可以在 = 之后指定它:
function showMessage(from, text = \"no text given\") {alert( from + \": \" + text );}showMessage(\"Ann\"); // Ann: no text given
这里 "no text given" 是一个字符串,但它可以是更复杂的表达式,并且只会在缺少参数时才会被计算和分配。所以,这也是可能的:
function showMessage(from, text = anotherFunction()) {// anotherFunction() 仅在没有给定 text 时执行// 其运行结果将成为 text 的值}
后备的默认参数
我们可以拿它跟 undefined 做比较:
function showMessage(text) {if (text === undefined) {text = \'empty message\';}alert(text);}showMessage(); // empty message
我们可以使用 || 运算符:
// 如果 \"text\" 参数被省略或者被传入空字符串,则赋值为 \'empty\'function showMessage(text) {text = text || \'empty\';...}
现代 JavaScript 引擎支持 空值合并运算符 ??,当可能遇到其他假值时它更有优势,如 0 会被视为正常值不被合并:
// 如果没有传入 \"count\" 参数,则显示 \"unknown\"function showCount(count) {alert(count ?? \"unknown\");}showCount(0); // 0showCount(null); // unknownshowCount(); // unknown
空值的 return 或没有 return 的函数返回值为 undefined
函数命名
函数以 XX 开始……
- "get…" —— 返回一个值,
- "calc…" —— 计算某些内容,
- "create…" —— 创建某些内容,
- "check…" —— 检查某些内容并返回 boolean 值,等。
2.16 函数表达式
在 JavaScript 中,函数不是“神奇的语言结构”,而是一种特殊的值。
函数声明:
function sayHi() {alert( \"Hello\" );}
函数表达式:
let sayHi = function() {alert( \"Hello\" );};
我们可以复制函数到其他变量:
function sayHi() { // (1) 创建alert( \"Hello\" );}let func = sayHi; // (2) 复制func(); // Hello // (3) 运行复制的值(正常运行)!sayHi(); // Hello // 这里也能运行(为什么不行呢)
为什么这里末尾会有个分号?
function sayHi() {// ...}let sayHi = function() {// ...};
- 在代码块的结尾不需要加分号 ;,像 if { … },for { },function f { } 等语法结构后面都不用加。
- 函数表达式是在语句内部的:let sayHi = …;,作为一个值。它不是代码块而是一个赋值语句。不管值是什么,都建议在语句末尾添加分号 ;。所以这里的分号与函数表达式本身没有任何关系,它只是用于终止语句。
回调函数
function ask(question, yes, no) {if (confirm(question)) yes()else no();}function showOk() {alert( \"You agreed.\" );}function showCancel() {alert( \"You canceled the execution.\" );}// 用法:函数 showOk 和 showCancel 被作为参数传入到 askask(\"Do you agree?\", showOk, showCancel);
ask 的两个参数值 showOk 和 showCancel 可以被称为 回调函数 或简称 回调。
函数表达式 vs 函数声明
函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用。
一旦代码执行到赋值表达式 let sum = function… 的右侧,此时就会开始创建该函数,并且可以从现在开始使用(分配,调用等)。
函数声明则不同。
在函数声明被定义之前,它就可以被调用。
一个全局函数声明对整个脚本来说都是可见的,无论它被写在这个脚本的哪个位置。
函数声明的另外一个特殊的功能是它们的块级作用域。
严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见。
如果我们使用函数声明,则以下代码无法像预期那样工作:
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
正确的做法是使用函数表达式,并将 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(); // 现在可以了
2.17 箭头函数,基础知识
单行箭头函数
let func = (arg1, arg2, ...argN) => expression
多行的箭头函数
let sum = (a, b) => { // 花括号表示开始一个多行函数let result = a + b;return result; // 如果我们使用了花括号,那么我们需要一个显式的 “return”};alert( sum(1, 2) ); // 3
2.18 JavaScript 特性
typeof 运算符返回值的类型,但有两个例外:
typeof null == \"object\" // JavaScript 编程语言的设计错误typeof function(){} == \"function\" // 函数被特殊对待
值 null 和 undefined 是特殊的:它们只在 == 下相等,且不相等于其他任何值。