update test 更新文章 1. 测试新主题 挖坑 挖坑 update config 更新文章 1. 简易FaaS平台 更新文章 1. 修复错误 更新文章:Promise信任问题 update theme 1. 合并js post: update notedly fix: update faas feature: change theme * fix: comment * feature: pgp * fix: delete local file post: update darkmode update: update dependencies fix: navbar in post incorrect height * pre code adapt to dark mode update new post useCallback update dependencies new post tiny router * add static files update vue tiny router 添加备案 * 更新依赖 add post Add ignore file
7.0 KiB
title, date, tags, categories, url
title | date | tags | categories | url |
---|---|---|---|---|
JavaScript-语法 | 2021-07-12 15:20:03 | JavaScript | 笔记 | javascript-syntax |
在 JavaScript 中,有很多常见的语法仍然有很多地方容易产生困惑、造成误解。
语句与表达式
语句(statement)和表达式(expression)常常被混为一谈,但他们二者之间有细微的差别。语句相当于一句话,而表达式更类似于一条短语。
JavaScript 中表达式可以返回一个结果值:
let a = 3 * 6;
const b = a;
b;
上述带有let
与const
的语句被称为“声明语句”(declaration statement),而不带声明关键词的a = 3 * 6;
则是“赋值表达式”。
乍一看可能还是不能太过于区分他们之间的区别,但是有些地方会让我们强制区分他们。在现代 SPA 框架中,模板语法中通常只允许插入 JavaScript 表达式,而不允许插入语句。例如在常见的 Vue 和 React 中:
<template>
<span>{{ 3 * 6 }}</span>
<span>{{ a }}</span>
<span>{{ let a = 3 * 6 }}</span> // 错误
</template>
语句的结果值
基本上所有的语句都有一个结果值(undefined 也算)。
如果经常和控制台打交道,应该会发现很多时候控制台都会给我们返回一个 undefined。其实这就是语句所返回的值,只不过大部分情况下都是 undefined。
代码块中的返回值是最后一个语句/表达式的结果:
let a ;
// undefined
if (true) {
a = 3 + 1;
}
// 4
目前代码块语句返回的值无法被拿到:
b = if (true) {
a = 3 + 1;
}
// VM268:1 Uncaught SyntaxError: Unexpected token 'if'
当然也有变通的方法 evil:
b = eval("if (true) {a = 3 + 1;}");
b; // 4
除了万恶的 evil,ES7 还有一项“do 表达式”提案,目前环境还没实现:
b = do {
if (true) {
a = 3 + 1;
}
};
b; // 4
表达式的副作用
副作用通常是指执行了语句之后,除了返回值或赋值,还有其他变量等被修改。
最常见的副作用的表达式是函数调用:
const foo = () => {
a = a + 1;
};
let a = 1;
foo(); // 结果:undefined,副作用:a 的值被修改
上下文规则
在 JavaScript 语法规则中,有时候同样的语法在不同的情况下会有不同的解释。这些语法规则孤立起来会很难理解。
大括号
大括号的作用有很多,随着 JavaScript 的演进可能会出现更多类似的情况。
对象常量
大括号可以使用表达式的方式定义对象:
const a = {
foo: bar()
}
大括号内的内容被赋值给变量 a,因而它是一个对象常量。
标签
将上述声明语句去掉,这时的大括号可不是一个孤立的对象常量。因为这里的{...}
被看作了一个普通的代码块。这里的foo: bar()
被解释为标签语句。
{
foo: bar()
}
标签语句配合 continue 和 break 语句可以实现类似于 goto 的方式进行跳转。但 goto 是一种极为糟糕的编码方式,所以 JavaScript 并不支持真正的 goto 语句。
代码块
有一个强制类型转换的坑经常被提到:
[] + {} // "[object Object]"
{} + [] // 0
看上去简直就是 JavaScript 在和我们开玩笑。其实不然,上述情况并不是非常难以理解。
第一行是普通的强制类型转换,二者都会被强制转换字符串,而[]
被转换为了''
,{}
被转换为了"[object Object]"
,所以他们相加的最后结果就是"[object Object]"
。
第二行看上去有点莫名其妙,换个位置结果就变了。这里其实{}
并没有参与运算,而是被当作了一段独立的空代码块。真正参与运算的是+[]
,最终被转换为数字 0。
对象解构
从 ES6 开始{...}
也可用于解构赋值。
const foo = () => {
return {
name: 'xfy',
age: 18,
};
};
const { name, age } = foo();
console.log(name, age);
同理,函数形参也可以利用解构赋值。
else if 和可选代码块
其实else if
并不存在于 JavaScript 语法中,只是多个if else
只包含单条语句的时候可以省略代码块的{...}
。
实际上else if
是这样的:
if (a) {
console.log(a);
} else {
if (b) {
console.log(b);
} else {
console.log(c);
}
}
运算符优先级
多数情况下,JavaScript 的数学运算符与真正的数学一样,按照真正的数学的顺序进行运算。但 JavaScript 的语句并不是数学公式,而且语句中通常会充斥着各种各样的运算符。他们也是有各自的运算优先级的。
短路
and 与 or 操作拥有“短路”(short circuiting)的特性,别急,我们还没走错片场。在 JavaScript 中,他们分别是&&
和||
运算符。
短路的意思是,当左边的操作数能够得出结果时,就可以忽略右边的操作数。
对于&&
来说,左边的操作数为假值时,则就没有必要再判断右边的值。同理,对于||
来说,左边的操作数为真值时,也没有必要再判断右边的操作数。
关联
JavaScript 的代码是从上往下执行的,而单条语句是从左往右执行的。但在特定的运算符下,某些语句需要先求得右边的值。
例如:
true ? false : true ? true : true;
这段代码乍一看很令人头疼,但它确实说明运算符的关联最容易理解的例子。在这里,第二个三目运算符的结果会影响第一个三目运算符,所以这里要执行右关联,也就是需要先求得第二个三目运算符的结果。
虽然是右关联,但它的运算顺序还是从左到右。
a ? b : c;
还是会先执行 a 然后 b c。
自动分号
有时 JavaScript 会自动为代码补上分号,即分号自动插入机制(Automatic Semicolon Insertion,ASI)。
ASI只会在换行符处起作用,而不会在代码中间插入分号。
常见的情况是在 return 后自动加上,这样 return 后换行的代码就会不生效。return 语句可以跨越多行,但是其后必须右换行符以外的代码。
const foo = () => {
return (
1 + 2
);
};
纠错机制
是否应该完全依赖 ASI 来编码,这是 JavaScript 社区中最具争议性的话题之一。
支持的一方认为 ASI 大有裨益,能省略掉那些不必要的;
,让代码更简洁。此外 ASI 让许多;
变得可有可无,因此只要代码没问题,有没有;
都一样。
反方则认为 ASI 机制问题太多,对于缺乏经验的初学者尤其如此,因为自动插入;
会无意改变代码的逻辑。有些人还认为依赖 linter 这些工具找出错误,而不是依赖 JavaScript 引擎。
事实上,现在多数;
可以通过 ESlint 这样的 linter 工具来在格式化代码是插入。这种方式相比较用引擎来找出错误好很多,而且它不会在运行时改变代码原有的意思。
小结
JavaScript 语法规则中的许多细节需要我们多花时间和精力来了解。从长远来看,这有助于更深入地掌握这门语言。