# 现代 JavaScript 教程 重要的 ## JavaScript 基础知识 浏览器中嵌入了 JavaScript 引擎,有时也称作“JavaScript 虚拟机”。 不同的引擎有不同的“代号”,例如: V8 —— Chrome 和 Opera 中的 JavaScript 引擎。 SpiderMonkey —— Firefox 中的 JavaScript 引擎。 ……还有其他一些代号,像 “Chakra” 用于 IE,“ChakraCore” 用于 Microsoft Edge,“Nitro” 和 “SquirrelFish” 用于 Safari,等等。 **f12** 開發工具 f12 換行 用shift + enter **script** 老的 HTML4 标准中,要求 script 标签有 type 特性。通常是 type="text/javascript" **分號** javaScript 将换行符理解成“隐式”的分号。这也被称为 自动分号插入。 但最好不要省 **分開寫的原因** 獨立文件的好处是浏览器会下载它,然后将它保存到浏览器的 缓存 中 **use strict** 为了保证旧的功能能够使用,大部分的修改是默认不生效的。你需要一个特殊的指令 —— "use strict" 来明确地激活这些特性 现代 JavaScript 支持 “classes” 和 “modules” —— 高级语言结构(本教程后续章节会讲到),它们会自动启用 use strict **变量** 三種寫法 ![](https://i.imgur.com/1zlylKj.png) **数据类型** JavaScript 中的 null 不是一个“对不存在的 object 的引用”或者 “null 指针”。 JavaScript 中的 null 仅仅是一个代表“无”、“空”或“值未知”的特殊值。 undefined 的含义是 未被赋值。 如果一个变量已被声明,但未被赋值,那么它的值就undefined **用户交互的 3 个浏览器的特定函数** * alert 显示信息。 * prompt 显示信息要求用户输入文本。点击确定返回文本,点击取消或按下 Esc 键返回 null。 * confirm 显示信息等待用户点击确定或取消。点击确定返回 true,点击取消或按下 Esc 键返回 false。 **类型转换** 数字型转换 `alert( "6" / "2" ); // 3,` string 类型的值被自动转换成 number 类型后进行计算 number 类型转换规则: 值 变成…… * undefined 變成 NaN * null 變成0 * true 和 false 變成 1 and 0 * string 變成 去掉首尾空格后的纯数字字符串中含有的数字。如果剩余字符串为空,则转换结果为 0。否则,将会从剩余字符串中“读取”数字。当类型转换出现 error 时返回 NaN。 **请注意**:包含 0 的字符串 "0" 是 true 对 "0" 和只有空格的字符串(比如:" ")进行布尔型转换时,输出结果为 true alert( Boolean("0") ); // true alert( Boolean(" ") ); // 空白,也是 true(任何非空字符串都是 true) **基础运算符** a % b 的结果是 a 整除 b 的 余数 通常,加号 + 用于求和。 但是如果加号 + 被应用于字符串,它将合并(连接)各个字符串: **注意**:只要任意一个运算元是字符串,那么另一个运算元也将被转化为字符串。 ``` alert( '1' + 2 ); // "12" alert( 2 + '1' ); // "21" ``` **数字转化,一元运算符 +** // 转化非数字 alert( +true ); // 1 alert( +"" ); // 0 它的效果和 Number(...) 相同,但是更加简短。 用在 ``` let apples = "2"; let oranges = "3"; // 在二元运算符加号起作用之前,所有的值都被转化为了数字 alert( +apples + +oranges ); // 5 // 更长的写法 // alert( Number(apples) + Number(oranges) );//5 ``` **字符串比较** ,JavaScript 会使用“字典(dictionary)”或“词典(lexicographical)”顺序进行判定。 换言之,字符串是按字符(母)逐个进行比较的 非真正的字典顺序,而是 Unicode 编码顺序 ``` alert( 'Z' > 'A' ); // true alert( 'Glow' > 'Glee' ); // true alert( 'Bee' > 'Be' ); // true ``` **不同类型间的比较** ``` alert( '2' > 1 ); // true,字符串 '2' 会被转化为数字 2 alert( '01' == 1 ); // true,字符串 '01' 会被转化为数字 1 ``` * 除了严格相等 === 外,其他但凡是有 undefined/null 参与的比较,我们都需要格外小心。 * 除非你非常清楚自己在做什么,否则永远不要使用 >= > < <= 去比较一个可能为 null/undefined 的变量。对于取值可能是 null/undefined 的变量,请按需要分别检查它的取值情况。 **当使用数学式或其他比较方法 < > <= >= 时:** null/undefined 会被转化为数字:null 被转化为 0,undefined 被转化为 NaN。 下面让我们看看,这些规则会带来什么有趣的现象。同时更重要的是,我们需要从中学会如何远离这些特性带来的“陷阱”。 奇怪的结果:null vs 0 通过比较 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。 **多个 ‘?’** ``` let age = prompt('age?', 18); let message = (age < 3) ? 'Hi, baby!' : (age < 18) ? 'Hello!' : (age < 100) ? 'Greetings!' : 'What an unusual age!'; alert( message ); ``` * 第一个问号检查 age < 3。 * 如果为真 — 返回 'Hi, baby!'。否则,会继续执行冒号 ":" 后的表达式,检查 age < 18。 * 如果为真 — 返回 'Hello!'。否则,会继续执行下一个冒号 ":" 后的表达式,检查 age < 100。 * 如果为真 — 返回 'Greetings!'。否则,会继续执行最后一个冒号 ":" 后面的表达式,返回 'What an unusual age!'。 **'??'** 另一方面,空值合并运算符 ?? 是最近才被添加到 JavaScript 中的,它的出现是因为人们对 || 不太满意。 **它们之间重要的区别是**: || 返回第一个 真 值。 ?? 返回第一个 已定义的 值。 换句话说,|| 无法区分 false、0、空字符串 "" 和 null/undefined。它们都一样 —— 假值(falsy values)。如果其中任何一个是 || 的第一个参数,那么我们将得到第二个参数作为结果。 ?? 首先会检查是否为 null/undefined,发现它不是才會往後 ?? 优先级很低 ``` // 重要:使用括号 let area = (height ?? 100) * (width ?? 50); ``` ?? 与 && 或 || 一起使用 出于安全原因,JavaScript 禁止将 ?? 运算符与 && 和 || 运算符一起使用,除非使用括号明确指定了优先级 總結 ?? 它被用于为变量分配默认值 **if** 数字 0、空字符串 ""、null、undefined 和 NaN 都会被转换成 false。因为它们被称为“假值(falsy)”值。 其他值被转换为 true,所以它们被称为“真值(truthy)”。 **while 和 for** break 跳出循环 continue 继续下一次迭代 continue 指令是 break 的“轻量版”。它不会停掉整个循环。而是停止当前这一次迭代,并强制启动新一轮循环 ``` for (let i = 0; i < 10; i++) { //如果为真,跳过循环体的剩余部分。 if (i % 2 == 0) continue; alert(i); // 1,然后 3,5,7,9 } ``` **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.'); } ``` **函数** 只有在没有局部变量的情况下才会使用外部变量。 如果在函数内部声明了同名变量,那么函数会 遮蔽 外部变量 ,不會覆蓋跟更改喔,是遮蔽 一个函数是一个行为,所以函数名通常是动词。 目前有许多优秀的函数名前缀,如 create…、show…、get…、check… 等等。使用它们来提示函数的作用吧 **函数表达式** 跟上面不一樣 上面是 函数声明 函數表達式 ``` let sayHi = function() { alert( "Hello" ); }; ``` **重點** ``` function sayHi() { alert( "Hello" ); } alert( sayHi ); // 显示函数代码 ``` 在其他编程语言中,只要提到函数的名称都会导致函数的调用执行,但 JavaScript 可不是这样。 在 JavaScript 中,函数是一个值,所以我们可以把它当成值对待。上面代码显示了一段字符串值,即函数的源码 **函数表达式 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 ``` 如果要在外面能用呢 正确的做法是使用函数表达式,并将 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(); // 现在可以了 ``` ## 測試 完全看不懂 之後再看 https://zh.javascript.info/testing-mocha ## Chrome 中调试(變強在回去看) **“资源(Sources)”面板** ![](https://i.imgur.com/BorhyVR.png) **控制台(Console)** 不解釋 都會的 **断点(Breakpoints)** ![](https://i.imgur.com/dGfu3Q5.png) **Debugger 命令** ![](https://i.imgur.com/e4khaei.png) **暂停并查看** ![](https://i.imgur.com/oyn56kB.png) 请打开右侧的信息下拉列表(箭头指示出的地方)。这里允许你查看当前的代码状态: * 察看(Watch) —— 显示任意表达式的当前值。 你可以点击加号 + 然后输入一个表达式。调试器将随时显示它的值,并在执行过程中自动重新计算该表达式。 * 调用栈(Call Stack) —— 显示嵌套的调用链。 此时,调试器正在 hello() 的调用链中,被 index.html 中的一个脚本调用(这里没有函数,因此显示 “anonymous”) 如果你点击了一个堆栈项,调试器将跳到对应的代码处,并且还可以查看其所有变量。 * 作用域(Scope) —— 显示当前的变量。 Local 显示当前函数中的变量,你还可以在源代码中看到它们的值高亮显示了出来。 Global 显示全局变量(不在任何函数中)。 这里还有一个 this 关键字,目前我们还没有学到它,不过我们很快就会学习它了。 ###### tags: `javaScript`