原文: http://exploringjs.com/impatient-js/ch_syntax.html
译者:Hunter-liu
注释:
// 单行注释
/*
多行
注释
*/
原始(原子)值:
// Booleans
true
false
// Numbers (JavaScript 的数字只有一种类型)
-123
1.414
// String (JavaScript 的字符没有对应的类型)
'abc'
"abc"
断言描述了计算结果的预期结果,如果这些期望不正确则抛出异常。例如,以下断言声明计算 7 加 1 的结果必须为 8:
assert.equal(7 + 1, 8);
assert.equal()是一个方法调用(对象是assert,方法是.equal()),有两个参数:实际结果和预期结果。它是 Node.js 断言 API 的一部分,本书后面的将对此进行解释。
打印日志到浏览器的控制台或 Node.js:
// 将值标准输出打印(另一种方法调用)
console.log("Hello!");
// 将错误信息标准输出打印
console.error('Something weng wrong!');
运算符:
// 布尔运算符
assert.equal(true && false, false); // 与
assert.equal(true || false, true); // 或
// 数学运算符
assert.equal(3 + 4, 7);
assert.equal(5 - 1, 4);
assert.equal(3 * 4, 12);
assert.equal(9 / 3, 3);
// 字符串操作符
assert.equal('a' + 'b', 'ab');
assert.equal('I see ' + 3 + ' monkeys', 'I see 3 monkeys');
// 比较运算符
assert.equal(3 < 4, true);
assert.equal(3 <= 4, true);
assert.equal('abc' === 'abc', true);
assert.equal('abc' !== 'def', true);
声明变量:
let x; // 声明 x(可变的)
x = 3 * 5; // 给 x 赋值
let y = 3 * 5; // 声明变量并为其赋值
const z = 8; // 声明 y(不可变的)
控制流声明:
// 条件语句
if (x < 0) { // x 小于 0?
x = -x;
}
普通函数声明:
// add1() 有 a 和 b 两个参数
function add1(a, b) {
return a + b;
}
// 调用函数 add1()
assert.eaual(add1(5, 2), 7);
箭头函数表达式(特别用作函数调用和方法调用的参数):
const add2 = (a, b) => a + b;
// 调用方法 add2()
assert.equal(add2(5, 2), 7);
const add3 = (a, b) => { return a + b };
上面的代码包含以下箭头函数(关于表达式和语句块等术语将在在本章后面解释):
// 主体为一个表达式的箭头函数
(a, b) => a + b
// 主体为一个代码块的箭头函数
(a, b) => { return a + b }
对象:
// 通过 object 符号({})创建一个对象
const obj = {
first: 'Jane', // 属性
last: 'Doe', // 属性
getFullName() { // 属性(方法)
return this.first + ' ' + this.last;
},
};
// 获取某个属性值
assert.equal(obj.first, 'Jane');
// 设置某个属性的值
obj.first = 'Janey';
// 调用对象的方法
assert.equal(obj.getFullName(), 'Janey Doe');
数组(数组也是对象):
// 通过 Array 符号([])创建一个数组
const arr = ['a', 'b', 'c'];
// 获取某个数组元素
assert.equal(arr[1], 'b');
// 设置某个数组元素的值
arr[1] = 'β';
每个模块都是一个文件。例如,考虑以下两个包含模块的文件:
file-tools.js
main.js
file-tools.js中的模块导出其功能isTextFilePath():
export function isTextFilePath(filePath) {
return filePath.endsWith('.txt');
}
main.js中的模块导入整个模块path和函数isTextFilePath():
// 导入整个模块 `path`
import * as path from 'path';
// 导入模块 file-tools.js export的一个函数
import {isTextFilePath} from './file-tools.js';
变量名称和属性名称的语法类别称为标识符。
标识符允许具有以下字符:
A - Z,a - z(等)$,_0 - 9(等)有些单词在 JavaScript 中有特殊含义,称为保留字。例如:if,true,const。
保留字不能用作变量名:
const if = 123;
// SyntaxError: Unexpected token if
但它们被允许作为属性的名称:
> const obj = { if: 123 };
> obj.if
123
用于连接单词的常见类型是:
threeConcatenatedWordsthree_concatenated_wordsthree-concatenated-words通常,JavaScript 使用驼峰大小写,但常量除外。
小写:
myFunctionobj.myMethodspecial-classspecialClass大写:
MyClassMY_CONSTANT
myConstant在语句的最后:
const x = 123;
func();
但是,如果该语句以大括号结尾,那就不加分号了:
while (false) {
// ···
} // 无分号
function func() {
// ···
} // 无分号
但是,在这样的语句之后添加分号不是语法错误 - 它被解释为空语句:
// 函数声明后跟空语句
function func() {
// ···
};
参见测验应用程序。
本章的所有其余部分都是深入说明(上面的内容)。
首字符:
é和ü等重音字符和非拉丁字母的字符,如α)$_后续字符:
例如:
const ε = 0.0001;
const строка = '';
let _tmp = 0;
const $foo2 = true;
保留字不能是变量名,但它们可以是属性名。
所有 JavaScript 关键字都是保留字:
awaitbreakcasecatchclassconstcontinuedebuggerdefaultdeletedoelseexportextendsfinallyforfunctionifimportininstanceofletnewreturnstaticsuperswitchthisthrowtrytypeofvarvoidwhilewithyield
以下标记也是关键字,但目前未在该语言中使用:
enumimplementspackageprotectedinterfaceprivatepublic
以下值是保留字:
truefalsenull
从技术上讲,这些单词不是保留的,但你也应该避免使用它们,因为它们实际上是关键字:
InfinityNaNundefinedasync
您也不应将全局变量的名称(String,Math等)用于您自己的变量和参数。
在本节中,我们将探讨 JavaScript 如何区分两种句法结构:_ 语句 _ 和 _ 表达式 _。之后,我们会发现这会导致问题,因为相同的语法可能意味着不同的东西,具体取决于它的使用位置。
为简单起见,我们假装 JavaScript 中只有语句和表达式。
语句 是一段可以执行并执行某种操作的代码。例如,if是一段语句:
let myStr;
if (myBool) {
myStr = 'Yes';
} else {
myStr = 'No';
}
语句的另一个例子:函数声明。
function twice(x) {
return x + x;
}
表达式是可以评估以产生值的一段代码。例如,括号之间的代码是一个表达式:
let myStr = (myBool ? 'Yes' : 'No');
括号之间使用的运算符 _?_:_ 称为三元运算符。它是if语句的表达式版本。
让我们看一下表达式的更多例子。我们输入表达式,REPL 为我们评估它们:
> 'ab' + 'cd'
'abcd'
> Number('123')
123
> true || false
true
JavaScript 源代码中的当前位置决定了您可以使用哪种语法结构:
function max(x, y) {
if (x > y) {
return x;
} else {
return y;
}
}
console.log('ab' + 'cd', Number('123'));
但是,表达式可以用作语句。然后将它们称为表达式语句。相反的情况并非如此:当上下文需要表达式时,你便不能使用语句。
以下代码演示了某个表达式bar()可以是表达式还是语句——它取决于上下文:
console.log(bar());
bar();
JavaScript 有几种语法歧义的编程结构:相同的语法被不同地解释,这取决于它是在语句上下文中还是在表达式上下文中使用。本节探讨了这一现象及其引发的陷阱。
函数声明是一个声明:
function id(x) {
return x;
}
函数表达式是一个表达式(=右侧的):
const id = function me(x) {
return x;
};
在下面的代码中,{}是 对象字面值:一个创建空对象的表达式。
const obj = {};
这是一个空代码块(声明):
{
}
歧义只是语句上下文中的一个问题:如果 JavaScript 解析器遇到模糊语法,它不知道它是简单语句还是表达式语句。例如:
function开头:它是函数声明还是函数表达式?{开头:它是对象字面值还是代码块?为了解决歧义,以function或{开头的语句永远不会被解释为表达式。如果希望表达式语句以这些标记中的任何一个开头,则必须将其包装在括号中:
(function (x) { console.log(x) })('abc');
// Output:
// 'abc'
在这段代码中:
function (x) { console.log(x) }
('abc')#1 只被解释为表达式,因为我们将它包装在括号中。如果我们没有,我们会得到一个语法错误,因为 JavaScript 需要一个函数声明,之后还会警告缺少的函数名称。此外,你不能在函数声明后立即进行函数调用。
在本书的后面,我们将看到更多由语法模糊引起的陷阱的例子:
每个语句都以分号结束。
const x = 3;
someFunction('abc');
i++;
例外:以块结尾的语句。
function foo() {
// ···
}
if (y > 0) {
// ···
}
以下情况有点棘手:
const func = () => {}; // 分号!
整个const声明(一个语句)以分号结尾,但在其中,有一个箭头函数表达式。那就是:声明本身并不是以花括号结尾;它是嵌入式箭头函数表达式。这就是为什么最后会有一个分号的原因。
控制语句的主体本身就是一个声明。例如,这是while循环的语法:
while (condition)
语句
正文可以是一行语句:
while (a > 0) a--;
但代码块也是声明,因此也是控制声明的合法主体:
while (a > 0) {
a--;
}
如果你想让一个循环有一个空的主体,那么你的首选便是一个空语句(它只是一个分号):
while (processNextItem() > 0);
你的第二个选择是一个空语句块:
while (processNextItem() > 0) {}
虽然我建议总是写分号,但大多数都是 JavaScript 中的可选项。使这成为可能的机制称为自动分号插入(ASI)。在某种程度上,它可以纠正语法错误。
ASI 的工作原理如下。语句解析会直到出现以下情况:
换句话说,ASI 可以看作在换行符处插入分号。接下来的小节将介绍 ASI 的陷阱。
关于 ASI 的好消息是——如果你不依赖它并且总是写分号——你只需要注意一个陷阱。这是 JavaScript 禁止在一些标记之后的换行符。如果插入换行符,也会插入分号。
最实际相关的标记是return。例如,考虑以下代码:
return
{
first: 'jane'
};
此代码解析为:
return;
{
first: 'jane';
}
;
也就是说,一个空的 return 语句,后跟一个代码块,后跟一个空语句。
为什么 JavaScript 会这样做?它可以防止在return之后意外返回一行中的值。
在某些情况下,当您认为 ASI 应该触发时,ASI 并没有触发。对于那些不喜欢分号的人来说,这会使生活更加复杂,因为他们需要意识到这些情况。以下是三个例子。还有更多。
**例 1:**非预期的函数调用。
a = b + c
(d + e).print()
解析为:
a = b + c(d + e).print();
**例 2:**意外分裂。
a = b
/hi/g.exec(c).map(d)
解析为:
a = b / hi / g.exec(c).map(d);
**例 3:**非预期的属性访问。
someFunction()
['ul', 'ol'].map(x => x + x)
被执行为:
const propKey = ('ul','ol');
assert.equal(propKey, 'ol'); // 因为逗号
someFunction()[propKey].map(x => x + x);
我建议你要经常写分号:
然而,也有许多人不喜欢添加分号的视觉混乱。如果你是其中之一:可能会认为没有它们的代码亦是合法的。我建议你使用工具来帮助您避免错误。以下是两个例子:
从 ECMAScript 5 开始,你可以选择在所谓的严格模式中执行 JavaScript。在该模式下,语言稍微清晰:不存在一些怪异的写法并且同时会抛出更多异常。
默认(非严格)模式也称为草率模式。
请注意,默认模式在模块和类中默认打开,因此在编写现代 JavaScript(几乎总是位于模块中)时,您并不需要了解它。在本书中,我假设严格模式始终打开。
在旧脚本文件和 CommonJS 模块中,通过将以下代码放在第一行中,您可以为完整文件切换严格模式:
'use strict';
关于这个“指令”的巧妙之处在于,5 之前的 ECMAScript 版本只是忽略它:它是一个什么都不做的表达式语句。
你还可以仅为单个函数打开严格模式:
function functionInStrictMode() {
'use strict';
}
让我们看一个示例,其中草率模式做一些严格模式不会做的坏事:更改未知变量(未通过let或类似创建)创建一个全局变量。
function sloppyFunc() {
unknownVar1 = 123;
}
sloppyFunc();
// Created global variable `unknownVar1`:
assert.equal(unknownVar1, 123);
严格模式则做得更好:
function strictFunc() {
'use strict';
unknownVar2 = 123;
}
assert.throws(
() => strictFunc(),
{
name: 'ReferenceError',
message: 'unknownVar2 is not defined',
});
assert.throws()要求它的第一个参数,某个函数,在调用时抛出ReferenceError。
参见测验应用程序。