代碼輸出結果也是面試中??嫉念}目,一段代碼中可能涉及到很多的知識點,這就考察到了應聘者的基礎能力。在前端面試中,??嫉拇a輸出問題主要涉及到以下知識點:異步編程、事件循環(huán)、this指向、作用域、變量提升、閉包、原型、繼承等,這些知識點往往不是單獨出現的,而是在同一段代碼中包含多個知識點。所以,筆者將這些問題大致分為四類進行討論。這里不會系統(tǒng)的闡述基礎知識,而是通過面試例題的形式,來講述每個題目的知識點以及代碼的執(zhí)行過程。如果會了這些例題,在前端面試中多數代碼輸出問題就可以輕而易舉的解決了。
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
輸出結果如下:
1
2
4
promise.then 是微任務,它會在所有的宏任務執(zhí)行完之后才會執(zhí)行,同時需要promise內部的狀態(tài)發(fā)生變化,因為這里內部沒有發(fā)生變化,一直處于pending狀態(tài),所以不輸出3。
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);
輸出結果如下:
promise1
1 Promise{<resolved>: resolve1}
2 Promise{<pending>}
resolve1
需要注意的是,直接打印promise1,會打印出它的狀態(tài)值和參數。
代碼執(zhí)行過程如下:
promise1
?;resolve
?函數, 將 ?promise1
?的狀態(tài)改變?yōu)?nbsp;?resolved
?, 并將結果保存下來;promise1.then
?這個微任務,將它放入微任務隊列;promise2
?是一個新的狀態(tài)為 ?pending
?的 ?Promise
?;promise1
?的狀態(tài)是 ?resolved
?;promise2
?的狀態(tài)是 ?pending
?;promise1.then
?這個微任務且狀態(tài)為 ?resolved
?,執(zhí)行它。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);
輸出結果如下:
1
2
4
timerStart
timerEnd
success
代碼執(zhí)行過程如下:
steTimeout
?,它是一個宏任務,放入宏任務隊列;Promise
?的狀態(tài)此時還是 ?pending
?,所以 ?promise.then
?先不執(zhí)行;steTimeout
?;timerStart
?,然后遇到了 ?resolve
?,將 ?promise
?的狀態(tài)改為 ?resolved
?且保存結果并將之前的 ?promise.then
?推入微任務隊列,再執(zhí)行 ?timerEnd
?;promise.then
?,打印出 ?resolve
?的結果。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');
輸出結果如下:
start
promise1
timer1
promise2
timer2
代碼執(zhí)行過程如下:
Promise.resolve().then
?是一個微任務,加入微任務隊列start
?Promise.resolve().then
?,打印出 ?promise1
?timer2
?,它是一個宏任務,將其加入宏任務隊列,此時宏任務隊列有兩個任務,分別是 ?timer1
?、?timer2
?;timer1
?,打印 ?timer1
?;Promise.resolve().then
?,它是一個微任務,加入微任務隊列promise2
?;timer2
?定時器,打印出 ?timer2
?;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);
})
輸出結果如下:
then:success1
這個題目考察的就是Promise的狀態(tài)在發(fā)生變化之后,就不會再發(fā)生變化。開始狀態(tài)由 pending
變?yōu)?nbsp;resolve
,說明已經變?yōu)橐淹瓿蔂顟B(tài),下面的兩個狀態(tài)的就不會再執(zhí)行,同時下面的catch也不會捕獲到錯誤。
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
輸出結果如下:
1
Promise {<fulfilled>: undefined}
Promise.resolve方法的參數如果是一個原始值,或者是一個不具有then方法的對象,則Promise.resolve方法返回一個新的Promise對象,狀態(tài)為resolved,Promise.resolve方法的參數,會同時傳給回調函數。
then方法接受的參數是函數,而如果傳遞的并非是一個函數,它實際上會將其解釋為then(null),這就會導致前一個Promise的結果會傳遞下面。
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)
輸出結果如下:
promise1 Promise {<pending>}
promise2 Promise {<pending>}
Uncaught (in promise) Error: error!!!
promise1 Promise {<fulfilled>: "success"}
promise2 Promise {<rejected>: Error: error!!}
Promise.resolve(1)
.then(res => {
console.log(res);
return 2;
})
.catch(err => {
return 3;
})
.then(res => {
console.log(res);
});
輸出結果如下:
1
2
Promise是可以鏈式調用的,由于每次調用 .then
或者 .catch
都會返回一個新的 promise,從而實現了鏈式調用, 它并不像一般任務的鏈式調用一樣return this。
上面的輸出結果之所以依次打印出1和2,是因為 resolve(1)
之后走的是第一個then方法,并沒有進catch里,所以第二個then中的res得到的實際上是第一個then的返回值。并且return 2會被包裝成 resolve(2)
,被最后的then打印輸出2。
Promise.resolve().then(() => {
return new Error('error!!!')
}).then(res => {
console.log("then: ", res)
}).catch(err => {
console.log("catch: ", err)
})
輸出結果如下:
"then: " "Error: error!!!"
返回任意一個非 promise 的值都會被包裹成 promise 對象,因此這里的 return new Error('error!!!')
也被包裹成了 return Promise.resolve(new Error('error!!!'))
,因此它會被then捕獲而不是catch。
const promise = Promise.resolve().then(() => {
return promise;
})
promise.catch(console.err)
輸出結果如下:
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
這里其實是一個坑,.then
或 .catch
返回的值不能是 promise 本身,否則會造成死循環(huán)。
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
輸出結果如下:
1
看到這個題目,好多的then,實際上只需要記住一個原則:.then
或 .catch
的參數期望是函數,傳入非函數則會發(fā)生值透傳。
第一個then和第二個then中傳入的都不是函數,一個是數字,一個是對象,因此發(fā)生了透傳,將 resolve(1)
的值直接傳到最后一個then里,直接打印出1。
Promise.reject('err!!!')
.then((res) => {
console.log('success', res)
}, (err) => {
console.log('error', err)
}).catch(err => {
console.log('catch', err)
})
輸出結果如下:
error err!!!
我們知道,.then
函數中的兩個參數:
也就是說 Promise.resolve('1')
的值會進入成功的函數,Promise.reject('2')
的值會進入失敗的函數。
在這道題中,錯誤直接被 then
的第二個參數捕獲了,所以就不會被 catch
捕獲了,輸出結果為: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
的第一參數中拋出了錯誤,那么他就不會被第二個參數不活了,而是被后面的 catch
捕獲到。
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函數', res)
})
輸出結果如下:
1
finally2
finally
finally2后面的then函數 2
.finally()
一般用的很少,只要記住以下幾點就可以了:
.finally()
?方法不管Promise對象最后的狀態(tài)如何都會執(zhí)行.finally()
?方法的回調函數不接受任何的參數,也就是說你在 ?.finally()
?函數中是無法知道Promise最終的狀態(tài)是 ?resolved
?還是 ?rejected
?的.finally()
的錯誤捕獲:
Promise.resolve('1')
.finally(() => {
console.log('finally1')
throw new Error('我是finally中拋出的異常')
})
.then(res => {
console.log('finally后面的then函數', res)
})
.catch(err => {
console.log('捕獲錯誤', err)
})
輸出結果為:
'finally1'
'捕獲錯誤' Error: 我是finally中拋出的異常
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))
輸出結果如下:
1
2
3
[1, 2, 3]
首先,定義了一個Promise,來異步執(zhí)行函數runAsync,該函數傳入一個值x,然后間隔一秒后打印出這個x。
之后再使用 Promise.all
來執(zhí)行這個函數,執(zhí)行的時候,看到一秒之后輸出了1,2,3,同時輸出了數組[1, 2, 3],三個函數是同步執(zhí)行的,并且在一個回調函數中返回了所有的結果。并且結果和函數的執(zhí)行順序是一致的。
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))
輸出結果如下:
// 1s后輸出
1
3
// 2s后輸出
2
Error: 2
// 4s后輸出
4
可以看到。catch捕獲到了第一個錯誤,在這道題目中最先的錯誤就是 runReject(2)
的結果。如果一組異步操作中有一個異常都不會進入 .then()
的第一個回調函數參數中。會被 .then()
的第二個回調函數捕獲。
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))
輸出結果如下:
1
'result: ' 1
2
3
then只會捕獲第一個成功的方法,其他的函數雖然還會繼續(xù)執(zhí)行,但是不是被then捕獲了。
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));
輸出結果如下:
0
Error: 0
1
2
3
可以看到在catch捕獲到第一個錯誤之后,后面的代碼還不執(zhí)行,不過不會再被捕獲了。
注意:all
和 race
傳入的數組中如果有會拋出異常的異步任務,那么只有最先拋出的錯誤會被捕獲,并且是被then的第二個參數或者后面的catch捕獲;但并不會影響數組中其它的異步任務的執(zhí)行。
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
async1();
console.log('start')
輸出結果如下:
async1 start
async2
start
async1 end
代碼的執(zhí)行過程如下:
async1 start
?,之后遇到了 ?await
?,它會阻塞 ?async1
?后面代碼的執(zhí)行,因此會先去執(zhí)行 ?async2
?中的同步代碼 ?async2
?,然后跳出 ?async1
?;async1
?函數后,執(zhí)行同步代碼 ?start
?;await
?后面的內容 ?async1 end
?。這里可以理解為await后面的語句相當于放到了new Promise中,下一行及之后的語句相當于放在Promise.then中。
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")
輸出結果如下:
async1 start
async2
start
async1 end
timer2
timer3
timer1
代碼的執(zhí)行過程如下:
async1
?,打印出 ?async1 start
?;async2
?,進入 ?async2
?,遇到定時器 ?timer2
?,加入宏任務隊列,之后打印 ?async2
?;async2
?阻塞了后面代碼的執(zhí)行,所以執(zhí)行后面的定時器 ?timer3
?,將其加入宏任務隊列,之后打印 ?start
?;async2
?后面的代碼,打印出 ?async1 end
?,遇到定時器?timer1
?,將其加入宏任務隊列;timer2
?,?timer3
?,?timer1
?,沒有微任務,所以直接所有的宏任務按照先進先出的原則執(zhí)行。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')
輸出結果如下:
script start
async1 start
promise1
script end
這里需要注意的是在 async1
中 await
后面的Promise是沒有返回值的,也就是它的狀態(tài)始終是 pending
狀態(tài),所以在 await
之后的內容是不會執(zhí)行的,包括 async1
后面的 .then
。
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')
這里是對上面一題進行了改造,加上了resolve。
輸出結果如下:
script start
async1 start
promise1
script end
promise1 resolve
async1 success
async1 end
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')
輸出結果如下:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
代碼執(zhí)行過程如下:
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))
輸出結果如下:
async2
Uncaught (in promise) error
可以看到,如果async函數中拋出了錯誤,就會終止錯誤結果,不會繼續(xù)向下執(zhí)行。
如果想要讓錯誤不足之處后面的代碼執(zhí)行,可以使用catch來捕獲:
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')
這樣的輸出結果就是:
script start
error!!!
async1
async1 success
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);
輸出結果如下:
3
7
4
1
2
5
Promise{<resolved>: 1}
代碼的執(zhí)行過程如下:
resolve(6)
?不會再執(zhí)行;console.log(p)
?打印出 ?Promise{<resolved>: 1}
?;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)
輸出結果如下:
script start
async1
promise1
script end
1
timer2
timer1
代碼的執(zhí)行過程如下:
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í)行結果為如下:
resolve1
finally undefined
timer1
Promise{<resolved>: undefined}
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')
})
})
輸出結果如下:
1
7
6
8
2
4
3
5
9
11
10
12
(1)第一輪事件循環(huán)流程分析如下:
console.log
?,輸出1。setTimeout
?,其回調函數被分發(fā)到宏任務Event Queue中。暫且記為 ?setTimeout1
?。process.nextTick()
?,其回調函數被分發(fā)到微任務Event Queue中。記為 ?process1
?。Promise
?,?new Promise
?直接執(zhí)行,輸出7。?then
?被分發(fā)到微任務Event Queue中。記為 ?then1
?。setTimeout
?,其回調函數被分發(fā)到宏任務Event Queue中,記為 ?setTimeout2
?。宏任務Event Queue | 微任務Event Queue |
---|---|
setTimeout1 | process1 |
setTimeout2 | then1 |
上表是第一輪事件循環(huán)宏任務結束時各Event Queue的情況,此時已經輸出了1和7。發(fā)現了 process1
和 then1
兩個微任務:
process1
?,輸出6。then1
?,輸出8。第一輪事件循環(huán)正式結束,這一輪的結果是輸出1,7,6,8。
(2)第二輪時間循環(huán)從 setTimeout1
宏任務開始:
process.nextTick()
?,同樣將其分發(fā)到微任務Event Queue中,記為 ?process2
?。new Promise
?立即執(zhí)行輸出4,?then
?也分發(fā)到微任務Event Queue中,記為 ?then2
?。宏任務Event Queue | 微任務Event Queue |
---|---|
setTimeout2 | process2 |
then2 |
第二輪事件循環(huán)宏任務結束,發(fā)現有 process2
和 then2
兩個微任務可以執(zhí)行:
第二輪事件循環(huán)結束,第二輪輸出2,4,3,5。
(3)第三輪事件循環(huán)開始,此時只剩setTimeout2了,執(zhí)行。
process.nextTick()
?分發(fā)到微任務Event Queue中。記為 ?process3
?。new Promise
?,輸出11。then
?分發(fā)到微任務Event Queue中,記為 ?then3
?。宏任務Event Queue | 微任務Event Queue |
---|---|
process3 | |
then3 |
第三輪事件循環(huán)宏任務執(zhí)行結束,執(zhí)行兩個微任務 process3
和 then3
:
第三輪事件循環(huán)結束,第三輪輸出9,11,10,12。
整段代碼,共進行了三次事件循環(huán),完整的輸出為1,7,6,8,2,4,3,5,9,11,10,12。
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)
輸出結果如下:
1
3
8
4
2
5
6
7
代碼執(zhí)行過程如下:
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);
代碼輸出結果如下:
1
4
7
5
2
3
6
代碼執(zhí)行過程如下:
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í)行結果如下:
1
3
5
6
在這道題目中,我們需要知道,無論是thne還是catch中,只要throw 拋出了錯誤,就會被catch捕獲,如果沒有throw出錯誤,就被繼續(xù)執(zhí)行后面的then。
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);
輸出結果為:
2
3
7
8
4
5
6
1
代碼執(zhí)行過程如下:
做完這道題目,我們就需要格外注意,每個定時器的時間,并不是所有定時器的時間都為0哦。
function foo() {
console.log( this.a );
}
function doFoo() {
foo();
}
var obj = {
a: 1,
doFoo: doFoo
};
var a = 2;
obj.doFoo()
輸出結果:2
在Javascript中,this指向函數執(zhí)行時的當前對象。在執(zhí)行foo的時候,執(zhí)行環(huán)境就是doFoo函數,執(zhí)行環(huán)境為全局。所以,foo中的this是指向window的,所以會打印出2。
var a = 10
var obj = {
a: 20,
say: () => {
console.log(this.a)
}
}
obj.say()
var anotherObj = { a: 30 }
obj.say.apply(anotherObj)
輸出結果:10 10
我么知道,箭頭函數時不綁定this的,它的this來自原其父級所處的上下文,所以首先會打印全局中的 a 的值10。后面雖然讓say方法指向了另外一個對象,但是仍不能改變箭頭函數的特性,它的this仍然是指向全局的,所以依舊會輸出10。
但是,如果是普通函數,那么就會有完全不一樣的結果:
var a = 10
var obj = {
a: 20,
say(){
console.log(this.a)
}
}
obj.say()
var anotherObj={a:30}
obj.say.apply(anotherObj)
輸出結果:20 30
這時,say方法中的this就會指向他所在的對象,輸出其中的a的值。
function a() {
console.log(this);
}
a.call(null);
打印結果:window對象
根據ECMAScript262規(guī)范規(guī)定:如果第一個參數傳入的對象調用者是null或者undefined,call方法將把全局對象(瀏覽器上是window對象)作為this的值。所以,不管傳入null 還是 undefined,其this都是全局對象window。所以,在瀏覽器上答案是輸出 window 對象。
要注意的是,在嚴格模式中,null 就是 null,undefined 就是 undefined:
'use strict';
function a() {
console.log(this);
}
a.call(null); // null
a.call(undefined); // undefined
var obj = {
name: 'cuggz',
fun: function(){
console.log(this.name);
}
}
obj.fun() // cuggz
new obj.fun() // undefined
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();
輸出結果:
1111 window對象
1111 obj對象
window對象
解析:
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();
輸出結果:bar bar undefined bar
解析:
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
這道題目看清起來有點亂,但是實際上是考察this指向的:
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
fn();
arguments[0]();
}
};
obj.method(fn, 1);
輸出結果: 10 2
解析:
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
輸出結果: 2 1 1
解析:
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
輸出結果:3 6
解析:
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
輸出結果: 20 10 20
解析:
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
輸出結果: undefined 6
解析:
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
輸出結果: 2 3 2 4
解析:
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
輸出結果: 2 2 3
這道題目和上面題目差不多,主要都是考察this綁定的優(yōu)先級。記住以下結論即可:**this綁定的優(yōu)先級:**new綁定 > 顯式綁定 > 隱式綁定 > 默認綁定。
(function(){
var x = y = 1;
})();
var z;
console.log(y); // 1
console.log(z); // undefined
console.log(x); // Uncaught ReferenceError: x is not defined
這段代碼的關鍵在于:var x = y = 1; 實際上這里是從右往左執(zhí)行的,首先執(zhí)行y = 1, 因為y沒有使用var聲明,所以它是一個全局變量,然后第二步是將y賦值給x,講一個全局變量賦值給了一個局部變量,最終,x是一個局部變量,y是一個全局變量,所以打印x是報錯。
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);
輸出結果:
undefined
undefined
3
3
undefined
3
這個題目和上面題目考察的知識點類似,b賦值為3,b此時是一個全局變量,而將3賦值給a,a是一個局部變量,所以最后打印的時候,a仍舊是undefined。
var friendName = 'World';
(function() {
if (typeof friendName === 'undefined') {
var friendName = 'Jack';
console.log('Goodbye ' + friendName);
} else {
console.log('Hello ' + friendName);
}
})();
輸出結果:Goodbye Jack
我們知道,在 JavaScript中, Function 和 var 都會被提升(變量提升),所以上面的代碼就相當于:
var name = 'World!';
(function () {
var name;
if (typeof name === 'undefined') {
name = 'Jack';
console.log('Goodbye ' + name);
} else {
console.log('Hello ' + name);
}
})();
這樣,答案就一目了然了。
function fn1(){
console.log('fn1')
}
var fn2
fn1()
fn2()
fn2 = function() {
console.log('fn2')
}
fn2()
輸出結果:
fn1
Uncaught TypeError: fn2 is not a function
fn2
這里也是在考察變量提升,關鍵在于第一個fn2(),這時fn2仍是一個undefined的變量,所以會報錯fn2不是一個函數。
function a() {
var temp = 10;
function b() {
console.log(temp); // 10
}
b();
}
a();
function a() {
var temp = 10;
b();
}
function b() {
console.log(temp); // 報錯 Uncaught ReferenceError: temp is not defined
}
a();
在上面的兩段代碼中,第一段是可以正常輸出,這個應該沒啥問題,關鍵在于第二段代碼,它會報錯Uncaught ReferenceError: temp is not defined。這時因為在b方法執(zhí)行時,temp 的值為undefined。
var a=3;
function c(){
alert(a);
}
(function(){
var a=4;
c();
})();
js中變量的作用域鏈與定義時的環(huán)境有關,與執(zhí)行時無關。執(zhí)行環(huán)境只會改變this、傳遞的參數、全局變量等
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);
輸出結果:
undefined 0 0 0
undefined 0 1 2
undefined 0 1 1
這是一道關于閉包的題目,對于fun方法,調用之后返回的是一個對象。我們知道,當調用函數的時候傳入的實參比函數聲明時指定的形參個數要少,剩下的形參都將設置為undefined值。所以 console.log(o);
會輸出undefined。而a就是是fun(0)返回的那個對象。也就是說,函數fun中參數 n 的值是0,而返回的那個對象中,需要一個參數n,而這個對象的作用域中沒有n,它就繼續(xù)沿著作用域向上一級的作用域中尋找n,最后在函數fun中找到了n,n的值是0。了解了這一點,其他運算就很簡單了,以此類推。
f = function() {return true;};
g = function() {return false;};
(function() {
if (g() && [] == ![]) {
f = function f() {return false;};
function g() {return true;} //在匿名函數內部發(fā)生函數提升 因此if判斷中的g()返回true
}
})();
console.log(f());
輸出結果: false
這里首先定義了兩個變量f和g,我們知道變量是可以重新賦值的。后面是一個匿名自執(zhí)行函數,在 if 條件中調用了函數 g(),由于在匿名函數中,又重新定義了函數g,就覆蓋了外部定義的變量g,所以,這里調用的是內部函數 g 方法,返回為 true。第一個條件通過,進入第二個條件。
第二個條件是[] == ![],先看 ![] ,在 JavaScript 中,當用于布爾運算時,比如在這里,對象的非空引用被視為 true,空引用 null 則被視為 false。由于這里不是一個 null, 而是一個沒有元素的數組,所以 [] 被視為 true, 而 ![] 的結果就是 false 了。當一個布爾值參與到條件運算的時候,true 會被看作 1, 而 false 會被看作 0?,F在條件變成了 [] == 0 的問題了,當一個對象參與條件比較的時候,它會被求值,求值的結果是數組成為一個字符串,[] 的結果就是 '' ,而 '' 會被當作 0 ,所以,條件成立。
兩個條件都成立,所以會執(zhí)行條件中的代碼, f 在定義是沒有使用var,所以他是一個全局變量。因此,這里會通過閉包訪問到外部的變量 f, 重新賦值,現在執(zhí)行 f 函數返回值已經成為 false 了。而 g 則不會有這個問題,這里是一個函數內定義的 g,不會影響到外部的 g 函數。所以最后的結果就是 false。
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后面沒有了,報錯
console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null后面沒有了,報錯
console.log(p2.constructor)//Person
console.log(p2.prototype)//undefined p2是實例,沒有prototype屬性
console.log(Person.constructor)//Function 一個空函數
console.log(Person.prototype)//打印出Person.prototype這個對象里所有的方法和屬性
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
這道義題目考察原型、原型鏈的基礎,記住就可以了。
// 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
輸出結果:2 4 1 1 2 3 3
解析:
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()
輸出結果:
a
Uncaught TypeError: f.b is not a function
a
b
解析:
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();
輸出結果:4 2 1
解析:
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)
輸出結果:true
解析:
因為constructor是prototype上的屬性,所以dog.constructor實際上就是指向Dog.prototype.constructor;constructor屬性指向構造函數。instanceof而實際檢測的是類型是否在實例的原型鏈上。
constructor是prototype上的屬性,這一點很容易被忽略掉。constructor和instanceof 的作用是不同的,感性地來說,constructor的限制比較嚴格,它只能嚴格對比對象的構造函數是不是指定的值;而instanceof比較松散,只要檢測的類型在原型鏈上,就會返回true。
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);
輸出結果:9999 4400
解析:
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);
輸出結果:1 undefined 2
解析:
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();
輸出結果:
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
這道題目值得審題,他涉及到的知識點很多,例如this的指向、原型、原型鏈、類的繼承、數據類型等。
解析:
Child
?的構造函數原本是指向 ?Child
?的,題目顯式將 ?Child
?類的原型對象指向了 ?Parent
?類的一個實例,需要注意 ?Child.prototype
?指向的是 ?Parent
?的實例 ?parent
?,而不是指向 ?Parent
?這個類。parent
?是一個 ?Parent
?類的實例,?Child.prorotype
?指向的是 ?Parent
?類的另一個實例,兩者在堆內存中互不影響,所以上述操作不影響 ?parent
?實例,所以輸出結果不變;child1
?執(zhí)行了 ?change()
?方法后,發(fā)生了怎樣的變化呢?Child.prototype
?上的b數組,this.a會指向 ?child1
?的a屬性,所以 ?Child.prototype.b
?變成了[1,2,1,11];this.a
?和 ?this.b
?的指向與上一句一致,故結果為 ?child1.a
?變?yōu)?;child1
?自身屬性并沒有c這個屬性,所以此處的 ?this.c
?會指向 ?Child.prototype.c
?,?this.a
?值為4,為原始類型,故賦值操作時會直接賦值,?Child.prototype.c.demo
?的結果為4,而 ?this.a
?隨后自增為5(4
+ 1 = 5)。child2
?執(zhí)行了 ?change()
?方法, 而 ?child2
?和 ?child1
?均是 ?Child
?類的實例,所以他們的原型鏈指向同一個原型對象 ?Child.prototype
?,也就是同一個 ?parent
?實例,所以 ?child2.change()
?中所有影響到原型對象的語句都會影響 ?child1
?的最終輸出結果。Child.prototype
?上的b數組,this.a會指向 ?child2
?的a屬性,所以 ?Child.prototype.b
?變成了[1,2,1,11,12];this.a
?和 ?this.b
?的指向與上一句一致,故結果為 ?child2.a
?變?yōu)?;child2
?自身屬性并沒有c這個屬性,所以此處的 ?this.c
?會指向 ?Child.prototype.c
?,故執(zhí)行結果為 ?Child.prototype.c.demo
?的值變?yōu)?nbsp;?child2.a
?的值5,而 ?child2.a
?最終自增為6(5
+ 1 = 6)。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());
輸出結果:true
實際上,這段代碼就是在實現原型鏈繼承,SubType繼承了SuperType,本質是重寫了SubType的原型對象,代之以一個新類型的實例。SubType的原型被重寫了,所以instance.constructor指向的是SuperType。具體如下:
更多建議: