语句和表达式
语句:是完整表达某个意思的一组词,由一个或多个'短语'(表达式)组成,他们之间由运算符连接 表达式: 相当于英语中的短语,有些短语不完整,不能表达意思;有些短语相对完整,能够表达某些意思
css
var a = 3 * 6
var b = a
b
这里,3*6是一个表达式(结果为18),第二行的a也是一个表达式,第三行的b也是。表达式的a和b的结果都是18
同时,这三行代码都是包含表达式的语句。var a = 36和var b = a称为'声明语句',因为他们声明了变量。而a = 36和b = a(不带var),叫做'赋值表达式'
第三行只有一个表达式b,同时也是一个语句(虽然没有太大意义),这叫做'表达式语句'
1.语句的结果值
很多人不知道,语句都有一个结果值(underfined也算)。
获得结果值最直接的方法是在浏览器开发者工具控制台中输入语句,默认情况下控制台会显示所执行的最后一条语句的结果值
以赋值表达式b = a为例,其结果值是赋给b的值(18),但规范定义var的结果值是underfined,如果在控制台上输入var a = 42会得到结果值underfined而并非42
如果用开发控制台调试过代码,应该会看到很多语句的返回值都是underfined,只是可能并没有探究过其中的原因,其实控制台中显示的就是最后一条语句的结果值。但是我们在代码中获取这个结果值是比较复杂的,首先得弄清楚为什么要获得语句的结果值。
先来看看其他语句的结果值。比如代码块{}的结果值是其最后一个语句/表达式的结果,例如:
csharp
var b
if(true){
b = 4+38
}
在控制台上输入以上的代码,应该会输出42(注意,这和var b = 4 + 38不一样哦),即返回最后一个语句/表达式的结果值。
但是以下代码是不能执行的
css
var a,b
a = if(true){
b = 4+38
}
因为语法不允许我们获得语句结果值并将其赋值给另一个变量,那应该怎么获取语句的结果值呢?可以用eval()
less
var a,b
a = eval("if(true){b = 4+38}")
a //42
众所周知,eval()能不用就不用,实际上,ES7规范有一项'do表达式'提案,可以让我们获取语句的结果值
css
var a,b
a = do{
if(true){
b = 4 + 38
}
}
a
上例中,do{}表达式执行一个代码块,并返回其中最后一个语句的结果值,然后赋值给变量a。他的目的是将语句当做表达式来处理,而不用将语句封装成函数再用return返回47
2.上下文规则
在JavaScript语法规则中,有时候同样的语法在不同情况下会有不同的解释,这些语法孤立起来很难理解
1. 大括号
- 对象常量
css
var a = {foo:bar()}
{...}被赋值给了a,因为a是一个对象常量
- 标签
css
{
foo:bar()
}
这并不是一个被孤立的对象常量,没有发生赋值,实际上{...}只是一个普通的代码块,在语法上是完全合法的,尤其与let在一起时非常有用
2. 代码块
这里有一个坑被经常提到
less
[] + {} //"[object Object]"
{} + [] //0
在第一行代码中{}出现在+运算符表达式中,因此可以看成一个空数组和空对象的相加,需要通过ToPrimitive抽象操作(不清楚什么是ToPrimitive抽象操作可以看看这个5k字讲透强制类型转换 - 掘金 (juejin.cn))将其转化为字符串,而[]转化为'',{}则转化为"[object Object]",相加就是结果
在第二行代码中,{}被当成一个代码块(不执行任何操作)因此实际上就相当于+[],自然就被转化成0了
3. 对象解构
ES6开始,{...}可以被用于'解构赋值'
css
function getData(){
return {
a:42,
b:'foo'
}
}
var {a,b} = getData()
a //42
b //'foo'
另外一种解构可能比较少人知道,{...}可以用作函数命名参数的对象解构
css
function foo({a,b,c}){
//不再需要这样
//var a = obj.a,b = obj.b,c = obj.c
console.log(a,b,c);
}
foo({
a:42,
c:1,
b:333
})
4.else if
实际上JavaScript是没有else if的,只是if和else值包含单条语句的时候可以忽略代码块{...}
scss
if(a) doSomething(a)
//实际的else if
if(a){
}else{
if(b){}
}
运算优先级
1. &&和||和三元运算符
==和===的优先级比&&高,&&的优先级比||的优先级更高,因此代码的执行顺序不总是从左到右。而||的优先级比三元运算符高
bash
true || false && false //true,先执行false && false
2.关联
&&的优先级比大于||大于三元运算符,那么当多个相同优先级的运算符同时出现时,该怎么处理呢
像a && b && c这样的组合就涉及到组合,意味着a && b或者b && c会先被执行
从技术角度来说,&&运算符是左关联,因此会被处理成(a && b) && c。但是需要注意的是,即便是右关联被处理成a && (b && c),并不是指从右往左执行,而是从右往左组合,也就是不会先执行(b && c),无论怎么组合,严格的执行顺序都是a,b,c
css
a ? b : c ? d : e
对于三元运算符来说,他是右关联的(即先执行右边的),即上述代码会被处理成
less
a ? b : (c ? d : e)
同样是右关联的还有=运算符
css
var a,b,c
a = b = c = 42
他首先会执行c = 42,然后是b = ,然后是a = ,因为是右关联,所以他实际上是a=(b=(c=42))
less
var a = 42
var b = 'foo'
var c = false
var d = a && b || c ? c || b ? a : c && b : a //42
根据优先级以及关联,这里会被处理成
less
((a && b) || c) ? ((c || b) ? a :( c && b)) : a
3. try..finally
try..catch..finaly的作用我们非常熟悉,finally中的代码总是会在try之后执行,如果有catch的话则在catch之后执行,也可以将finally中的代码当成一个回调函数,即无论出现什么情况都会被调用。
javascript
function foo(){
try {
return 42
}
finally {
console.log('hello');
}
console.log('never runs');
}
console.log(foo());
//hello
//42
这里首先执行foo()函数,先执行try中的return 42,但此时foo()还未执行完,因此不会立刻输出42。接着执行finally,输出hello,此时再输出42。即在try中无论是return还是throw抛出一个错误或者是continue,最后finally的代码还是会执行
如果finally中抛出了异常(无论是有意还是无意),函数就会在此处终止,如果此前在try中已经有了return返回值,则该值也会被丢弃
在finally中return会覆盖try和catch中return的返回值。
4.switch
switch可以看成if...else if..else的简化关系
arduino
switch(a){
case 2:
//执行一些操作
break;
case 22:
//执行一些操作
break;
default:
//执行缺省代码
}
这里的a和case表达式逐一进行比较,如果匹配就执行case中的代码,直到break结束代码,但是这里还是有一些不太为人知的陷阱:
1.a和case表达式的匹配算法与===相同,即不会有隐式数据类型转换
2.有时可能需要强制类型转换的话需要进行一些处理
arduino
var a = '42'
switch(true){
case a == 10:
console.log("10 or '10'");
break;
case a == 42:
console.log("42 or '42'");
break;
default:
//执行缺省代码
}
case中的表达式的结果值会和true进行比较,因为a == 42的结果为true,所以条件成立。但如果返回的结果并不是严格意义上的true,那么条件不会成立
csharp
var a = 'hello world'
var b = 10
switch(true){
case (a || b == 10):
//永远不会走到这里
break;
default:
//执行缺省代码
}
(a || b == 10)由于==的优先级比较高,因此这里的组合为(a || (b == 10)),返回值为'hello world',这不是严格意义上的true,而是真值,可以通过强制类型转换!!(a || (b == 10))来返回true
javascript
var b = 10
switch(b){
case 1:
case 3:
default :
console.log('default');
case 11:
console.log(11);
case 'a':
console.log('a');
break
case 6 :
console.log(6);
}
//default
//11
//'a'
这段代码是这样执行的,首先遍历并找到所有匹配的case,如果没有匹配则执行default中的代码,因为其中没有break,所以继续执行已经遍历过的11和'a'
完结撒花❀