对象字面量的增强
小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
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
}