前端面試 代碼輸出篇

2023-02-17 10:50 更新

前言:

代碼輸出結(jié)果也是面試中??嫉念}目,一段代碼中可能涉及到很多的知識(shí)點(diǎn),這就考察到了應(yīng)聘者的基礎(chǔ)能力。在前端面試中,??嫉拇a輸出問(wèn)題主要涉及到以下知識(shí)點(diǎn):異步編程、事件循環(huán)、this指向、作用域、變量提升、閉包、原型、繼承等,這些知識(shí)點(diǎn)往往不是單獨(dú)出現(xiàn)的,而是在同一段代碼中包含多個(gè)知識(shí)點(diǎn)。所以,筆者將這些問(wèn)題大致分為四類(lèi)進(jìn)行討論。這里不會(huì)系統(tǒng)的闡述基礎(chǔ)知識(shí),而是通過(guò)面試?yán)}的形式,來(lái)講述每個(gè)題目的知識(shí)點(diǎn)以及代碼的執(zhí)行過(guò)程。如果會(huì)了這些例題,在前端面試中多數(shù)代碼輸出問(wèn)題就可以輕而易舉的解決了。

一、異步&事件循環(huán)


1. 代碼輸出結(jié)果

const promise = new Promise((resolve, reject) => {
  console.log(1);
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);

輸出結(jié)果如下:

1 
2 
4

promise.then 是微任務(wù),它會(huì)在所有的宏任務(wù)執(zhí)行完之后才會(huì)執(zhí)行,同時(shí)需要promise內(nèi)部的狀態(tài)發(fā)生變化,因?yàn)檫@里內(nèi)部沒(méi)有發(fā)生變化,一直處于pending狀態(tài),所以不輸出3。

2. 代碼輸出結(jié)果

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);

輸出結(jié)果如下:

promise1
1 Promise{<resolved>: resolve1}
2 Promise{<pending>}
resolve1

需要注意的是,直接打印promise1,會(huì)打印出它的狀態(tài)值和參數(shù)。

代碼執(zhí)行過(guò)程如下:

  1. script是一個(gè)宏任務(wù),按照順序執(zhí)行這些代碼;
  2. 首先進(jìn)入Promise,執(zhí)行該構(gòu)造函數(shù)中的代碼,打印 ?promise1?;
  3. 碰到 ?resolve?函數(shù), 將 ?promise1?的狀態(tài)改變?yōu)?nbsp;?resolved?, 并將結(jié)果保存下來(lái);
  4. 碰到 ?promise1.then?這個(gè)微任務(wù),將它放入微任務(wù)隊(duì)列;
  5. ?promise2?是一個(gè)新的狀態(tài)為 ?pending?的 ?Promise?;
  6. 執(zhí)行同步代碼1, 同時(shí)打印出 ?promise1?的狀態(tài)是 ?resolved?;
  7. 執(zhí)行同步代碼2,同時(shí)打印出 ?promise2?的狀態(tài)是 ?pending?;
  8. 宏任務(wù)執(zhí)行完畢,查找微任務(wù)隊(duì)列,發(fā)現(xiàn) ?promise1.then?這個(gè)微任務(wù)且狀態(tài)為 ?resolved?,執(zhí)行它。

3. 代碼輸出結(jié)果

const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);
});
console.log(4);

輸出結(jié)果如下:

1
2
4
timerStart
timerEnd
success

代碼執(zhí)行過(guò)程如下:

  • 首先遇到Promise構(gòu)造函數(shù),會(huì)先執(zhí)行里面的內(nèi)容,打印 1;
  • 遇到定時(shí)器 ?steTimeout?,它是一個(gè)宏任務(wù),放入宏任務(wù)隊(duì)列;
  • 繼續(xù)向下執(zhí)行,打印出2;
  • 由于 ?Promise?的狀態(tài)此時(shí)還是 ?pending?,所以 ?promise.then?先不執(zhí)行;
  • 繼續(xù)執(zhí)行下面的同步任務(wù),打印出4;
  • 此時(shí)微任務(wù)隊(duì)列沒(méi)有任務(wù),繼續(xù)執(zhí)行下一輪宏任務(wù),執(zhí)行 ?steTimeout?;
  • 首先執(zhí)行 ?timerStart?,然后遇到了 ?resolve?,將 ?promise?的狀態(tài)改為 ?resolved?且保存結(jié)果并將之前的 ?promise.then?推入微任務(wù)隊(duì)列,再執(zhí)行 ?timerEnd?;
  • 執(zhí)行完這個(gè)宏任務(wù),就去執(zhí)行微任務(wù) ?promise.then?,打印出 ?resolve?的結(jié)果。

4. 代碼輸出結(jié)果

Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start');

輸出結(jié)果如下:

start
promise1
timer1
promise2
timer2

代碼執(zhí)行過(guò)程如下:

  1. 首先,?Promise.resolve().then?是一個(gè)微任務(wù),加入微任務(wù)隊(duì)列
  2. 執(zhí)行timer1,它是一個(gè)宏任務(wù),加入宏任務(wù)隊(duì)列
  3. 繼續(xù)執(zhí)行下面的同步代碼,打印出 ?start?
  4. 這樣第一輪宏任務(wù)就執(zhí)行完了,開(kāi)始執(zhí)行微任務(wù) ?Promise.resolve().then?,打印出 ?promise1?
  5. 遇到 ?timer2?,它是一個(gè)宏任務(wù),將其加入宏任務(wù)隊(duì)列,此時(shí)宏任務(wù)隊(duì)列有兩個(gè)任務(wù),分別是 ?timer1?、?timer2?;
  6. 這樣第一輪微任務(wù)就執(zhí)行完了,開(kāi)始執(zhí)行第二輪宏任務(wù),首先執(zhí)行定時(shí)器 ?timer1?,打印 ?timer1?;
  7. 遇到 ?Promise.resolve().then?,它是一個(gè)微任務(wù),加入微任務(wù)隊(duì)列
  8. 開(kāi)始執(zhí)行微任務(wù)隊(duì)列中的任務(wù),打印 ?promise2?;
  9. 最后執(zhí)行宏任務(wù) ?timer2?定時(shí)器,打印出 ?timer2?;

5. 代碼輸出結(jié)果

const promise = new Promise((resolve, reject) => {
    resolve('success1');
    reject('error');
    resolve('success2');
});
promise.then((res) => {
    console.log('then:', res);
}).catch((err) => {
    console.log('catch:', err);
})

輸出結(jié)果如下:

then:success1

這個(gè)題目考察的就是Promise的狀態(tài)在發(fā)生變化之后,就不會(huì)再發(fā)生變化。開(kāi)始狀態(tài)由 pending變?yōu)?nbsp;resolve,說(shuō)明已經(jīng)變?yōu)橐淹瓿蔂顟B(tài),下面的兩個(gè)狀態(tài)的就不會(huì)再執(zhí)行,同時(shí)下面的catch也不會(huì)捕獲到錯(cuò)誤。

6. 代碼輸出結(jié)果

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

輸出結(jié)果如下:

1
Promise {<fulfilled>: undefined}

Promise.resolve方法的參數(shù)如果是一個(gè)原始值,或者是一個(gè)不具有then方法的對(duì)象,則Promise.resolve方法返回一個(gè)新的Promise對(duì)象,狀態(tài)為resolved,Promise.resolve方法的參數(shù),會(huì)同時(shí)傳給回調(diào)函數(shù)。

then方法接受的參數(shù)是函數(shù),而如果傳遞的并非是一個(gè)函數(shù),它實(shí)際上會(huì)將其解釋為then(null),這就會(huì)導(dǎo)致前一個(gè)Promise的結(jié)果會(huì)傳遞下面。

7. 代碼輸出結(jié)果

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)

輸出結(jié)果如下:

promise1 Promise {<pending>}
promise2 Promise {<pending>}

Uncaught (in promise) Error: error!!!
promise1 Promise {<fulfilled>: "success"}
promise2 Promise {<rejected>: Error: error!!}

8. 代碼輸出結(jié)果

Promise.resolve(1)
  .then(res => {
    console.log(res);
    return 2;
  })
  .catch(err => {
    return 3;
  })
  .then(res => {
    console.log(res);
  });

輸出結(jié)果如下:

1   
2

Promise是可以鏈?zhǔn)秸{(diào)用的,由于每次調(diào)用 .then 或者 .catch 都會(huì)返回一個(gè)新的 promise,從而實(shí)現(xiàn)了鏈?zhǔn)秸{(diào)用, 它并不像一般任務(wù)的鏈?zhǔn)秸{(diào)用一樣return this。

上面的輸出結(jié)果之所以依次打印出1和2,是因?yàn)?nbsp;resolve(1)之后走的是第一個(gè)then方法,并沒(méi)有進(jìn)catch里,所以第二個(gè)then中的res得到的實(shí)際上是第一個(gè)then的返回值。并且return 2會(huì)被包裝成 resolve(2),被最后的then打印輸出2。

9. 代碼輸出結(jié)果

Promise.resolve().then(() => {
  return new Error('error!!!')
}).then(res => {
  console.log("then: ", res)
}).catch(err => {
  console.log("catch: ", err)
})

輸出結(jié)果如下:

"then: " "Error: error!!!"

返回任意一個(gè)非 promise 的值都會(huì)被包裹成 promise 對(duì)象,因此這里的 return new Error('error!!!')也被包裹成了 return Promise.resolve(new Error('error!!!')),因此它會(huì)被then捕獲而不是catch。

10. 代碼輸出結(jié)果

const promise = Promise.resolve().then(() => {
  return promise;
})
promise.catch(console.err)

輸出結(jié)果如下:

Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

這里其實(shí)是一個(gè)坑,.then 或 .catch 返回的值不能是 promise 本身,否則會(huì)造成死循環(huán)。

11. 代碼輸出結(jié)果

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

輸出結(jié)果如下:

1

看到這個(gè)題目,好多的then,實(shí)際上只需要記住一個(gè)原則:.then 或 .catch 的參數(shù)期望是函數(shù),傳入非函數(shù)則會(huì)發(fā)生值透?jìng)?/strong>。

第一個(gè)then和第二個(gè)then中傳入的都不是函數(shù),一個(gè)是數(shù)字,一個(gè)是對(duì)象,因此發(fā)生了透?jìng)?,?nbsp;resolve(1) 的值直接傳到最后一個(gè)then里,直接打印出1。

12. 代碼輸出結(jié)果

Promise.reject('err!!!')
  .then((res) => {
    console.log('success', res)
  }, (err) => {
    console.log('error', err)
  }).catch(err => {
    console.log('catch', err)
  })

輸出結(jié)果如下:

error err!!!

我們知道,.then函數(shù)中的兩個(gè)參數(shù):

  • 第一個(gè)參數(shù)是用來(lái)處理Promise成功的函數(shù)
  • 第二個(gè)則是處理失敗的函數(shù)

也就是說(shuō) Promise.resolve('1')的值會(huì)進(jìn)入成功的函數(shù),Promise.reject('2')的值會(huì)進(jìn)入失敗的函數(shù)。

在這道題中,錯(cuò)誤直接被 then的第二個(gè)參數(shù)捕獲了,所以就不會(huì)被 catch捕獲了,輸出結(jié)果為:error err!!!'

但是,如果是像下面這樣:

Promise.resolve()
  .then(function success (res) {
    throw new Error('error!!!')
  }, function fail1 (err) {
    console.log('fail1', err)
  }).catch(function fail2 (err) {
    console.log('fail2', err)
  })

在 then的第一參數(shù)中拋出了錯(cuò)誤,那么他就不會(huì)被第二個(gè)參數(shù)不活了,而是被后面的 catch捕獲到。

13. 代碼輸出結(jié)果

Promise.resolve('1')
  .then(res => {
    console.log(res)
  })
  .finally(() => {
    console.log('finally')
  })
Promise.resolve('2')
  .finally(() => {
    console.log('finally2')
    return '我是finally2返回的值'
  })
  .then(res => {
    console.log('finally2后面的then函數(shù)', res)
  })

輸出結(jié)果如下:

1
finally2
finally
finally2后面的then函數(shù) 2

.finally()一般用的很少,只要記住以下幾點(diǎn)就可以了:

  • ?.finally()?方法不管Promise對(duì)象最后的狀態(tài)如何都會(huì)執(zhí)行
  • ?.finally()?方法的回調(diào)函數(shù)不接受任何的參數(shù),也就是說(shuō)你在 ?.finally()?函數(shù)中是無(wú)法知道Promise最終的狀態(tài)是 ?resolved?還是 ?rejected?的
  • 它最終返回的默認(rèn)會(huì)是一個(gè)上一次的Promise對(duì)象值,不過(guò)如果拋出的是一個(gè)異常則返回異常的Promise對(duì)象。
  • finally本質(zhì)上是then方法的特例

.finally()的錯(cuò)誤捕獲:

Promise.resolve('1')
  .finally(() => {
    console.log('finally1')
    throw new Error('我是finally中拋出的異常')
  })
  .then(res => {
    console.log('finally后面的then函數(shù)', res)
  })
  .catch(err => {
    console.log('捕獲錯(cuò)誤', err)
  })

輸出結(jié)果為:

'finally1'
'捕獲錯(cuò)誤' Error: 我是finally中拋出的異常

14. 代碼輸出結(jié)果

function runAsync (x) {
    const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
    return p
}

Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))

輸出結(jié)果如下:

1
2
3
[1, 2, 3]

首先,定義了一個(gè)Promise,來(lái)異步執(zhí)行函數(shù)runAsync,該函數(shù)傳入一個(gè)值x,然后間隔一秒后打印出這個(gè)x。

之后再使用 Promise.all來(lái)執(zhí)行這個(gè)函數(shù),執(zhí)行的時(shí)候,看到一秒之后輸出了1,2,3,同時(shí)輸出了數(shù)組[1, 2, 3],三個(gè)函數(shù)是同步執(zhí)行的,并且在一個(gè)回調(diào)函數(shù)中返回了所有的結(jié)果。并且結(jié)果和函數(shù)的執(zhí)行順序是一致的。

15. 代碼輸出結(jié)果

function runAsync (x) {
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
  return p
}
function runReject (x) {
  const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))
  return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
       .then(res => console.log(res))
       .catch(err => console.log(err))

輸出結(jié)果如下:

// 1s后輸出
1
3
// 2s后輸出
2
Error: 2
// 4s后輸出
4

可以看到。catch捕獲到了第一個(gè)錯(cuò)誤,在這道題目中最先的錯(cuò)誤就是 runReject(2)的結(jié)果。如果一組異步操作中有一個(gè)異常都不會(huì)進(jìn)入 .then()的第一個(gè)回調(diào)函數(shù)參數(shù)中。會(huì)被 .then()的第二個(gè)回調(diào)函數(shù)捕獲。

16. 代碼輸出結(jié)果

function runAsync (x) {
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
  return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log('result: ', res))
  .catch(err => console.log(err))

輸出結(jié)果如下:

1
'result: ' 1
2
3

then只會(huì)捕獲第一個(gè)成功的方法,其他的函數(shù)雖然還會(huì)繼續(xù)執(zhí)行,但是不是被then捕獲了。

17. 代碼輸出結(jié)果

function runAsync(x) {
  const p = new Promise(r =>
    setTimeout(() => r(x, console.log(x)), 1000)
  );
  return p;
}
function runReject(x) {
  const p = new Promise((res, rej) =>
    setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
  );
  return p;
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log("result: ", res))
  .catch(err => console.log(err));

輸出結(jié)果如下:

0
Error: 0
1
2
3

可以看到在catch捕獲到第一個(gè)錯(cuò)誤之后,后面的代碼還不執(zhí)行,不過(guò)不會(huì)再被捕獲了。

注意:all和 race傳入的數(shù)組中如果有會(huì)拋出異常的異步任務(wù),那么只有最先拋出的錯(cuò)誤會(huì)被捕獲,并且是被then的第二個(gè)參數(shù)或者后面的catch捕獲;但并不會(huì)影響數(shù)組中其它的異步任務(wù)的執(zhí)行。

18. 代碼輸出結(jié)果

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
async1();
console.log('start')

輸出結(jié)果如下:

async1 start
async2
start
async1 end

代碼的執(zhí)行過(guò)程如下:

  1. 首先執(zhí)行函數(shù)中的同步代碼 ?async1 start?,之后遇到了 ?await?,它會(huì)阻塞 ?async1?后面代碼的執(zhí)行,因此會(huì)先去執(zhí)行 ?async2?中的同步代碼 ?async2?,然后跳出 ?async1?;
  2. 跳出 ?async1?函數(shù)后,執(zhí)行同步代碼 ?start?;
  3. 在一輪宏任務(wù)全部執(zhí)行完之后,再來(lái)執(zhí)行 ?await?后面的內(nèi)容 ?async1 end?。

這里可以理解為await后面的語(yǔ)句相當(dāng)于放到了new Promise中,下一行及之后的語(yǔ)句相當(dāng)于放在Promise.then中。

19. 代碼輸出結(jié)果

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
  setTimeout(() => {
    console.log('timer1')
  }, 0)
}
async function async2() {
  setTimeout(() => {
    console.log('timer2')
  }, 0)
  console.log("async2");
}
async1();
setTimeout(() => {
  console.log('timer3')
}, 0)
console.log("start")

輸出結(jié)果如下:

async1 start
async2
start
async1 end
timer2
timer3
timer1

代碼的執(zhí)行過(guò)程如下:

  1. 首先進(jìn)入 ?async1?,打印出 ?async1 start?;
  2. 之后遇到 ?async2?,進(jìn)入 ?async2?,遇到定時(shí)器 ?timer2?,加入宏任務(wù)隊(duì)列,之后打印 ?async2?;
  3. 由于 ?async2?阻塞了后面代碼的執(zhí)行,所以執(zhí)行后面的定時(shí)器 ?timer3?,將其加入宏任務(wù)隊(duì)列,之后打印 ?start?;
  4. 然后執(zhí)行?async2?后面的代碼,打印出 ?async1 end?,遇到定時(shí)器?timer1?,將其加入宏任務(wù)隊(duì)列;
  5. 最后,宏任務(wù)隊(duì)列有三個(gè)任務(wù),先后順序?yàn)?nbsp;?timer2?,?timer3?,?timer1?,沒(méi)有微任務(wù),所以直接所有的宏任務(wù)按照先進(jìn)先出的原則執(zhí)行。

20. 代碼輸出結(jié)果

async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

輸出結(jié)果如下:

script start
async1 start
promise1
script end

這里需要注意的是在 async1中 await后面的Promise是沒(méi)有返回值的,也就是它的狀態(tài)始終是 pending狀態(tài),所以在 await之后的內(nèi)容是不會(huì)執(zhí)行的,包括 async1后面的 .then。

21. 代碼輸出結(jié)果

async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
    resolve('promise1 resolve')
  }).then(res => console.log(res))
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

這里是對(duì)上面一題進(jìn)行了改造,加上了resolve。

輸出結(jié)果如下:

script start
async1 start
promise1
script end
promise1 resolve
async1 success
async1 end

22. 代碼輸出結(jié)果

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}

async function async2() {
  console.log("async2");
}

console.log("script start");

setTimeout(function() {
  console.log("setTimeout");
}, 0);

async1();

new Promise(resolve => {
  console.log("promise1");
  resolve();
}).then(function() {
  console.log("promise2");
});
console.log('script end')

輸出結(jié)果如下:

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

代碼執(zhí)行過(guò)程如下:

  1. 開(kāi)頭定義了async1和async2兩個(gè)函數(shù),但是并未執(zhí)行,執(zhí)行script中的代碼,所以打印出script start;
  2. 遇到定時(shí)器Settimeout,它是一個(gè)宏任務(wù),將其加入到宏任務(wù)隊(duì)列;
  3. 之后執(zhí)行函數(shù)async1,首先打印出async1 start;
  4. 遇到await,執(zhí)行async2,打印出async2,并阻斷后面代碼的執(zhí)行,將后面的代碼加入到微任務(wù)隊(duì)列;
  5. 然后跳出async1和async2,遇到Promise,打印出promise1;
  6. 遇到resolve,將其加入到微任務(wù)隊(duì)列,然后執(zhí)行后面的script代碼,打印出script end;
  7. 之后就該執(zhí)行微任務(wù)隊(duì)列了,首先打印出async1 end,然后打印出promise2;
  8. 執(zhí)行完微任務(wù)隊(duì)列,就開(kāi)始執(zhí)行宏任務(wù)隊(duì)列中的定時(shí)器,打印出setTimeout。

23. 代碼輸出結(jié)果

async function async1 () {
  await async2();
  console.log('async1');
  return 'async1 success'
}
async function async2 () {
  return new Promise((resolve, reject) => {
    console.log('async2')
    reject('error')
  })
}
async1().then(res => console.log(res))

輸出結(jié)果如下:

async2
Uncaught (in promise) error

可以看到,如果async函數(shù)中拋出了錯(cuò)誤,就會(huì)終止錯(cuò)誤結(jié)果,不會(huì)繼續(xù)向下執(zhí)行。

如果想要讓錯(cuò)誤不足之處后面的代碼執(zhí)行,可以使用catch來(lái)捕獲:

async function async1 () {
  await Promise.reject('error!!!').catch(e => console.log(e))
  console.log('async1');
  return Promise.resolve('async1 success')
}
async1().then(res => console.log(res))
console.log('script start')

這樣的輸出結(jié)果就是:

script start
error!!!
async1
async1 success

24. 代碼輸出結(jié)果

const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
            console.log(p)
        }, 0)
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });
}));
first().then((arg) => {
    console.log(arg);
});
console.log(4);

輸出結(jié)果如下:

3
7
4
1
2
5
Promise{<resolved>: 1}

代碼的執(zhí)行過(guò)程如下:

  1. 首先會(huì)進(jìn)入Promise,打印出3,之后進(jìn)入下面的Promise,打印出7;
  2. 遇到了定時(shí)器,將其加入宏任務(wù)隊(duì)列;
  3. 執(zhí)行Promise p中的resolve,狀態(tài)變?yōu)閞esolved,返回值為1;
  4. 執(zhí)行Promise first中的resolve,狀態(tài)變?yōu)閞esolved,返回值為2;
  5. 遇到p.then,將其加入微任務(wù)隊(duì)列,遇到first().then,將其加入任務(wù)隊(duì)列;
  6. 執(zhí)行外面的代碼,打印出4;
  7. 這樣第一輪宏任務(wù)就執(zhí)行完了,開(kāi)始執(zhí)行微任務(wù)隊(duì)列中的任務(wù),先后打印出1和2;
  8. 這樣微任務(wù)就執(zhí)行完了,開(kāi)始執(zhí)行下一輪宏任務(wù),宏任務(wù)隊(duì)列中有一個(gè)定時(shí)器,執(zhí)行它,打印出5,由于執(zhí)行已經(jīng)變?yōu)閞esolved狀態(tài),所以 ?resolve(6)?不會(huì)再執(zhí)行;
  9. 最后 ?console.log(p)?打印出 ?Promise{<resolved>: 1}?;

25. 代碼輸出結(jié)果

const async1 = async () => {
  console.log('async1');
  setTimeout(() => {
    console.log('timer1')
  }, 2000)
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 end')
  return 'async1 success'
} 
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .catch(4)
  .then(res => console.log(res))
setTimeout(() => {
  console.log('timer2')
}, 1000)

輸出結(jié)果如下:

script start
async1
promise1
script end
1
timer2
timer1

代碼的執(zhí)行過(guò)程如下:

  1. 首先執(zhí)行同步帶嗎,打印出script start;
  2. 遇到定時(shí)器timer1將其加入宏任務(wù)隊(duì)列;
  3. 之后是執(zhí)行Promise,打印出promise1,由于Promise沒(méi)有返回值,所以后面的代碼不會(huì)執(zhí)行;
  4. 然后執(zhí)行同步代碼,打印出script end;
  5. 繼續(xù)執(zhí)行下面的Promise,.then和.catch期望參數(shù)是一個(gè)函數(shù),這里傳入的是一個(gè)數(shù)字,因此就會(huì)發(fā)生值滲透,將resolve(1)的值傳到最后一個(gè)then,直接打印出1;
  6. 遇到第二個(gè)定時(shí)器,將其加入到微任務(wù)隊(duì)列,執(zhí)行微任務(wù)隊(duì)列,按順序依次執(zhí)行兩個(gè)定時(shí)器,但是由于定時(shí)器時(shí)間的原因,會(huì)在兩秒后先打印出timer2,在四秒后打印出timer1。

26. 代碼輸出結(jié)果

const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve('resolve3');
    console.log('timer1')
  }, 0)
  resolve('resovle1');
  resolve('resolve2');
}).then(res => {
  console.log(res)  // resolve1
  setTimeout(() => {
    console.log(p1)
  }, 1000)
}).finally(res => {
  console.log('finally', res)
})

執(zhí)行結(jié)果為如下:

resolve1
finally  undefined
timer1
Promise{<resolved>: undefined}

27. 代碼輸出結(jié)果

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

輸出結(jié)果如下:

1
7
6
8
2
4
3
5
9
11
10
12

(1)第一輪事件循環(huán)流程分析如下:

  • 整體script作為第一個(gè)宏任務(wù)進(jìn)入主線程,遇到 ?console.log?,輸出1。
  • 遇到 ?setTimeout?,其回調(diào)函數(shù)被分發(fā)到宏任務(wù)Event Queue中。暫且記為 ?setTimeout1?。
  • 遇到 ?process.nextTick()?,其回調(diào)函數(shù)被分發(fā)到微任務(wù)Event Queue中。記為 ?process1?。
  • 遇到 ?Promise?,?new Promise?直接執(zhí)行,輸出7。?then?被分發(fā)到微任務(wù)Event Queue中。記為 ?then1?。
  • 又遇到了 ?setTimeout?,其回調(diào)函數(shù)被分發(fā)到宏任務(wù)Event Queue中,記為 ?setTimeout2?。
宏任務(wù)Event Queue 微任務(wù)Event Queue
setTimeout1 process1
setTimeout2 then1

上表是第一輪事件循環(huán)宏任務(wù)結(jié)束時(shí)各Event Queue的情況,此時(shí)已經(jīng)輸出了1和7。發(fā)現(xiàn)了 process1和 then1兩個(gè)微任務(wù):

  • 執(zhí)行 ?process1?,輸出6。
  • 執(zhí)行 ?then1?,輸出8。

第一輪事件循環(huán)正式結(jié)束,這一輪的結(jié)果是輸出1,7,6,8。

(2)第二輪時(shí)間循環(huán)從 setTimeout1宏任務(wù)開(kāi)始:

  • 首先輸出2。接下來(lái)遇到了 ?process.nextTick()?,同樣將其分發(fā)到微任務(wù)Event Queue中,記為 ?process2?。
  • ?new Promise?立即執(zhí)行輸出4,?then?也分發(fā)到微任務(wù)Event Queue中,記為 ?then2?。
宏任務(wù)Event Queue 微任務(wù)Event Queue
setTimeout2 process2
then2

第二輪事件循環(huán)宏任務(wù)結(jié)束,發(fā)現(xiàn)有 process2和 then2兩個(gè)微任務(wù)可以執(zhí)行:

  • 輸出3。
  • 輸出5。

第二輪事件循環(huán)結(jié)束,第二輪輸出2,4,3,5。

(3)第三輪事件循環(huán)開(kāi)始,此時(shí)只剩setTimeout2了,執(zhí)行。

  • 直接輸出9。
  • 將 ?process.nextTick()?分發(fā)到微任務(wù)Event Queue中。記為 ?process3?。
  • 直接執(zhí)行 ?new Promise?,輸出11。
  • 將 ?then?分發(fā)到微任務(wù)Event Queue中,記為 ?then3?。
宏任務(wù)Event Queue 微任務(wù)Event Queue
process3
then3

第三輪事件循環(huán)宏任務(wù)執(zhí)行結(jié)束,執(zhí)行兩個(gè)微任務(wù) process3和 then3

  • 輸出10。
  • 輸出12。

第三輪事件循環(huán)結(jié)束,第三輪輸出9,11,10,12。

整段代碼,共進(jìn)行了三次事件循環(huán),完整的輸出為1,7,6,8,2,4,3,5,9,11,10,12。

28. 代碼輸出結(jié)果

console.log(1)

setTimeout(() => {
  console.log(2)
})

new Promise(resolve =>  {
  console.log(3)
  resolve(4)
}).then(d => console.log(d))

setTimeout(() => {
  console.log(5)
  new Promise(resolve =>  {
    resolve(6)
  }).then(d => console.log(d))
})

setTimeout(() => {
  console.log(7)
})

console.log(8)

輸出結(jié)果如下:

1
3
8
4
2
5
6
7

代碼執(zhí)行過(guò)程如下:

  1. 首先執(zhí)行script代碼,打印出1;
  2. 遇到第一個(gè)定時(shí)器,加入到宏任務(wù)隊(duì)列;
  3. 遇到Promise,執(zhí)行代碼,打印出3,遇到resolve,將其加入到微任務(wù)隊(duì)列;
  4. 遇到第二個(gè)定時(shí)器,加入到宏任務(wù)隊(duì)列;
  5. 遇到第三個(gè)定時(shí)器,加入到宏任務(wù)隊(duì)列;
  6. 繼續(xù)執(zhí)行script代碼,打印出8,第一輪執(zhí)行結(jié)束;
  7. 執(zhí)行微任務(wù)隊(duì)列,打印出第一個(gè)Promise的resolve結(jié)果:4;
  8. 開(kāi)始執(zhí)行宏任務(wù)隊(duì)列,執(zhí)行第一個(gè)定時(shí)器,打印出2;
  9. 此時(shí)沒(méi)有微任務(wù),繼續(xù)執(zhí)行宏任務(wù)中的第二個(gè)定時(shí)器,首先打印出5,遇到Promise,首選打印出6,遇到resolve,將其加入到微任務(wù)隊(duì)列;
  10. 執(zhí)行微任務(wù)隊(duì)列,打印出6;
  11. 執(zhí)行宏任務(wù)隊(duì)列中的最后一個(gè)定時(shí)器,打印出7。

29. 代碼輸出結(jié)果

console.log(1);
  
setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
})

setTimeout(() => {
  console.log(6);
})

console.log(7);

代碼輸出結(jié)果如下:

1
4
7
5
2
3
6

代碼執(zhí)行過(guò)程如下:

  1. 首先執(zhí)行scrip代碼,打印出1;
  2. 遇到第一個(gè)定時(shí)器setTimeout,將其加入到宏任務(wù)隊(duì)列;
  3. 遇到Promise,執(zhí)行里面的同步代碼,打印出4,遇到resolve,將其加入到微任務(wù)隊(duì)列;
  4. 遇到第二個(gè)定時(shí)器setTimeout,將其加入到紅任務(wù)隊(duì)列;
  5. 執(zhí)行script代碼,打印出7,至此第一輪執(zhí)行完成;
  6. 指定微任務(wù)隊(duì)列中的代碼,打印出resolve的結(jié)果:5;
  7. 執(zhí)行宏任務(wù)中的第一個(gè)定時(shí)器setTimeout,首先打印出2,然后遇到 Promise.resolve().then(),將其加入到微任務(wù)隊(duì)列;
  8. 執(zhí)行完這個(gè)宏任務(wù),就開(kāi)始執(zhí)行微任務(wù)隊(duì)列,打印出3;
  9. 繼續(xù)執(zhí)行宏任務(wù)隊(duì)列中的第二個(gè)定時(shí)器,打印出6。

30. 代碼輸出結(jié)果

Promise.resolve().then(() => {
    console.log('1');
    throw 'Error';
}).then(() => {
    console.log('2');
}).catch(() => {
    console.log('3');
    throw 'Error';
}).then(() => {
    console.log('4');
}).catch(() => {
    console.log('5');
}).then(() => {
    console.log('6');
});

執(zhí)行結(jié)果如下:

1 
3 
5 
6

在這道題目中,我們需要知道,無(wú)論是thne還是catch中,只要throw 拋出了錯(cuò)誤,就會(huì)被catch捕獲,如果沒(méi)有throw出錯(cuò)誤,就被繼續(xù)執(zhí)行后面的then。

31. 代碼輸出結(jié)果

setTimeout(function () {
  console.log(1);
}, 100);

new Promise(function (resolve) {
  console.log(2);
  resolve();
  console.log(3);
}).then(function () {
  console.log(4);
  new Promise((resove, reject) => {
    console.log(5);
    setTimeout(() =>  {
      console.log(6);
    }, 10);
  })
});
console.log(7);
console.log(8);

輸出結(jié)果為:

2
3
7
8
4
5
6
1

代碼執(zhí)行過(guò)程如下:

  1. 首先遇到定時(shí)器,將其加入到宏任務(wù)隊(duì)列;
  2. 遇到Promise,首先執(zhí)行里面的同步代碼,打印出2,遇到resolve,將其加入到微任務(wù)隊(duì)列,執(zhí)行后面同步代碼,打印出3;
  3. 繼續(xù)執(zhí)行script中的代碼,打印出7和8,至此第一輪代碼執(zhí)行完成;
  4. 執(zhí)行微任務(wù)隊(duì)列中的代碼,首先打印出4,如遇到Promise,執(zhí)行其中的同步代碼,打印出5,遇到定時(shí)器,將其加入到宏任務(wù)隊(duì)列中,此時(shí)宏任務(wù)隊(duì)列中有兩個(gè)定時(shí)器;
  5. 執(zhí)行宏任務(wù)隊(duì)列中的代碼,這里我們需要注意是的第一個(gè)定時(shí)器的時(shí)間為100ms,第二個(gè)定時(shí)器的時(shí)間為10ms,所以先執(zhí)行第二個(gè)定時(shí)器,打印出6;
  6. 此時(shí)微任務(wù)隊(duì)列為空,繼續(xù)執(zhí)行宏任務(wù)隊(duì)列,打印出1。

做完這道題目,我們就需要格外注意,每個(gè)定時(shí)器的時(shí)間,并不是所有定時(shí)器的時(shí)間都為0哦。

二、this


1. 代碼輸出結(jié)果

function foo() {
  console.log( this.a );
}

function doFoo() {
  foo();
}

var obj = {
  a: 1,
  doFoo: doFoo
};

var a = 2; 
obj.doFoo()

輸出結(jié)果:2

在Javascript中,this指向函數(shù)執(zhí)行時(shí)的當(dāng)前對(duì)象。在執(zhí)行foo的時(shí)候,執(zhí)行環(huán)境就是doFoo函數(shù),執(zhí)行環(huán)境為全局。所以,foo中的this是指向window的,所以會(huì)打印出2。

2. 代碼輸出結(jié)果

var a = 10
var obj = {
  a: 20,
  say: () => {
    console.log(this.a)
  }
}
obj.say() 

var anotherObj = { a: 30 } 
obj.say.apply(anotherObj) 

輸出結(jié)果:10 10

我么知道,箭頭函數(shù)時(shí)不綁定this的,它的this來(lái)自原其父級(jí)所處的上下文,所以首先會(huì)打印全局中的 a 的值10。后面雖然讓say方法指向了另外一個(gè)對(duì)象,但是仍不能改變箭頭函數(shù)的特性,它的this仍然是指向全局的,所以依舊會(huì)輸出10。

但是,如果是普通函數(shù),那么就會(huì)有完全不一樣的結(jié)果:

var a = 10  
var obj = {  
  a: 20,  
  say(){
    console.log(this.a)  
  }  
}  
obj.say()   
var anotherObj={a:30}   
obj.say.apply(anotherObj)

輸出結(jié)果:20 30

這時(shí),say方法中的this就會(huì)指向他所在的對(duì)象,輸出其中的a的值。

3. 代碼輸出結(jié)果

function a() {
  console.log(this);
}
a.call(null);

打印結(jié)果:window對(duì)象

根據(jù)ECMAScript262規(guī)范規(guī)定:如果第一個(gè)參數(shù)傳入的對(duì)象調(diào)用者是null或者undefined,call方法將把全局對(duì)象(瀏覽器上是window對(duì)象)作為this的值。所以,不管傳入null 還是 undefined,其this都是全局對(duì)象window。所以,在瀏覽器上答案是輸出 window 對(duì)象。

要注意的是,在嚴(yán)格模式中,null 就是 null,undefined 就是 undefined:

'use strict';

function a() {
    console.log(this);
}
a.call(null); // null
a.call(undefined); // undefined

4. 代碼輸出結(jié)果

var obj = { 
  name: 'cuggz', 
  fun: function(){ 
     console.log(this.name); 
  } 
} 
obj.fun()     // cuggz
new obj.fun() // undefined

6. 代碼輸出結(jié)果

var obj = {
   say: function() {
     var f1 = () =>  {
       console.log("1111", this);
     }
     f1();
   },
   pro: {
     getPro:() =>  {
        console.log(this);
     }
   }
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();

輸出結(jié)果:

1111 window對(duì)象
1111 obj對(duì)象
window對(duì)象

解析:

  1. o(),o是在全局執(zhí)行的,而f1是箭頭函數(shù),它是沒(méi)有綁定this的,它的this指向其父級(jí)的this,其父級(jí)say方法的this指向的是全局作用域,所以會(huì)打印出window;
  2. obj.say(),誰(shuí)調(diào)用say,say 的this就指向誰(shuí),所以此時(shí)this指向的是obj對(duì)象;
  3. obj.pro.getPro(),我們知道,箭頭函數(shù)時(shí)不綁定this的,getPro處于pro中,而對(duì)象不構(gòu)成單獨(dú)的作用域,所以箭頭的函數(shù)的this就指向了全局作用域window。

7. 代碼輸出結(jié)果

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log(this.foo);  
        console.log(self.foo);  
        (function() {
            console.log(this.foo);  
            console.log(self.foo);  
        }());
    }
};
myObject.func();

輸出結(jié)果:bar bar undefined bar

解析:

  1. 首先f(wàn)unc是由myObject調(diào)用的,this指向myObject。又因?yàn)関ar self = this;所以self指向myObject。
  2. 這個(gè)立即執(zhí)行匿名函數(shù)表達(dá)式是由window調(diào)用的,this指向window 。立即執(zhí)行匿名函數(shù)的作用域處于myObject.func的作用域中,在這個(gè)作用域找不到self變量,沿著作用域鏈向上查找self變量,找到了指向 myObject對(duì)象的self。

8. 代碼輸出問(wèn)題

window.number = 2;
var obj = {
 number: 3,
 db1: (function(){
   console.log(this);
   this.number *= 4;
   return function(){
     console.log(this);
     this.number *= 5;
   }
 })()
}
var db1 = obj.db1;
db1();
obj.db1();
console.log(obj.number);     // 15
console.log(window.number);  // 40

這道題目看清起來(lái)有點(diǎn)亂,但是實(shí)際上是考察this指向的:

  1. 執(zhí)行db1()時(shí),this指向全局作用域,所以window.number * 4 = 8,然后執(zhí)行匿名函數(shù), 所以window.number * 5 = 40;
  2. 執(zhí)行obj.db1();時(shí),this指向obj對(duì)象,執(zhí)行匿名函數(shù),所以obj.numer * 5 = 15。

9. 代碼輸出結(jié)果

var length = 10;
function fn() {
    console.log(this.length);
}
 
var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};
 
obj.method(fn, 1);

輸出結(jié)果: 10 2

解析:

  1. 第一次執(zhí)行fn(),this指向window對(duì)象,輸出10。
  2. 第二次執(zhí)行arguments0,相當(dāng)于arguments調(diào)用方法,this指向arguments,而這里傳了兩個(gè)參數(shù),故輸出arguments長(zhǎng)度為2。

10. 代碼輸出結(jié)果

var a = 1;
function printA(){
  console.log(this.a);
}
var obj={
  a:2,
  foo:printA,
  bar:function(){
    printA();
  }
}

obj.foo(); // 2
obj.bar(); // 1
var foo = obj.foo;
foo(); // 1

輸出結(jié)果: 2 1 1

解析:

  1. obj.foo(),foo 的this指向obj對(duì)象,所以a會(huì)輸出2;
  2. obj.bar(),printA在bar方法中執(zhí)行,所以此時(shí)printA的this指向的是window,所以會(huì)輸出1;
  3. foo(),foo是在全局對(duì)象中執(zhí)行的,所以其this指向的是window,所以會(huì)輸出1;

11. 代碼輸出結(jié)果

var x = 3;
var y = 4;
var obj = {
    x: 1,
    y: 6,
    getX: function() {
        var x = 5;
        return function() {
            return this.x;
        }();
    },
    getY: function() {
        var y = 7;
        return this.y;
    }
}
console.log(obj.getX()) // 3
console.log(obj.getY()) // 6

輸出結(jié)果:3 6

解析:

  1. 我們知道,匿名函數(shù)的this是指向全局對(duì)象的,所以this指向window,會(huì)打印出3;
  2. getY是由obj調(diào)用的,所以其this指向的是obj對(duì)象,會(huì)打印出6。

12. 代碼輸出結(jié)果

 var a = 10; 
 var obt = { 
   a: 20, 
   fn: function(){ 
     var a = 30; 
     console.log(this.a)
   } 
 }
 obt.fn();  // 20
 obt.fn.call(); // 10
 (obt.fn)(); // 20

輸出結(jié)果: 20 10 20

解析:

  1. obt.fn(),fn是由obt調(diào)用的,所以其this指向obt對(duì)象,會(huì)打印出20;
  2. obt.fn.call(),這里call的參數(shù)啥都沒(méi)寫(xiě),就表示null,我們知道如果call的參數(shù)為undefined或null,那么this就會(huì)指向全局對(duì)象this,所以會(huì)打印出 10;
  3. (obt.fn)(), 這里給表達(dá)式加了括號(hào),而括號(hào)的作用是改變表達(dá)式的運(yùn)算順序,而在這里加與不加括號(hào)并無(wú)影響;相當(dāng)于 obt.fn(),所以會(huì)打印出 20;

13. 代碼輸出結(jié)果

function a(xx){
  this.x = xx;
  return this
};
var x = a(5);
var y = a(6);

console.log(x.x)  // undefined
console.log(y.x)  // 6

輸出結(jié)果: undefined 6

解析:

  1. 最關(guān)鍵的就是var x = a(5),函數(shù)a是在全局作用域調(diào)用,所以函數(shù)內(nèi)部的this指向window對(duì)象。所以 this.x = 5 就相當(dāng)于:window.x = 5。之后 return this,也就是說(shuō) var x = a(5) 中的x變量的值是window,這里的x將函數(shù)內(nèi)部的x的值覆蓋了。然后執(zhí)行console.log(x.x), 也就是console.log(window.x),而window對(duì)象中沒(méi)有x屬性,所以會(huì)輸出undefined。
  2. 當(dāng)指向y.x時(shí),會(huì)給全局變量中的x賦值為6,所以會(huì)打印出6。

14. 代碼輸出結(jié)果

function foo(something){
    this.a = something
}

var obj1 = {
    foo: foo
}

var obj2 = {}

obj1.foo(2); 
console.log(obj1.a); // 2

obj1.foo.call(obj2, 3);
console.log(obj2.a); // 3

var bar = new obj1.foo(4)
console.log(obj1.a); // 2
console.log(bar.a); // 4

輸出結(jié)果: 2 3 2 4

解析:

  1. 首先執(zhí)行obj1.foo(2); 會(huì)在obj中添加a屬性,其值為2。之后執(zhí)行obj1.a,a是右obj1調(diào)用的,所以this指向obj,打印出2;
  2. 執(zhí)行 obj1.foo.call(obj2, 3) 時(shí),會(huì)將foo的this指向obj2,后面就和上面一樣了,所以會(huì)打印出3;
  3. obj1.a會(huì)打印出2;
  4. 最后就是考察this綁定的優(yōu)先級(jí)了,new 綁定是比隱式綁定優(yōu)先級(jí)高,所以會(huì)輸出4。

15. 代碼輸出結(jié)果

function foo(something){
    this.a = something
}

var obj1 = {}

var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2

var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3

輸出結(jié)果: 2 2 3

這道題目和上面題目差不多,主要都是考察this綁定的優(yōu)先級(jí)。記住以下結(jié)論即可:**this綁定的優(yōu)先級(jí):**new綁定 > 顯式綁定 > 隱式綁定 > 默認(rèn)綁定。

三、作用域&變量提升&閉包


1. 代碼輸出結(jié)果

(function(){
   var x = y = 1;
})();
var z;

console.log(y); // 1
console.log(z); // undefined
console.log(x); // Uncaught ReferenceError: x is not defined

這段代碼的關(guān)鍵在于:var x = y = 1; 實(shí)際上這里是從右往左執(zhí)行的,首先執(zhí)行y = 1, 因?yàn)閥沒(méi)有使用var聲明,所以它是一個(gè)全局變量,然后第二步是將y賦值給x,講一個(gè)全局變量賦值給了一個(gè)局部變量,最終,x是一個(gè)局部變量,y是一個(gè)全局變量,所以打印x是報(bào)錯(cuò)。

2. 代碼輸出結(jié)果

var a, b
(function () {
   console.log(a);
   console.log(b);
   var a = (b = 3);
   console.log(a);
   console.log(b);   
})()
console.log(a);
console.log(b);

輸出結(jié)果:

undefined 
undefined 
3 
3 
undefined 
3

這個(gè)題目和上面題目考察的知識(shí)點(diǎn)類(lèi)似,b賦值為3,b此時(shí)是一個(gè)全局變量,而將3賦值給a,a是一個(gè)局部變量,所以最后打印的時(shí)候,a仍舊是undefined。

3. 代碼輸出結(jié)果

var friendName = 'World';
(function() {
  if (typeof friendName === 'undefined') {
    var friendName = 'Jack';
    console.log('Goodbye ' + friendName);
  } else {
    console.log('Hello ' + friendName);
  }
})();

輸出結(jié)果:Goodbye Jack

我們知道,在 JavaScript中, Function 和 var 都會(huì)被提升(變量提升),所以上面的代碼就相當(dāng)于:

var name = 'World!';
(function () {
    var name;
    if (typeof name === 'undefined') {
        name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

這樣,答案就一目了然了。

4. 代碼輸出結(jié)果

function fn1(){
  console.log('fn1')
}
var fn2
 
fn1()
fn2()
 
fn2 = function() {
  console.log('fn2')
}
 
fn2()

輸出結(jié)果:

fn1
Uncaught TypeError: fn2 is not a function
fn2

這里也是在考察變量提升,關(guān)鍵在于第一個(gè)fn2(),這時(shí)fn2仍是一個(gè)undefined的變量,所以會(huì)報(bào)錯(cuò)fn2不是一個(gè)函數(shù)。

5. 代碼輸出結(jié)果

function a() {
    var temp = 10;
    function b() {
        console.log(temp); // 10
    }
    b();
}
a();

function a() {
    var temp = 10;
    b();
}
function b() {
    console.log(temp); // 報(bào)錯(cuò) Uncaught ReferenceError: temp is not defined
}
a();

在上面的兩段代碼中,第一段是可以正常輸出,這個(gè)應(yīng)該沒(méi)啥問(wèn)題,關(guān)鍵在于第二段代碼,它會(huì)報(bào)錯(cuò)Uncaught ReferenceError: temp is not defined。這時(shí)因?yàn)樵赽方法執(zhí)行時(shí),temp 的值為undefined。

6. 代碼輸出結(jié)果

 var a=3;
 function c(){
    alert(a);
 }
 (function(){
  var a=4;
  c();
 })();

js中變量的作用域鏈與定義時(shí)的環(huán)境有關(guān),與執(zhí)行時(shí)無(wú)關(guān)。執(zhí)行環(huán)境只會(huì)改變this、傳遞的參數(shù)、全局變量等

7. 代碼輸出問(wèn)題

function fun(n, o) {
  console.log(o)
  return {
    fun: function(m){
      return fun(m, n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);

輸出結(jié)果:

undefined  0  0  0
undefined  0  1  2
undefined  0  1  1

這是一道關(guān)于閉包的題目,對(duì)于fun方法,調(diào)用之后返回的是一個(gè)對(duì)象。我們知道,當(dāng)調(diào)用函數(shù)的時(shí)候傳入的實(shí)參比函數(shù)聲明時(shí)指定的形參個(gè)數(shù)要少,剩下的形參都將設(shè)置為undefined值。所以 console.log(o); 會(huì)輸出undefined。而a就是是fun(0)返回的那個(gè)對(duì)象。也就是說(shuō),函數(shù)fun中參數(shù) n 的值是0,而返回的那個(gè)對(duì)象中,需要一個(gè)參數(shù)n,而這個(gè)對(duì)象的作用域中沒(méi)有n,它就繼續(xù)沿著作用域向上一級(jí)的作用域中尋找n,最后在函數(shù)fun中找到了n,n的值是0。了解了這一點(diǎn),其他運(yùn)算就很簡(jiǎn)單了,以此類(lèi)推。

8. 代碼輸出結(jié)果

f = function() {return true;};   
g = function() {return false;};   
(function() {   
   if (g() && [] == ![]) {   
      f = function f() {return false;};   
      function g() {return true;}  //在匿名函數(shù)內(nèi)部發(fā)生函數(shù)提升 因此if判斷中的g()返回true  
   }   
})();   
console.log(f());

輸出結(jié)果: false

這里首先定義了兩個(gè)變量f和g,我們知道變量是可以重新賦值的。后面是一個(gè)匿名自執(zhí)行函數(shù),在 if 條件中調(diào)用了函數(shù) g(),由于在匿名函數(shù)中,又重新定義了函數(shù)g,就覆蓋了外部定義的變量g,所以,這里調(diào)用的是內(nèi)部函數(shù) g 方法,返回為 true。第一個(gè)條件通過(guò),進(jìn)入第二個(gè)條件。

第二個(gè)條件是[] == ![],先看 ![] ,在 JavaScript 中,當(dāng)用于布爾運(yùn)算時(shí),比如在這里,對(duì)象的非空引用被視為 true,空引用 null 則被視為 false。由于這里不是一個(gè) null, 而是一個(gè)沒(méi)有元素的數(shù)組,所以 [] 被視為 true, 而 ![] 的結(jié)果就是 false 了。當(dāng)一個(gè)布爾值參與到條件運(yùn)算的時(shí)候,true 會(huì)被看作 1, 而 false 會(huì)被看作 0?,F(xiàn)在條件變成了 [] == 0 的問(wèn)題了,當(dāng)一個(gè)對(duì)象參與條件比較的時(shí)候,它會(huì)被求值,求值的結(jié)果是數(shù)組成為一個(gè)字符串,[] 的結(jié)果就是 '' ,而 '' 會(huì)被當(dāng)作 0 ,所以,條件成立。

兩個(gè)條件都成立,所以會(huì)執(zhí)行條件中的代碼, f 在定義是沒(méi)有使用var,所以他是一個(gè)全局變量。因此,這里會(huì)通過(guò)閉包訪問(wèn)到外部的變量 f, 重新賦值,現(xiàn)在執(zhí)行 f 函數(shù)返回值已經(jīng)成為 false 了。而 g 則不會(huì)有這個(gè)問(wèn)題,這里是一個(gè)函數(shù)內(nèi)定義的 g,不會(huì)影響到外部的 g 函數(shù)。所以最后的結(jié)果就是 false。

四、原型&繼承


1. 代碼輸出結(jié)果

function Person(name) {
    this.name = name
}
var p2 = new Person('king');
console.log(p2.__proto__) //Person.prototype
console.log(p2.__proto__.__proto__) //Object.prototype
console.log(p2.__proto__.__proto__.__proto__) // null
console.log(p2.__proto__.__proto__.__proto__.__proto__)//null后面沒(méi)有了,報(bào)錯(cuò)
console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null后面沒(méi)有了,報(bào)錯(cuò)
console.log(p2.constructor)//Person
console.log(p2.prototype)//undefined p2是實(shí)例,沒(méi)有prototype屬性
console.log(Person.constructor)//Function 一個(gè)空函數(shù)
console.log(Person.prototype)//打印出Person.prototype這個(gè)對(duì)象里所有的方法和屬性
console.log(Person.prototype.constructor)//Person
console.log(Person.prototype.__proto__)// Object.prototype
console.log(Person.__proto__) //Function.prototype
console.log(Function.prototype.__proto__)//Object.prototype
console.log(Function.__proto__)//Function.prototype
console.log(Object.__proto__)//Function.prototype
console.log(Object.prototype.__proto__)//null

這道義題目考察原型、原型鏈的基礎(chǔ),記住就可以了。

2. 代碼輸出結(jié)果

// a
function Foo () {
 getName = function () {
   console.log(1);
 }
 return this;
}
// b
Foo.getName = function () {
 console.log(2);
}
// c
Foo.prototype.getName = function () {
 console.log(3);
}
// d
var getName = function () {
 console.log(4);
}
// e
function getName () {
 console.log(5);
}

Foo.getName();           // 2
getName();               // 4
Foo().getName();         // 1
getName();               // 1 
new Foo.getName();       // 2
new Foo().getName();     // 3
new new Foo().getName(); // 3

輸出結(jié)果:2 4 1 1 2 3 3

解析:

  1. Foo.getName(),F(xiàn)oo為一個(gè)函數(shù)對(duì)象,對(duì)象都可以有屬性,b 處定義Foo的getName屬性為函數(shù),輸出2;
  2. getName(),這里看d、e處,d為函數(shù)表達(dá)式,e為函數(shù)聲明,兩者區(qū)別在于變量提升,函數(shù)聲明的 5 會(huì)被后邊函數(shù)表達(dá)式的 4 覆蓋;
  3. Foo().getName(),這里要看a處,在Foo內(nèi)部將全局的getName重新賦值為 console.log(1) 的函數(shù),執(zhí)行Foo()返回 this,這個(gè)this指向window,F(xiàn)oo().getName() 即為window.getName(),輸出 1;
  4. getName(),上面3中,全局的getName已經(jīng)被重新賦值,所以這里依然輸出 1;
  5. new Foo.getName(),這里等價(jià)于 new (Foo.getName()),先執(zhí)行 Foo.getName(),輸出 2,然后new一個(gè)實(shí)例;
  6. new Foo().getName(),這里等價(jià)于 (new Foo()).getName(), 先new一個(gè)Foo的實(shí)例,再執(zhí)行這個(gè)實(shí)例的getName方法,但是這個(gè)實(shí)例本身沒(méi)有這個(gè)方法,所以去原型鏈__protot__上邊找,實(shí)例.protot === Foo.prototype,所以輸出 3;
  7. new new Foo().getName(),這里等價(jià)于new (new Foo().getName()),如上述6,先輸出 3,然后new 一個(gè) new Foo().getName() 的實(shí)例。

3. 代碼輸出結(jié)果

var F = function() {};
Object.prototype.a = function() {
  console.log('a');
};
Function.prototype.b = function() {
  console.log('b');
}
var f = new F();
f.a();
f.b();
F.a();
F.b()

輸出結(jié)果:

a
Uncaught TypeError: f.b is not a function
a
b

解析:

  1. f 并不是 Function 的實(shí)例,因?yàn)樗緛?lái)就不是構(gòu)造函數(shù),調(diào)用的是 Function 原型鏈上的相關(guān)屬性和方法,只能訪問(wèn)到 Object 原型鏈。所以 f.a() 輸出 a ,而 f.b() 就報(bào)錯(cuò)了。
  2. F 是個(gè)構(gòu)造函數(shù),而 F 是構(gòu)造函數(shù) Function 的一個(gè)實(shí)例。因?yàn)?F instanceof Object === true,F(xiàn) instanceof Function === true,由此可以得出結(jié)論:F 是 Object 和 Function 兩個(gè)的實(shí)例,即 F 能訪問(wèn)到 a, 也能訪問(wèn)到 b。所以 F.a() 輸出 a ,F(xiàn).b() 輸出 b。

4. 代碼輸出結(jié)果

function Foo(){
    Foo.a = function(){
        console.log(1);
    }
    this.a = function(){
        console.log(2)
    }
}

Foo.prototype.a = function(){
    console.log(3);
}

Foo.a = function(){
    console.log(4);
}

Foo.a();
let obj = new Foo();
obj.a();
Foo.a();

輸出結(jié)果:4 2 1

解析:

  1. Foo.a() 這個(gè)是調(diào)用 Foo 函數(shù)的靜態(tài)方法 a,雖然 Foo 中有優(yōu)先級(jí)更高的屬性方法 a,但 Foo 此時(shí)沒(méi)有被調(diào)用,所以此時(shí)輸出 Foo 的靜態(tài)方法 a 的結(jié)果:4
  2. let obj = new Foo(); 使用了 new 方法調(diào)用了函數(shù),返回了函數(shù)實(shí)例對(duì)象,此時(shí) Foo 函數(shù)內(nèi)部的屬性方法初始化,原型鏈建立。
  3. obj.a() ; 調(diào)用 obj 實(shí)例上的方法 a,該實(shí)例上目前有兩個(gè) a 方法:一個(gè)是內(nèi)部屬性方法,另一個(gè)是原型上的方法。當(dāng)這兩者都存在時(shí),首先查找 ownProperty ,如果沒(méi)有才去原型鏈上找,所以調(diào)用實(shí)例上的 a 輸出:2
  4. Foo.a() ; 根據(jù)第2步可知 Foo 函數(shù)內(nèi)部的屬性方法已初始化,覆蓋了同名的靜態(tài)方法,所以輸出:1

5. 代碼輸出結(jié)果

function Dog() {
  this.name = 'puppy'
}
Dog.prototype.bark = () => {
  console.log('woof!woof!')
}
const dog = new Dog()
console.log(Dog.prototype.constructor === Dog && dog.constructor === Dog && dog instanceof Dog)

輸出結(jié)果:true

解析:

因?yàn)閏onstructor是prototype上的屬性,所以dog.constructor實(shí)際上就是指向Dog.prototype.constructor;constructor屬性指向構(gòu)造函數(shù)。instanceof而實(shí)際檢測(cè)的是類(lèi)型是否在實(shí)例的原型鏈上。

constructor是prototype上的屬性,這一點(diǎn)很容易被忽略掉。constructor和instanceof 的作用是不同的,感性地來(lái)說(shuō),constructor的限制比較嚴(yán)格,它只能?chē)?yán)格對(duì)比對(duì)象的構(gòu)造函數(shù)是不是指定的值;而instanceof比較松散,只要檢測(cè)的類(lèi)型在原型鏈上,就會(huì)返回true。

6. 代碼輸出結(jié)果

var A = {n: 4399};
var B =  function(){this.n = 9999};
var C =  function(){var n = 8888};
B.prototype = A;
C.prototype = A;
var b = new B();
var c = new C();
A.n++
console.log(b.n);
console.log(c.n);

輸出結(jié)果:9999 4400

解析:

  1. console.log(b.n),在查找b.n是首先查找 b 對(duì)象自身有沒(méi)有 n 屬性,如果沒(méi)有會(huì)去原型(prototype)上查找,當(dāng)執(zhí)行var b = new B()時(shí),函數(shù)內(nèi)部this.n=9999(此時(shí)this指向 b) 返回b對(duì)象,b對(duì)象有自身的n屬性,所以返回 9999。
  2. console.log(c.n),同理,當(dāng)執(zhí)行var c = new C()時(shí),c對(duì)象沒(méi)有自身的n屬性,向上查找,找到原型 (prototype)上的 n 屬性,因?yàn)?A.n++(此時(shí)對(duì)象A中的n為4400), 所以返回4400。

7. 代碼輸出問(wèn)題

function A(){
}
function B(a){
  this.a = a;
}
function C(a){
  if(a){
this.a = a;
  }
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;
 
console.log(new A().a);
console.log(new B().a);
console.log(new C(2).a);

輸出結(jié)果:1 undefined 2

解析:

  1. console.log(new A().a),new A()為構(gòu)造函數(shù)創(chuàng)建的對(duì)象,本身沒(méi)有a屬性,所以向它的原型去找,發(fā)現(xiàn)原型的a屬性的屬性值為1,故該輸出值為1;
  2. console.log(new B().a),ew B()為構(gòu)造函數(shù)創(chuàng)建的對(duì)象,該構(gòu)造函數(shù)有參數(shù)a,但該對(duì)象沒(méi)有傳參,故該輸出值為undefined;
  3. console.log(new C(2).a),new C()為構(gòu)造函數(shù)創(chuàng)建的對(duì)象,該構(gòu)造函數(shù)有參數(shù)a,且傳的實(shí)參為2,執(zhí)行函數(shù)內(nèi)部,發(fā)現(xiàn)if為真,執(zhí)行this.a = 2,故屬性a的值為2。

8 代碼輸出問(wèn)題

function Parent() {
    this.a = 1;
    this.b = [1, 2, this.a];
    this.c = { demo: 5 };
    this.show = function () {
        console.log(this.a , this.b , this.c.demo );
    }
}

function Child() {
    this.a = 2;
    this.change = function () {
        this.b.push(this.a);
        this.a = this.b.length;
        this.c.demo = this.a++;
    }
}

Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;
parent.show();
child1.show();
child2.show();
child1.change();
child2.change();
parent.show();
child1.show();
child2.show();

輸出結(jié)果:

parent.show(); // 1  [1,2,1] 5

child1.show(); // 11 [1,2,1] 5
child2.show(); // 12 [1,2,1] 5

parent.show(); // 1 [1,2,1] 5

child1.show(); // 5 [1,2,1,11,12] 5

child2.show(); // 6 [1,2,1,11,12] 5

這道題目值得審題,他涉及到的知識(shí)點(diǎn)很多,例如this的指向、原型、原型鏈、類(lèi)的繼承、數(shù)據(jù)類(lèi)型等。

解析

  1. parent.show(),可以直接獲得所需的值,沒(méi)啥好說(shuō)的;
  2. child1.show(),?Child?的構(gòu)造函數(shù)原本是指向 ?Child?的,題目顯式將 ?Child?類(lèi)的原型對(duì)象指向了 ?Parent?類(lèi)的一個(gè)實(shí)例,需要注意 ?Child.prototype?指向的是 ?Parent?的實(shí)例 ?parent?,而不是指向 ?Parent?這個(gè)類(lèi)。
  3. child2.show(),這個(gè)也沒(méi)啥好說(shuō)的;
  4. parent.show(),?parent?是一個(gè) ?Parent?類(lèi)的實(shí)例,?Child.prorotype?指向的是 ?Parent?類(lèi)的另一個(gè)實(shí)例,兩者在堆內(nèi)存中互不影響,所以上述操作不影響 ?parent?實(shí)例,所以輸出結(jié)果不變;
  5. child1.show(),?child1?執(zhí)行了 ?change()?方法后,發(fā)生了怎樣的變化呢?
    • this.b.push(this.a),由于this的動(dòng)態(tài)指向特性,this.b會(huì)指向 ?Child.prototype?上的b數(shù)組,this.a會(huì)指向 ?child1?的a屬性,所以 ?Child.prototype.b?變成了[1,2,1,11];
    • this.a = this.b.length,這條語(yǔ)句中 ?this.a?和 ?this.b?的指向與上一句一致,故結(jié)果為 ?child1.a?變?yōu)?;
    • this.c.demo = this.a++,由于 ?child1?自身屬性并沒(méi)有c這個(gè)屬性,所以此處的 ?this.c?會(huì)指向 ?Child.prototype.c?,?this.a?值為4,為原始類(lèi)型,故賦值操作時(shí)會(huì)直接賦值,?Child.prototype.c.demo?的結(jié)果為4,而 ?this.a?隨后自增為5(4 + 1 = 5)。
  6. ?child2?執(zhí)行了 ?change()?方法, 而 ?child2?和 ?child1?均是 ?Child?類(lèi)的實(shí)例,所以他們的原型鏈指向同一個(gè)原型對(duì)象 ?Child.prototype?,也就是同一個(gè) ?parent?實(shí)例,所以 ?child2.change()?中所有影響到原型對(duì)象的語(yǔ)句都會(huì)影響 ?child1?的最終輸出結(jié)果。
    • this.b.push(this.a),由于this的動(dòng)態(tài)指向特性,this.b會(huì)指向 ?Child.prototype?上的b數(shù)組,this.a會(huì)指向 ?child2?的a屬性,所以 ?Child.prototype.b?變成了[1,2,1,11,12];
    • this.a = this.b.length,這條語(yǔ)句中 ?this.a?和 ?this.b?的指向與上一句一致,故結(jié)果為 ?child2.a?變?yōu)?;
    • this.c.demo = this.a++,由于 ?child2?自身屬性并沒(méi)有c這個(gè)屬性,所以此處的 ?this.c?會(huì)指向 ?Child.prototype.c?,故執(zhí)行結(jié)果為 ?Child.prototype.c.demo?的值變?yōu)?nbsp;?child2.a?的值5,而 ?child2.a?最終自增為6(5 + 1 = 6)。

9. 代碼輸出結(jié)果

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
};

function SubType(){
    this.subproperty = false;
}

SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};

var instance = new SubType();
console.log(instance.getSuperValue());

輸出結(jié)果:true

實(shí)際上,這段代碼就是在實(shí)現(xiàn)原型鏈繼承,SubType繼承了SuperType,本質(zhì)是重寫(xiě)了SubType的原型對(duì)象,代之以一個(gè)新類(lèi)型的實(shí)例。SubType的原型被重寫(xiě)了,所以instance.constructor指向的是SuperType。具體如下:



以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)