let
和 const
是 ES6(ES2015)新增的两个关键字,用于声明变量和常量。它们的作用域都是块级的。(块级作用域由最近的一对大括号 {}
界定)
使用 var 声明变量时,变量会自动地被添加到最接近的上下文。在函数中,最接近的上下文就是函数的局部上下文。如果变量未经声明就被初始化了,那么它就会自动地被添加到全局上下文。
var | let | const | |
---|---|---|---|
作用 | 声明一个函数内的局部变量或全局变量 | 声明一个块级作用域的局部变量 | 声明一个只读的块级作用域的局部常量 |
作用域 | 函数内 / 全局 | 代码块内 | 代码块内 |
初始化 | 可选 | 可选 | 必须 |
重复声明 | 可以 | 不可以 | 不可以 |
修改值 | 可以 | 可以 | 不可以 |
变量提升 | 存在 | 不存在 | 不存在 |
变量提升(Hoisting):在编译阶段,函数及变量的声明都将先被放入内存中(函数比变量会先被提升)。这会导致在变量声明之前就可以使用变量。【更多内容可参考附录6.1】
console.log(name); // undefined var name = 'Jake'; console.log(age); // Uncaught ReferenceError: a is not defined let age = 19;
【注意】:
由于 let 和 const 不存在变量提升,所以必须先声明后使用。
// 正确用法
let num;
num = 1;
let age = 20;
// 错误用法
num = 1;
let num;
const 声明的常量的值是无法改变的。对于基本类型(Number、Boolean、String等)的常量而言,其存储的值是原始值,这个值是常量存储的数据,不可被改变;
对于复杂类型(Object、Array、Function等)的常量而言,其存储的是引用值,这个值是对象所在内存空间的地址,不可被改变,但是对象本身的属性的值可以被修改。
JavaScript 中变量值的存储方式类似于 C++ 和 Java,对于复杂类型的变量值理解可参考 C++ 指针或 Java 引用。【更多内容可参考附录6.2】
声明变量时建议不再使用 var,const 优先,let 次之。
解构赋值是一种 JavaScript 表达式,是对赋值运算符的扩展,可以将数组的值或对象的属性取出来,赋值给其他变量。
let [a, b, c] = [1, 2, 3]
// a = 1, b = 2, c = 3
let { foo, bar } = { foo: '111', bar: 222 }
// foo = '111', bar = 222
【注意】:null 和 undefined 不能被解构,否则会抛出错误。
let { x } = null; // TypeError
let { x } = undefined; // TypeError
let obj = { a: ['111'], b: 222, c: [333, 444] }
let { a: [x], b, c: [y, z] } = obj
// x = '111', b = 222, y = 333, z = 444
// a 和 c 不是变量,是对象属性名
let arr = [111, '222', { x: 333, y: '444' }]
let [a, , { y: b }] = arr
// a = 111, y = b = 333
let [a = 1, b, c = 333, d] = [111, '222']
// a = 111, b = '222', c = 333, d = undefined
剩余操作符可以在解构对象时将所有剩下未指定的可枚举属性收集到一个对象中
let [a, ...b] = [1, 2, 3]
// a = 1
// b = [2, 3]
let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}
// a = 10
// b = 20
// rest = {c: 30, d: 40}
每个对象字面量中最多可以使用一次剩余操作符,而且必须放在最后。
const person = { name: 'Matt', age: 27, job: { title: 'Engineer', level: 10 } };
const { name, job: { title, ...otherJobData }, ...otherPersonData } = person;
剩余操作符在对象间执行浅复制,因此会复制对象的引用而不会克隆整个对象。
const person = { name: 'Matt', age: 27, job: { title: 'Engineer', level: 10 } };
const { ...personData } = person;
console.log(person === personData); // false
console.log(person.job === personData.job); // true
在 ES5 以前,函数实现默认参数的一种常用方式是检测某个参数是否等于 undefined,如下所示:
function getAvatar(avatar) {
avatar = (typeof avatar !== undefined) ? avatar : 'defaultURL'
return `avatar url: ${avatar}`
}
而在 ES6 中支持了显式地给函数参数定义默认值了,就可以写作如下的代码:
function getAvatar(avatar = 'defaultURL') {
return `avatar url: ${avatar}`
}
【注意】:
只有在未传递参数,或者参数为 undefined 时,才会使用默认参数,null 值被认为是有效的参数值;
function f(name, age = 18) {
console.log(name, age)
}
f('Jake') // Jake, 18
f('Jake', null) // Jake, null
通常情况下,把定义了默认值的参数放到函数参数的尾部,这样比较方便地看出哪些参数可以省略。
如果给非尾部的函数参数设置默认值,则会遇到不可省略的情况,如下所示:
function fn(x, y = 5, z) {
console.log([x, y, z])
}
fn(1) // [1, 5, undefined]
fn(1, ,2) // 报错
fn(1, undefined, 2) // [1, 5, 2]
ES6 引入 rest 参数(形式为 ...变量名
),用于获取函数的剩余参数,这样就不需要使用 arguments
对象了。
rest 参数搭配的变量是一个数组,该变量将剩余的参数放入数组中。
function getSum(...args) {
let sum = 0;
for(let arg of args) {
sum += arg
}
console.log(sum)
}
getSum(1, 2, 3) // 6
getSum(1, 2, 3, 4, 5) // 15
【注意】:rest 参数必须放在函数参数的尾部, 否则会报错。
ES6 新增了使用 =>
语法定义函数表达式的能力。
let f1 = function(a, b) {
return a + b
}
// 等价于
let f2 = (a, b) => {
return a + b
}
console.log(f1(6, 9), f2(6, 9)) // 15, 15
当箭头函数不需要参数或者需要两个及以上的参数时,则使用一个圆括号 ()
代表参数部分。
当箭头函数只需要一个参数时,圆括号 ()
可以省略。
let f1 = () => {
return 'f1'
}
let f2 = a => {
return a * a
}
当箭头函数的代码块部分多于一条语句时,就要使用大括号 {}
将它们括起来,并且使用 return
语句返回。
当箭头函数的代码块部分只有一条语句时,大括号 {}
和 return
语句可以省略。
let f1 = () => 'f1'
let f2 = a => {
let pow_a = a * a
console.log(pow_a)
return pow_a
}
由于大括号 {}
被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。
let fn = (pid, pname) => ({id: pid, name: pname})
箭头函数的一个用处是简化回调函数。
// 普通函数写法
[1,2,3].map(function (x) {
return x * x
});
// 箭头函数写法
[1,2,3].map(x => x * x)
【注意】:箭头函数体内的 this
对象,是定义时所在的对象,而不是使用时所在的对象。
// 普通函数
const person1 = {
name: 'Jake',
greeting: function() {
setTimeout(function() {
console.log('Hello, My name is ' + this.name)
})
}
}
person1.greeting() // Hello, My name is undefined
// 箭头函数
const person2 = {
name: 'Jake',
greeting: function() {
setTimeout(() => {
console.log('Hello, My name is ' + this.name)
})
}
}
person2.greeting() // Hello, My name is Jake
Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理、更强大。
ES6 将其写入语言标准,形成一种新的引用类型 Promise,可以通过 new 操作符来实例化。
Promise 对象表示异步操作最终的完成(或失败)以及其结果值。
一个 Promise 必然处于以下3种状态之一:
一个待定的 Promise 最终状态可以是已兑现并返回一个值,或者是已拒绝并返回一个原因(错误)。
【缺点】:
实例化 Promise 对象时需要传入两个执行器函数作为参数,它们通常都命名为 resolve()
和 reject()
。
resolve()
函数的作用是将 Promise对象 的状态从 “待定” 切换为 “兑现”(即从 pending 变为 fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject()
函数的作用是将 Promise 对象的状态从 “待定” 切换为 “拒绝”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作的错误,作为参数传递出去。
const promise = new Promise((resolve, reject) => {
// ... 异步操作代码
if (/* 异步操作成功 */){
resolve(value)
} else {
reject(error)
}
})
Promise.prototype.then()
是用来处理 Promise 对象执行异步操作产生的结果的主要方法。该方法支持链式调用。
这个 then()
方法可以接收两个回调函数作为参数,这两个参数都是可选的,如果提供的话, 则会在 Promise 对象分别进入 “兑现” 和 “拒绝” 状态时执行。
then()
方法的返回值是一个 resolved 或 rejected 状态的 Promise 对象。
const promise = new Promise((resolve, reject) => {
resolve(111)
})
promise.then((res) => {
console.log(res) // 111
return res * 2 // then() 方法会自动封装成一个处于 resolved 状态的 Promise 对象
}).then((res) => {
console.log(res) // 222
return Promise.resolve(res)
}).then((res) => {
console.log(res) // 222
}).then((res) => {
console.log(res) // undefined
return Promise.reject('promise rejected')
}).then((res) => {
console.log(res) // 该语句不会执行
}, (err) => {
console.log(err) // promise rejected
})
【注意】:由于Promise 的状态只会从 “待定” 切换为 “兑现”,或者从 “待定” 切换为 “拒绝”,并且只会转换一次。
const promise1 = new Promise((resolve, reject) => {
resolve('fulfilled')
reject('rejected')
})
promise1.then(res => {
console.log(res) // fulfilled
})
// 链式调用
const promise2 = new Promise((resolve, reject) => {
resolve('fulfilled1')
resolve('fulfilled2')
}).then(res => {
console.log(res) // fulfilled1
})
Promise.prototype.catch()
方法是 .then(null, onRejected)
的别名,用于指定 Promise 对象执行异步操作发生错误时的回调函数。
const promise = new Promise((resolve, reject) => {
// ... 异步操作代码
if (/* 异步操作成功 */){
resolve(value)
} else {
reject(error)
}
})
// 常用写法
promise.then((res) => {
console.log(res)
}).catch((err) => {
console.log(err)
})
// 等同于
promise.then((res) => {
console.log(res)
}, (err) => {
console.log(err)
})
Promise.race()
和 Promise.all()
方法都用于将多个 Promise 实例,包装成一个新的 Promise 实例。二者的最大不同之处是对新的 Promise 实例状态改变的影响方式。
// p1, p2, p3 均为 Promise 实例,这里省略初始化
const promise_race = Promise.race([p1, p2, p3])
// 对于 promise_race 实例而言,只要 p1, p2, p3 之中有一个实力率先改变状态,promise_race 的状态就随之改变,且那个率先改变的 promise 实例的返回值,会传递给 promise_race 的回调函数
const promise_all = Promise.all([p1, p2, p3])
// 对于 promise_race 实例而言,只有 p1、p2、p3 的状态都变成 fulfilled,promise_race 的状态才会变成 fulfilled,此时 p1、p2、p3 的返回值组成一个数组,传递给 promise_race 的回调函数;只要 p1、p2、p3 之中有一个被 rejected,promise_race 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 promise_race 的回调函数。
async / await 是 ES2017 新增的两个关键字,可以让以同步方式写的代码能够异步执行。
async 关键字用于声明异步函数。它可以用在函数声明、函数表达式、箭头函数和方法上:
aysnc function foo() {}
let f1 = async function() {}
let f2 = async () => {}
class Demo {
async fn() {}
}
使用 async 关键字可以让函数具有异步特征,但总体上代码仍然是同步求值的。而在参数或闭包方面,异步函数仍然具有普通 JavaScript 函数的正常行为。
async function fn() {
console.log(1)
}
fn()
console.log(2)
// 1
// 2
如果异步函数使用了 return 关键字返回了值(如果没有 return 则会返回 undefined),这个返回值会被 Promise.resolve()
包装为一个 Promise 对象。
异步函数始终返回 Promise 对象。
async function fn() {
console.log(1)
return 3 // 等价于 return Promise.resolve(3)
}
fn().then(res => console.log(res))
console.log(2)
// 1
// 2
// 3
await 关键字可以暂停通过 async 声明的异步函数中代码的执行,等待 Promise 对象处理完成。
function getApiData() {
return new Promise((resolve, reject) => {
// 模拟延迟(获取服务的数据)
setTimeout(() => {
// 执行成功
resolve({ name: 'Jake', age: 18 })
}, 2000)
})
}
async function getUser() {
// 执行
const res = await getApiData()
// await 后的代码在此处会阻塞,直到 getApiData 执行完成后才能继续往下执行
let user = res
console.log('获取数据完成: ', user)
}
getUser() // 异步执行的方法
console.log('开始获取数据:') // 此处无阻塞(同步代码)
// 开始获取数据: // 直接输出
// 获取数据完成: { name: 'Jake', age: 18 } // 2s后输出此行结果
【注意】:await 只能用于通过 async 声明的异步函数中,用在普通函数中会报错。
// 注意: getUser() 函数并未使用 async 声明
function getUser() {
let user = await getApiData()
console.log(user)
}
// Uncaught SyntaxError: await can not be used when evaluating code while paused
浅谈 JavaScript 变量提升 - 掘金 (juejin.cn)
JavaScript中的变量在内存中的具体存储形式_- CSDN博客