变量提升
变量的提升,提升的是变量的声明,而不是变量的赋值。值保留在原地
- 定义变量,JS解析代码。⚠️:隐式变量不会提升(c = 3)
// JS定义变量
var a = 1
var b = 2
// JS解析代码
var a
var b
a = 1
b = 2
- 函数中定义变量的提升
// 定义一个函数,函数中存在变量
function fn(){
var a= 1
console.log(a)
console.log(b)
var b = 2
}
fn()
// JS解析函数
function fn(){
var a
var b
a = 1
console.log(a) // 1
console.log(b) // undefined
b = 2
}
fn()
总结:变量在声明提升的时候,是全部提升到作用域的最前面,一个接着一个的。但是在变量赋值的时候就不是一个接着一个赋值了,而是赋值的位置在变量原本定义的位置。原本js定义变量的地方,在js运行到这里的时候,才会进行赋值操作,而没有运行到的变量,不会进行赋值操作。
函数提升
普通函数提升
function foo(){
console.log(2)
}
foo() // 2
函数表达式提升(提升的是变量,函数保留在原地)
var foo = function (){
console.log(2)
}
foo()
// 代码解析
foo() // foo is not a function
var foo = function(){
console.log(2)
}
函数覆盖 (如果一个函数被重复声明,后面的声明会覆盖前面的)
1:都是用function 声明,后面的会覆盖前面的
function a(){
console.log(1)
}
a() // 2
function a(){
console.log(2)
}
a() // 2
// 两个都打印出2
2:function声明又有函数表达式,返回的是函数表达式的值
var a = function (){
console.log(1)
}
function a(){
console.log(2)
}
a() // 1
// 相当于
var a
function a(){
console.log(2)
}
a = function(){
console.log(1)
}
a()
变量提升、函数提升的顺序
在作用域中,不管是变量还是函数,都会提升到作用域最开始的位置,不同的是,函数的提升后的位置是在变量提升后的位置之后的。
function fn(){
console.log(a)
var a = 1
console.log(a)
function a(){}
console.log(a)
}
fn()
// JS 解析后的代码
function fn(){
var a
function a(){}
console.log(a) // a()
a = 1
console.log(a) // 1
console.log(a) // 1
}
fn()
JS--静态作用域(JS的作用域在定义的时候就确定了)
var a = 1
function test(){
console.log(a)
}
function bar (){
var a = 2
test()
}
bar() // 1
静态作用域执行过程
作用域查找始终从运行时所处的最内层作用域开始查找,逐级向外查找,直到遇见第一个匹配的标识符为止。
无论函数在哪里被调用,无论如何被调用,它的作用域只由函数定义所处的位置决定。
var a = 1
function fn1(){
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
function fn2(){
console.log(a) // 1
}
var fn = fn1()
fn()
// 解析
`fn2` 定义在全局上,当 `fn2` 中找不到变量 `a` 时,它会去全局中寻找,
与 `fn1` 和 `fn3` 毫无关系,打印 `1`.
var a = 1
function fn1(){
function fn2(){
console.log(a) // 2
}
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
var fn = fn1()
fn()
// 解析
`fn2` 是定义在函数 `fn1` 内部,因此当 `fn2` 内部没有变量 `a` 时,
它会去 `fn1` 中寻找,跟函数 `fn3` 毫无关系,如果 `fn1` 中寻找不到,
会到 `fn1` 定义的位置的上一层(全局)寻找,直至寻找到第一个匹配的标识符。
本题可以在 `fn1` 中找到变量 `a`,打印 `2`
var a = 1;
function fn1(){
function fn3(){
function fn2(){
console.log(a) // undefined
}
var a;
fn2()
a = 4
}
var a = 2
return fn3
}
var fn = fn1()
fn()
// 解析
`fn2` 定义在函数 `fn3` 中,当 `fn2` 中找不到变量 `a` 时,会首先去 `fn3` 中查找,
如果还查找不到,会到 `fn1` 中查找。本题可以在 `fn3` 中找到变量 `a`,
但由于 `fn2()` 执行时,`a` 未赋值,打印 `undefined`。
event loop执行顺序
1.先执行同步代码,所有同步代码都在主线程上执行,形成一个执行栈。
2.当遇到异步任务时,会将其挂起并添加到任务队列中,宏任务放入宏任务队列,微任务放进微任务队列。
3.当执行栈为空时,事件循环从任务队列中取出一个任务,加入到执行栈中执行。先执行微任务,后执行宏任务
4.重复上述步骤,直到任务队列为空。
- 同步代码先执行:console.log()、new.Promise()、
- 异步代码---微任务:Promise.then()、async/await、process.nextTick
- 异步任务---宏任务:setTimeout、I/O
Promise
Promise 基础
promise状态没有发生改变时,返回的promise是pending状态【出现promise.then(()=>{})不会执行】
new Promise((resolve,reject)=>{
console.log('promise1')
})
promise.then(()=>{
console.log(2) // 不会解析
})
console.log('1',promise1)
结果:
promise1
1 Promise{<pending>}
Promise+setTimeout
new Promise()中代码执行[从上到下执行],但是状态没有发生改变,则promise.then不会执行
const promise = new Promise((resolve, reject) => {
console.log(1); -----> 第1步,输出1
setTimeout(() => {
console.log("timerStart");-----> 第4步,输出timerStart
resolve("success");
console.log("timerEnd");-----> 第5步,输出timerEnd
}, 0);
console.log(2); -----> 第2步,输出2
});
promise.then((res) => {
console.log(res);-----> 第6步,输出success
});
console.log(4); -----> 第3步,输出4
当setTimeout中有异步任务中的微任务时,会执行
setTimeout(() => {
console.log('timer1'); -----> 第2步,输出timer1
Promise.resolve().then(() => {
console.log('promise') -----> 第3步,输出promise
})
}, 0)
setTimeout(() => {
console.log('timer2') -----> 第4步,输出timer2
}, 0)
console.log('start') -----> 第1步,输出start
Promise.resolve().then(() => {
console.log('promise1'); -----> 第2步,输出promise1
const timer2 = setTimeout(() => {
console.log('timer2') -----> 第5步,输出timer2
}, 0)
});
const timer1 = setTimeout(() => {
console.log('timer1') -----> 第3步,输出timer1
Promise.resolve().then(() => {
console.log('promise2') -----> 第4步,输出promise2
})
}, 0)
console.log('start'); -----> 第1步,输出start
Promise中的then、catch、finally
Promise的状态一经改变就不能再次改变
.then和.catch都会返回一个新的Promise,catch不管被连接到哪里,都能捕获上层未捕捉过的错误,且catch后面的还会继续执行
const promise = new Promise((resolve,reject)=>{
reject('error')
resolve('success')
})
promise.then(res=>{
console.log('then1',res)
}).catch(err=>{
console.log('catch',err) --->第一步 catch error
}).then(res=>{
console.log('then2',res) --->第二步 then2 undefined
})
Promise的状态一经改变,并且有一个值,那么后续每次调用.then或者.catch时都会拿到该值
Promise.resolve(1)
.then(res=>{
console.log(res) ---> 1
return 2 //相当于返回 return Promise.resolve(2)
}).then(res=>{
console.log(res) ---> 2
})
在Promise中,返回任意一个非promise的值都会被包裹成promise对象
Promise.resolve().then(()=>{
return new Error('error') //相当于return Promise.resolve(new Error('error'))
}).then(res=>{
console.log('then',res) ----> then error
})
Promise.reject().then(()=>{
return new Error('error') //相当于return Promise.reject(new Error('error'))
}).then(res=>{
console.log('then',res)
}).catch(err=>{
console.log('catch',err) ----> catch error
})
.then或.catch返回值不能是promise本身,会造成死循环
const promise = Promise.resolve().then(() => {
return promise;
})
promise.catch(console.err) // 报错:Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
.then或.catch的参数期望是函数,传入非函数则会发生值透传
Promise.resolve(1)
.then(2) // 传入的是数字类型
.then(Promise.resolve(3)) // 传入的是对象类型
.then(console.log) ----> 1
.then函数中的两个参数,第一个参数处理Promise 成功的函数,第二个处理失败的函数
Promise.reject('err')
.then((res)=>{
console.log('success',res)
},(err)=>{
console.log('error',err) ----> error err
}).catch(err=>{
console.log('catch',err) // 不会执行
})
Promise.reject('err')
.then((res)=>{
console.log('success',res)
}).catch(err=>{
console.log('catch',err) ---> catch err
})
Promise.all()接收一组异步任务,并行执行,等所有异步操作执行完后才回调
Promise.race()接收一组异步任务,并行执行,只保留第一个执行完成的异步操作结果
async/await【async中的await命令是一个Promise对象,返回该对象的结果】
async中的await会阻塞async后面的代码,跳出async执行后面的
async function async1(){
console.log('1') ---> 第一步 1
await async2()
console.log('2') ---> 第四步 2
}
async function async2(){
console.log('3') --->第二步 3
}
async1()
console.log('4') ---> 第三步 4
async + setTimeout
async function async1() {
console.log("async1 start"); ---> 第一步
await async2();
setTimeout(() => {
console.log('timer1') ---> 第七步
}, 0)
console.log("async1 end"); ---> 第四步
}
async function async2() {
setTimeout(() => {
console.log('timer2') ---> 第五步
}, 0)
console.log("async2"); ---> 第二步
}
async1();
setTimeout(() => {
console.log('timer3') ---> 第六步
}, 0)
console.log("start") ---> 第三步
async中,await后面的内容是一个异常或者错误,会抛出错误,终止错误结果,不会继续向下执行
字节面试题
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(() => {
console.log("setTimeout");
}, 0);
requestAnimationFrame(() => {
console.log("requestAnimationFrame");
});
async1();
new Promise(resolve => {
console.log("promise1");
resolve();
}).then(() => {
console.log("promise2");
});
console.log("script end");
执行步骤详解:
-
console.log("script start");- 这是同步代码,立即执行。
- 输出:
script start
-
setTimeout(() => { console.log("setTimeout"); }, 0);setTimeout是一个宏任务。它被添加到Web API中,等待0毫秒后(实际上是尽可能快地)将其回调函数放入宏任务队列。- 当前状态: 宏任务队列:
[setTimeout回调]
-
requestAnimationFrame(() => { console.log("requestAnimationFrame"); });requestAnimationFrame(RAF) 是一个特殊的异步任务,它不属于宏任务或微任务队列。它的回调会在浏览器下一次重绘之前执行。- 当前状态: RAF队列:
[requestAnimationFrame回调]
-
async1();-
调用
async1函数,进入函数内部。 -
console.log("async1 start");:同步代码,立即执行。 -
输出:
async1 start -
await async2();:- 调用
async2函数,进入函数内部。 console.log("async2");:同步代码,立即执行。- 输出:
async2 async2函数执行完毕并返回一个resolved的Promise。await会暂停async1的执行,并将async1中await后面的代码(即console.log("async1 end");)作为微任务添加到微任务队列。
- 调用
-
当前状态: 微任务队列:
[async1剩余代码]
-
-
new Promise(resolve => { console.log("promise1"); resolve(); }).then(() => { console.log("promise2"); });Promise的构造函数是同步执行的。console.log("promise1");:同步代码,立即执行。- 输出:
promise1 resolve()被调用,Promise状态变为resolved。.then()的回调函数(即console.log("promise2");)被添加到微任务队列。- 当前状态: 微任务队列:
[async1剩余代码, promise2回调]
-
console.log("script end");- 这是同步代码,立即执行。
- 输出:
script end
至此,所有同步代码执行完毕,调用栈清空。事件循环开始检查微任务队列。
-
执行微任务队列
- 事件循环从微任务队列中取出第一个微任务:
async1中await后面的代码。 console.log("async1 end");:执行。- 输出:
async1 end - 事件循环继续从微任务队列中取出下一个微任务:
promise2的回调。 console.log("promise2");:执行。- 输出:
promise2 - 微任务队列清空。
- 事件循环从微任务队列中取出第一个微任务:
-
浏览器渲染阶段 (如果有)
- 在执行下一个宏任务之前,浏览器可能会进行一次渲染。此时,如果存在RAF回调,它们会在渲染之前被执行。
console.log("requestAnimationFrame");:执行。- 输出:
requestAnimationFrame - RAF队列清空。
-
执行宏任务队列
- 事件循环从宏任务队列中取出第一个宏任务:
setTimeout的回调。 console.log("setTimeout");:执行。- 输出:
setTimeout - 宏任务队列清空。
- 事件循环从宏任务队列中取出第一个宏任务:
最终输出顺序:
script start
async1 start
async2
promise1
script end
async1 end
promise2
requestAnimationFrame
setTimeout