《重学前端》学习笔记(9)

JavaScript 的文法:JavaScript语法(2):

JavaScript 遵循了一般编程语言的‘语句 - 表达式’结构,多数编程语言都是这样设计的。

脚本,或者模块都是由语句列表构成的,这次,就来一起了解一下语句。
在 JavaScript 标准中,把语句分成了两种:声明和语句,不过,这里的区分逻辑比较奇怪,所以,这里winter按照他的思路整理一下:

  • 普通语句
    在这里插入图片描述
    在这里插入图片描述
  • 声明型语句

在这里插入图片描述

普通语句

语句块

简单理解,语句块就是一对大括号:

{
 var x, y;
 x = 10;
 y = 20;
}

语句块的意义和好处在于:让我们可以把多行语句视为同一行语句,这样,iffor 等语句定义起来就比较简单了。不过,我们需要注意的是,语句块会产生作用域。

{
 let x = 1;
}
console.log(x); // 报

这里我们的let声明,仅仅对语句块作用域生效,于是我们在语句块外试图访问语句块内的变量 x 就会报错。

空语句

空语句就是一个独立的分号,实际上没什么。

空语句的存在仅仅是从语言设计完备性的角度考虑,允许插入多个分号而不抛出错

if语句

if 语句是条件语句。

if语句的作用是,在满足条件时执行它的内容语句,这个语句可以是一个语句块,这样就可以实现有条件地执行多个语句了。
if语句还有 else结构,用于不满足条件时执行,一种常见的用法是,利用语句的嵌套能力,把 ifelse连写成多分支条件判

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中出现了 returnfinally中的语句也一定要被执行。

debugger 语句

debugger 语句的作用是:通知调试器在此断点。在没有调试器挂载时,它不产生任何效果。

声明型语句

var

var 声明语句是古典的 JavaScript 中声明变量的方式。而现在,在绝大多数情况下,letconst 都是更好的选择。

它是一种预处理机制。

如果我们仍然想要使用 var,的个人建议是,把它当做一种“保障变量是局部”的逻辑,遵循以下三条规则:

  • 声明同时必定初始化;
  • 尽可能在离使用的位置近处声明;
  • 不要在意重复声明。

let 和 const

letconst的作用范围是if、for 等结构型语句。

letconst 声明虽然看上去是执行到了才会生效,但是实际上,它们还是会被预处理。如果当前作用域内有声明,就无法访问到外部的变

class 声明

class最基本的用法只需要 class关键字、名称和一对大括号。它的声明特征跟 constlet类似,都是作用于块级作用域,预处理阶段则会屏蔽外部变量

函数声明

函数声明使用 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 ExpressionMember 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 ExpressionCall 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 类型跟等号另一边类型恰好相同,则不需要转换成数字
发布了33 篇原创文章 · 获赞 73 · 访问量 2769

猜你喜欢

转载自blog.csdn.net/weixin_46124214/article/details/104214925
今日推荐