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