JavaScript 的文法:JavaScript语法(2):
JavaScript 遵循了一般编程语言的‘语句 - 表达式’结构,多数编程语言都是这样设计的。
脚本,或者模块都是由语句列表构成的,这次,就来一起了解一下语句。
在 JavaScript 标准中,把语句分成了两种:声明和语句,不过,这里的区分逻辑比较奇怪,所以,这里winter按照他的思路整理一下:
- 普通语句:


- 声明型语句:

普通语句
语句块
简单理解,语句块就是一对大括号:
{
var x, y;
x = 10;
y = 20;
}
语句块的意义和好处在于:让我们可以把多行语句视为同一行语句,这样,if、for 等语句定义起来就比较简单了。不过,我们需要注意的是,语句块会产生作用域。
{
let x = 1;
}
console.log(x); // 报
这里我们的let声明,仅仅对语句块作用域生效,于是我们在语句块外试图访问语句块内的变量 x 就会报错。
空语句
空语句就是一个独立的分号,实际上没什么。
;
空语句的存在仅仅是从语言设计完备性的角度考虑,允许插入多个分号而不抛出错
if语句
if 语句是条件语句。
if语句的作用是,在满足条件时执行它的内容语句,这个语句可以是一个语句块,这样就可以实现有条件地执行多个语句了。if语句还有 else结构,用于不满足条件时执行,一种常见的用法是,利用语句的嵌套能力,把 if和else连写成多分支条件判
for in 循环
for in 循环枚举对象的属性,这里体现了属性的 enumerable 特征。
let o = { a: 10, b: 20}
Object.defineProperty(o, "c", {enumerable:false, value:30})
for(let p in o)
console.log(p);
这段代码中,我们定义了一个对象 o,给它添加了不可枚举的属性 c,之后我们用 for in 循环枚举它的属性,我们会发现,输出时得到的只有 a 和 b。
如果我们定义 c 这个属性时,enumerable 为 true,则 for in循环中也能枚举到它
try 语句和 throw 语句
try语句和throw 语句用于处理异常。它们是配合使用的,所以我们就放在一起讲了。在大型应用中,异常机制非常重要。
一般来说,throw用于抛出异常,但是单纯从语言的角度,我们可以抛出任何值,也不一定是异常逻辑,但是为了保证语义清晰,不建议用 throw表达任何非异常
逻辑。try 语句用于捕获异常,用throw抛出的异常,可以在try语句的结构中被处理掉:try部分用于标识捕获异常的代码段,catch部分则用于捕获异常后做一些处理,而 finally 则是用于执行后做一些必须执行的清理工作。catch结构会创建一个局部的作用域,并且把一个变量写入其中,需要注意,在这个作用域,不能再声明变量 e 了,否则会出错。
在catch 中重新抛出错误的情况非常常见,在设计比较底层的函数时,常常会这样做,保证抛出的错误能被理解。finally语句一般用于释放资源,它一定会被执行,我们在前面的课程中已经讨论过一些finally的特征,即使在try中出现了 return,finally中的语句也一定要被执行。
debugger 语句
debugger 语句的作用是:通知调试器在此断点。在没有调试器挂载时,它不产生任何效果。
声明型语句
var
var 声明语句是古典的 JavaScript 中声明变量的方式。而现在,在绝大多数情况下,let和 const 都是更好的选择。
它是一种预处理机制。
如果我们仍然想要使用 var,的个人建议是,把它当做一种“保障变量是局部”的逻辑,遵循以下三条规则:
- 声明同时必定初始化;
- 尽可能在离使用的位置近处声明;
- 不要在意重复声明。
let 和 const
let和 const的作用范围是if、for 等结构型语句。
let 和 const 声明虽然看上去是执行到了才会生效,但是实际上,它们还是会被预处理。如果当前作用域内有声明,就无法访问到外部的变
class 声明
class最基本的用法只需要 class关键字、名称和一对大括号。它的声明特征跟 const和let类似,都是作用于块级作用域,预处理阶段则会屏蔽外部变量
函数声明
函数声明使用 function关键。
JavaScript 的文法:JavaScript语法(3):
在语句部分,讲到了很多种语句类型,但是,其实最终产生执行效果的语句不多。
事实上,真正能干活的就只有表达式语句,其它语句的作用都是产生各种结构,来控制表达式语句执行,或者改变表达式语句的意义。
什么是表达式语句
表达式语句实际上就是一个表达式,它是由运算符连接变量或者直接量构成的
一般来说,我们的表达式语句要么是函数调用,要么是赋值,要么是自增、自减,否则表达式计算的结果没有任何意义。
但是从语法上,并没有这样的限制,任何合法的表达式都可以当做表达式语句使用。比如a + b;
下面来了解下都有哪些表达式,从粒度最小到粒度最大了解一下
PrimaryExpression 主要表达
表达式的原子项:Primary Expression。它是表达式的最小单位,它所涉及的语法结构也是优先级最高的。
Primary Expression 包含了各种“直接量”,直接量就是直接用某种语法写出来的具有特定类型的值。
-
简单回顾直接量。比如,用
null关键字获取null值,这个用法就是null直接量。"abc"; 123; null; true false; -
除这些之外,JavaScript 还能够直接量的形式定义对象,针对函数、类、数组、正则表达式等特殊对象类型,JavaScript 提供了语法层面的支持。
({}); (function(){}); (class{ }); []; /abc/g -
需要注意,在语法层面,
function、{和class开头的表达式语句 与 声明语句有语法冲突,所以,我们要想使用这样的表达式,必须加上括号来回避语法冲突。在 JavaScript 标准中,这些结构有的被称作直接量(Literal),有的被称作表达式(**Expression),在我看来,把它们都理解成直接量比较合适。
-
Primary Expression还可以是this或者变量,在语法上,把变量称作“标识符引用”。 -
任何表达式加上圆括号,都被认为是
Primary Expression,这个机制使得圆括号成为改变运算优先顺序的(a + b)
这就是Primary Expression的几种形式了,接下来,讲讲由 Primary Expression 构成的更复杂的表达式:Member Expression。
MemberExpression 成员表达式
-
Member Expression通常是用于访问对象成员的。它有几种形式:a.b; a["b"]; new.target; super.b;前面两种用法都很好理解,就是用标识符的属性访问和用字符串的属性访问。而
new.target是个新加入的语法,用于判断函数是否是被new调用,super则是构造函数中,用于访问父类的属性的语法。 -
从名字就可以看出,
Member Expression最初设计是为了属性访问的,不过从语法结构需要,以下两种在 JavaScript 标准中当做Member Expression:f`a${b}c`;这是一个是带函数的模板,这个带函数名的模板表示把模板的各个部分算好后传递给一个
new Cls()另一个是带参数列表的
new运算,注意,不带参数列表的new运算优先级更低,不属于Member Expression
实际上,这两种被放入 Member Expression,仅仅意味着它们跟属性运算属于同一优先级,没有任何语义上的关联。接下来我们看看 Member Expression能组成什么。
NewExpression NEW 表达式
这种非常简单,Member Expression加上 new 就是New Expression(当然,不加new 也可以构成 New Expression,JavaScript 中默认独立的高优先级表达式都可以构成低优先级表达式)。
这里的 New Expression 特指没有参数列表的表达式。我们看个稍微复杂的例子:
new new Cls(1);
CallExpression 函数调用表达
除了 New Expression,Member Expression 还能构成 Call Expression。它的基本形式是 Member Expression后加一个括号里的参数列表,或者我们可以用上super 关键字代替 Member Expression。
a.b(c);
super();
这看起来很简单,但是它有一些变体。比如:
a.b(c)(d)(e);
a.b(c)[3];
a.b(c).d;
a.b(c)`xyz`;
这些变体的形态,跟 Member Expression几乎是一一对应的。实际上,我们可以理解为,Member Expression 中的某一子结构具有函数调用,那么整个表达式就成为了一个Call Expression。
而 Call Expression 就失去了比New Expression优先级高的特性,这是一个主要的区分。
LeftHandSideExpression 左值表达式
New Expression和 Call Expression统称 LeftHandSideExpression,左值表达式
们直观地讲,左值表达式就是可以放到等号左边的表达式。JavaScript 语法则是下面这样。
a() = b;
左值表达式最经典的用法是用于构成赋值表。
AssignmentExpression 赋值表达式
AssignmentExpression 赋值表达式也有多种形态,最基本的当然是使用等号赋值:
a = b;
这个等号是可以嵌套的:
a = b = c = d
这样的连续赋值,是右结合的,它等价于下面这种:
a = (b = (c = d)
当然,这并非一个很好的代码风格,我们讲解语法是为了让你理解这样的用法,而不是推荐你这样写代码。
赋值表达式的使用,还可以结合一些运算符,例如:
a += b;
JavaScript 的文法:JavaScript语法(4):
其中关于赋值表达式,讲完了它的左边部分,而留下了它右边部分,那么,来详细讲解。
在一些通用的计算机语言设计理论中,能够出现在赋值表达式右边的叫做:右值表达式(RightHandSideExpression),而在 JavaScript 标准中,规定了在等号右边表达式叫做条件表达式(ConditionalExpression),不过,在 JavaScript 标准中,从未出现过右值表达式字样。
JavaScript 标准也规定了左值表达式同时都是条件表达式(也就是右值表达式),此外,左值表达式也可以通过跟一定的运算符组合,逐级构成更复杂的结构,直到成为右值表达式。
更新表达式 UpdateExpression
左值表达式搭配 ++ – 运算符,可以形成更新表达式。
-- a
++ a
a --
a ++
更新表达式会改变一个左值表达式的值。分为前后自增,前后自减一共四种。
一元运算表达式 UnaryExpressio
更新表达式搭配一元运算符,可以形成一元运算表达式
delete a.b
void a
typeof a
- a
~ a
! a
await a
他的特点就是一个更新表达式搭配了一个一元运算符。
乘方表达式 ExponentiationExpressio
乘方表达式也是由更新表达式构成的。它使用**
++i ** 30
2 ** 30 // 正确
-2 ** 30 // 报
例子中,-2 这样的一元运算表达式,是不可以放入乘方表达式的,如果需要表达类似的逻辑,必须加括号。
我们需要注意一下结合性,** 运算是右结合的,这跟其它正常的运算符(也就是左结合运算符)都不一样:
4 ** 3 **2;
//以上它是这样被运算的:
4 ** (3 ** 2)
//而不是这样被运算的
(4 ** 3) **
乘法表达式 MultiplicativeExpression
乘方表达式可以构成乘法表达式,用乘号或者除号、取余符号连接就可以了,我们看看例子:
x * 2
乘法表达式有三种运算符:*,/,%
它们分别表示乘、除和取余。它们的优先级是一样的,所以统一放在乘法运算表达式中。
加法表达式 AdditiveExpression
加法表达式有加号和减号两种运算符。
加法表达式是由乘法表达式用加号或者减号连接构成的。
a + b * c
移位表达式 ShiftExpression
移位表达式由加法表达式构成,移位是一种位运算,分成三种:
<< 向左移位
>> 向右移位
>>> 无符号向右
移位运算把操作数看做二进制表示的整数,然后移动特定位数。所以左移 n 位相当于乘以 2 的 n 次方,右移 n 位相当于除以 2 取整 n次。
关系表达式 RelationalExpression
移位表达式可以构成关系表达式,这里的关系表达式就是大于、小于、大于等于、小于等于等运算符号连接,统称为关系运算。
<=
>=
<
>
instanceof
in
需要注意,这里的 <= 和 >= 关系运算,完全是针对数字的,所以 <= 并不等价于 < 或 ==。例如:
null <= undefined
//false
null == undefined
//tru
相等表达式 EqualityExpression
在语法上,相等表达式是由关系表达式用相等比较运算符(如==)连接构成的。所以我们可以像下面这段代码一样使用,而不需要加括号:
a instanceof "object" == true
相等表达式由四种运算符和关系表达式构成,我们来看一下运算符:
==
!=
===
!=
类型不同的变量比较时==运算只有三条规则:
- undefined 与 null 相等;
- 字符串和 bool 都转为数字再比较;
- 对象转换成 primitive 类型再比较。
这样我们就可以理解一些不太符合直觉的例子了,比如:
false == '0' //true
true == 'true'// false
[] == 0 //true
[] == false //true
new Boolean('false') == false //false
这里不太符合直觉的有两点:
- 一个是即使字符串与 boolean 比较,也都要转换成数字;
- 二是对象如果转换成了 primitive 类型跟等号另一边类型恰好相同,则不需要转换成数字
来源:CSDN
作者:Lin_Tui_
链接:https://blog.csdn.net/weixin_46124214/article/details/104214925

