JS Advance --- ES6 ~ ES12语法(一)

410 阅读5分钟

对象字面量的增强

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

ES6中对 对象字面量 进行了增强,称之为 Enhanced object literals(增强对象字面量)。

属性的简写: Property Shorthand

const name = 'Klaus'
const age = 23

// 传统写法
const obj = {
  name: name,
  age: age
}

// 增强写法
const foo = {
  name,
  age
}

方法的简写: Method Shorthand

// 传统写法
const obj = {
  running: function() {}
}

// 增强写法
const foo = {
  // 注意: 方法简写是普通函数的语法糖,不是箭头函数的语法糖
  running() {}
}

计算属性名: Computed Property Names

计算属性名就是在对象中的属性名可以是变量或合法的JS表达式

const name = 'name'

// 传统写法
const obj = {}
obj[name] = 'Klaus'
obj[name + '123'] = 'Klaus123'

// 增强写法
const foo = {
  [name]: 'Klaus',
  [name + '123']: 'Klaus123'
}

console.log(obj)
console.log(foo)

解构

ES6中新增了一个从数组或对象中方便获取数据的方法,称之为解构Destructuring

数组解构

// 完整解构
const arr = ['Klaus', 'Alex', 'Steven']

const [user1, user2, user3] = arr
/* 
  <=> (等价于)
  const user1 = arr[0],
        user2 = arr[1],
        user3 = arr[2]
*/
// 部分解构
const arr = ['Klaus', 'Alex', 'Steven']
const [, user2, user3] = arr

console.log(user2) // => Alex
console.log(user3) // => Steven
// 解构出变量和数组
const arr = ['Klaus', 'Alex', 'Steven']
const [user1, ...users] = arr

console.log(user1) // => Klaus
console.log(users) // => ['Alex', 'Steven']
// 默认值
const arr = ['Klaus', 'Alex', 'Steven']
const [user1, user2, user3, user4 = 'default value'] = arr

console.log(user4)

对象解构

// 基本解构
const info = {
  name: 'Klaus',
  age: 23
}

const { name, age } = info
// 重命名
const info = {
  name: 'Klaus',
  age: 23
}

// 从info中取属性为name的值赋值给变量userName
// <=> const userName = info.name
// 所以这里只能使用userName,不能使用name
const { name: userName } = info

console.log(userName) // => Klaus
console.log(name) // error
// 默认值
const info = {
  name: 'Klaus',
  age: 23
}

const { address = '上海市' } = info

let/const

在ES5中我们声明变量都是使用的var关键字,从ES6开始新增了两个关键字可以声明变量:let、const

let用来定义变量,const用来定义常量,从直观的角度来说,let/const和var定义变量的方法没有太大的区别

但是在实际使用中,已经使用let/const来定义变量,而不再推荐使用var来定义变量

const 用来定义常量

const name = 'Klaus'
name = 'Alex' // error

但是如果const赋值的是引用类型,那么可以通过引用找到对应的对象,修改对象的内容;

const obj = {
  name: 'Klaus'
}
obj.name = 'Alex'
console.log(obj.name) // => Alex

var vs let/const

let、const不允许重复声明变量
var age = 23
var age = 24
console.log(age) // => 24
let age = 23
let age = 24
console.log(age) // error
作用域提升
console.log(username) // => undefined
var username = 'Klaus'
console.log(username) // error
let username = 'Klaus'

使用let定义变量的时候,变量在执行上下文的词法环境创建出来的时候,变量事实上已经被创建了,只是这个变量是不能被访问的

而作用域提升的定义是在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么我们可以称之为作用域提升

所以let、const没有进行作用域提升,但是会在对应VE对象被创建的时候被创建出来

Window对象添加属性

在全局通过var来声明一个变量,事实上会在window上添加一个属性

但是let、const是不会给window上添加任何属性的

var foo = 'foo'
console.log(foo) // => foo

let baz = 'baz'
console.log(baz) // => undefined

在ES5及以前,V8在实现的时候,全局的VO和window其实指向的是同一个对象,所以使用var定义的变量会被自动挂载到window对象上(也就是会被挂载到GO对象上)

但是自ES6开始,保存变量不在使用VO,而是使用VE(variableEnvrionemnt) [VE的数据结构是HashMap],也就是说window对象和保存数据的对象不在指向同一个对象

(在JS中VE对象的变量是variables_, 所以VE对应的变量都是存储在variables_这个HashMap中)

所以ES6中使用let/const声明的变量是不会存放在window对象上的

为了向下兼容,在ES6及以后的JS版本中,使用var定义变量会在VE存放对应的变量值的同时,也会在window对象上定义对应的变量

如果使用window对象操作某一个变量的同时,也会同步在VE对象上对对应的变量进行相应的操作

块级作用域

在ES5及以前,只会形成两个作用域:全局作用域和函数作用域,是没有块级作用域概念的

{
  var name = 'Klaus'
}

console.log(name) // => Klaus

ES6中新增了块级作用域,并且通过let、const、function、class声明的标识符是具备块级作用域的限制的

但是function比较特殊,虽然在ES6中,function声明的标识符是具备块级作用域的限制的,但是为了兼容之前的JS语法规则

所以使用function声明的标识符看上去依旧是不具备块级作用域限制的

{
  const username = 'Klaus'
  let age = 23
  class Person {}
  function foo() {}
}

foo() // 比较特殊,为了兼容ES5,所以这里是可以被正常调用的
console.log(username) // error
console.log(age) // error
console.log(new Person()) // error

逻辑运算符也会开启块级作用域

条件判断

if (true) {
  let username = 'Klaus'
  var age = 23
}

console.log(age) // => 23
console.log(username) // error

循环语句

// for循环
for (let i = 0; i < 2; i++) {
  var username = 'Klaus'  
}

console.log(username) // => Klaus
console.log(i) // error
// do...while循环
const i = 0
do {
  let username = 'Klaus'
  var age = 23
} while(i > 3)

console.log(age) // => 23
console.log(username) // error

案例

<button id="btn">btn1</button>
<button id="btn">btn2</button>
<button id="btn">btn3</button>

ES5

var btns = document.getElementsByTagName('button')

for (var i = 0; i < btns.length; i++) {
  // 因为在事件处理函数中没有i
  // 所以会去上层作用域中查找
  // 而var使用定义的时候,并没有块级作用域
  // 所以就去全局找,而全局的i是3
  // 所以无论点击那个按钮,数输出的结果都是3
  btns[i].addEventListener('click', () => console.log(i))
}
var btns = document.getElementsByTagName('button')

for (var i = 0; i < btns.length; i++) {
  // 使用IIFE开启一个函数作用域
  // 利用闭包机制,使事件处理函数调用时查找i的时候
  // 去IIFE的VO中进行查找
  (function(n) {
    btns[i].addEventListener('click', () => console.log(n))
  })(i)
}

ES6

var btns = document.getElementsByTagName('button')

for (let i = 0; i < btns.length; i++) {
  // 使用i定义的变量受块级作用域限制
  // 所以事件处理函数在被调用的时候,查找i时候,会找到对应for块级作用域中的i
  // 此时就不需要我们在手动创建函数作用域来保存对应的i
  btns[i].addEventListener('click', () => console.log(n))
}

块级作用域的一些小问题

 for (let i = 0; i < 2; i++) {
   console.log(i)
   /*
     =>
       0
       1
   */
 }
 ​
 /*
  => 可以将上述for循环转换为对应的伪代码
  {
   let i = 0
   console.log(i)
  }
  
  {
   let i = 0
   console.log(i)
  }
 */
 for (const i = 0; i < 2; i++) {
   // 虽然在循环中,在逻辑上市开辟了两个块级作用域
   // 但是实际运行的时候,其内部循环遍历i依旧是会进行++操作
   // 所以这个i是需要改变的,所以不可以使用const来定义循环变量i
   console.log(i) // error
 }
 for (const i of [1, 2]) {
   // 但是在for ... of循环中,i使用let或const定义都是可以的
   // 因为在这里并没有对i进行自增或自减操作
   // 内部实际是使用迭代器来完成的循环功能
   console.log(i)
   /* 
     =>
      1
      2
   */
 }

暂时性死区

 var age = 23
 {
   // 虽然上边使用var定义了age
   // 但是在块级作用域中有使用let定义变量
   // 所以此时使用let定义的age是在解析的时候被识别的
   // 但是在变量实际声明之前是无法访问的
   // 所以这里会报错, 而自块级作用域(或函数作用域)开始到变量实际声明之间这段区域就被称之为暂时性死区(TDZ temporal dead zone)
   console.log(age) // error
   let age = 23
 }