语法和运算符

语句和表达式

语句:是完整表达某个意思的一组词,由一个或多个'短语'(表达式)组成,他们之间由运算符连接 表达式: 相当于英语中的短语,有些短语不完整,不能表达意思;有些短语相对完整,能够表达某些意思

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'

完结撒花❀

相关推荐
web1350858863528 分钟前
前端node.js
前端·node.js·vim
m0_5127446429 分钟前
极客大挑战2024-web-wp(详细)
android·前端
若川38 分钟前
Taro 源码揭秘:10. Taro 到底是怎样转换成小程序文件的?
前端·javascript·react.js
潜意识起点1 小时前
精通 CSS 阴影效果:从基础到高级应用
前端·css
奋斗吧程序媛1 小时前
删除VSCode上 origin/分支名,但GitLab上实际上不存在的分支
前端·vscode
IT女孩儿1 小时前
JavaScript--WebAPI查缺补漏(二)
开发语言·前端·javascript·html·ecmascript
m0_748256563 小时前
如何解决前端发送数据到后端为空的问题
前端
请叫我飞哥@3 小时前
HTML5适配手机
前端·html·html5
@解忧杂货铺5 小时前
前端vue如何实现数字框中通过鼠标滚轮上下滚动增减数字
前端·javascript·vue.js
F-2H7 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++