语法和运算符

语句和表达式

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

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'

完结撒花❀

相关推荐
光影少年7 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_8 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu108301891110 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾11 分钟前
前端基础-html-注册界面
前端·算法·html
Rattenking12 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
Dragon Wu14 分钟前
前端 Canvas 绘画 总结
前端
CodeToGym18 分钟前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫19 分钟前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫23 分钟前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
Cwhat25 分钟前
前端性能优化2
前端