Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

《你不知道的javascript(中)》1.5 语法 #31

Open
qunzi0214 opened this issue Apr 9, 2021 · 0 comments
Open

《你不知道的javascript(中)》1.5 语法 #31

qunzi0214 opened this issue Apr 9, 2021 · 0 comments
Labels
read book 读书笔记

Comments

@qunzi0214
Copy link
Owner

qunzi0214 commented Apr 9, 2021

语句和表达式

在JavaScript中,需要区分语句和表达式,因为它们存在一些重要差别。语句相当于句子,表达式相当于短语,运算符则相当于标点符号和连接词。在JavaScript中,表达式会返回一个结果值

var a = 3 * 6
var b = a
// 3 * 6是一个表达式,第二行的a也是一个表达式
// 这两行都是包含表达式的声明语句

var a
var b = 18
a = b
// 这里第三行只是赋值表达式,结果值是b的值18

语句的结果值

有如下两种情况:

  1. var let const 语句的结果值是 undefined

  2. 代码块 {..} 的结果值是其最后一个表达式/语句的结果值

var b
if (true) {
  b = 4 + 38
}
// chrome 控制台会输出 42

var a, b
a = if (true) {
  b = 4 + 38
}
// 无法运行!

var a, b
a = eval('if (true) { b = 4 + 38 }')
a
// 新版浏览器会报错:Uncaught EvalError,与书中不符。不过一般也没人这么写

表达式的副作用

最常见有副作用的表达式是函数调用(函数调用也是一个表达式,结果值是该函数的返回值):

function foo () {
  a = a + 1
}

var a = 1
foo() // 结果值undefined,副作用:a的值被改变

递增和递减运算符的副作用:

var a = 42
var b = a++

a // 43
b // 42
// 副作用将a自增1

++ or -- 在前时,副作用产生在表达式返回结果前,在后时,副作用产生在表达式返回结果之后

可以使用语句系列逗号运算符,将多个独立的表达式语句串联成一个语句:

var a = 42, b
b = ( a++, a )
a // 43
b // 43
// 使用括号包起来是因为','运算符优先级比'='运算符优先级低

delete 运算符的副作用:

var obj = {
  a: 42
}

obj.a // 42
delete obj.a // true
obj.a // undefined
// 表达式本身返回true或false来表示是否删除成功,副作用是删除了obj的a属性

= 运算符的副作用:

var a;
a = 42 // 42
a // 42
// 表达式本身返回42,副作用将42赋值给a

利用赋值表达式的副作用合并 if 条件

function vowels (str) {
  var matches
  
  if (str) {
    matches = str.match(/[aeiou]/g)
    
    if (matches) {
      return matches
    }
  }
}

// 优化后
function vowels (str) {
  var matches
  
  if (str && (matches = str.match(/[aeiou]/g))) {
    return matches
  }
}

上下文规则

在不同的上下文中 {..} 的含义不尽相同

  1. 对象常量

    var a = {
      foo: bar()
    }
  2. 代码块

    { // 这里并非对象常量,而是一个代码块
      foo: bar() // 合法,标签语句(不建议使用)
    }

    标签语句最大的用处在于,双层循环的时候可以从内循环控制外循环

    foo: for (var i = 0; i < 4; i++) {
      for (var j = 0; j < 4; j++) {
        if (j === i) {
          continue foo // 跳转到foo的下一个循环
        }
        console.log(i, j)
      }
    }
    
    // 1 0
    // 2 0
    // 2 1
    // 3 0
    // 3 1
    // 3 2
    
    foo: for (var i = 0; i < 4; i++) {
      for (var j = 0; j < 4; j++) {
        if (j * i >= 3) {
          break foo // 终止外层循环
        }
        console.log(i, j)
      }
    }
    // 0 0
    // 0 1
    // 0 2
    // 0 3
    // 1 0
    // 1 1
    // 1 2

    一个常见的坑,原因如下:

    • 第一行中 {} 被算作出现在 + 运算符表达式中,[] 会被强制类型转换为 '' ,而 {} 被强制类型转换为 '[object object]'
    • 第二行中,{} 被当做一个空代码块,因此 +[] 被强制类型转换为 0
    [] + {} // '[object object]'
    {} + [] // 0
  3. 对象解构

function getData () {
  return {
    a: 42,
    b: 'foo'
  }
}
var { a, b } = getData()
console.log(a, b) // 42 'foo'

function foo ({ a, b, c }) {
  console.log(a, b, c)
}
foo({
  c: [1, 2, 3],
  a: 42,
  b: 'foo'
}) // 42 'foo' [1, 2, 3]
  1. else if 和可选代码块

实际上JavaScript中并没有 else if 语法,只不过 ifelse 只包含单条语句的时候可以省略代码块的大括号

if (a) {
  // ...
} else if (b) {
  // ...
} else {
  // ...
}

// 实际上是
if (a) {
  // ...
} else {
  if (b) {
    // ...
  } else {
    // ...
  }
}

运算符优先级

MDN运算符优先级列表

需要注意:除了优先级,操作符的左关联 or 右关联也非常重要

自动分号

JavaScript会自动为代码补上缺失的分号,(Automatic Semicolon Insertion, ASI)

ASI只在换行符处起作用,且这个行为只有在代码行末尾与换行符中间除了空格和注释没有其他内容,才会产生

var a = 42, b
c
// 这里c是一条单独的表达式语句

var a = 42, b,
c
// 这里ASI不会生效,c会被当做第一行声明语句的一部分

书中建议在所有需要的地方加上分号,对ASI依赖的程度降到最低,仅为了追求“代码的美观”不值得。然而这件事完全可以交给webpack在打包/编译时处理。时代变了啊大人: )

函数参数

ES6中,参数可以设置默认值,但是需要注意暂时性死区(Temporal Dead Zone, TDZ)

function foo (a = 42, b = a + b + 5) {
  // ...
}
// 报错!b的默认值在b声明前就调用了b!但是访问a没有问题

有参数默认值的情况下,参数被省略或被赋值为 undefined 参数取值是一样的,但是 arguments 会产生如下问题

function foo (a = 42, b = a + 1) {
  console.log(
  	arguments.length,
    a,
    b,
    arguments[0],
    arguments[1]
  )
}

foo() // 0 42 43 undefined undefined
foo(10) // 1 10 11 10 undefined
foo(10, undefined) // 2 10 11 10 undefined
foo(10, null) // 2 10 null 10 null

除此之外,向函数传递参数时,arguments 数组中的对应单元会和命名参数建立关联(严格模式下不关联)

function foo (a) {
  a = 42
  console.log(arguments[0])
}
foo(2) // 42 关联了
foo() // undefined 未关联

function foo (a) {
  'use strict'
  a = 42
  console.log(arguments[0])
}
foo(2) // 2
foo() // undefined

因此,尽量不去使用 arguments ,本身它也是JavaScript语言底层实现的一个抽象泄露。ES6之后,完全可以用剩余参数语法来代替

try .. finally

关于执行顺序,这里 try 先执行,将函数的返回值设置为42,接着执行 finally 。最后函数执行完毕

function foo () {
  try {
    return 42
  } finally {
    console.log('hi')
  }
  console.log('never run')
}
console.log(foo())
// hi
// 42

如果包含 throw 也是如此

function foo () {
  try {
    throw 42
  } finally {
    console.log('hi')
  }
}
console.log(foo())
// hi
// Uncaught Exception: 42

但是当 finally 抛出异常,则函数执行会终止,try 中的返回值会被丢弃

function foo () {
  try {
    return 42
  } finally {
    throw 'oops'
  }
}
console.log(foo())
// Uncaught Exception: 'oops'

finally 存在返回值时,会覆盖 trycatch 中的返回值

function foo () {
  try {
    return 42
  } finally {
    return 43
  }
}
console.log(foo())
// 43

甚至还可以将 finally 和带标签的 break 语句一起使用(千万不要)

function foo () {
  bar: {
    try {
      return 42
    } finally {
      break bar
    }
  }
  console.log('crazy')
  return 'hi'
}
console.log(foo())
// 'crazy'
// 'hi'

switch

switch 语句中,变量会逐一与 case 表达式比较(严格相等)。需要注意的是,即使 case 表达式中使用了宽松相等,但是在 switch 内部仍然是严格相等的比较。

var a = 'hi'
var b = 10

switch (true) {
  case (a || b == 10):
    // 不会执行到这里,因为 a || b == 10 的结果为'hi',真值与true不严格相等
    break;
  default:
    console.log('oops')
}
// 'oops'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
read book 读书笔记
Projects
None yet
Development

No branches or pull requests

1 participant