收藏 javascript-questions 這個倉庫很久了,趁著周末來鍛煉下自己的 JS 基礎水平
因為逐漸也在承擔一些面試作業,順便摘錄一些個人覺得比較適合面試的題目和方向
事件流(捕獲、冒泡)
源鏈接
以下代碼點擊結果是啥?
<div onclick="console.log('div')">
<p onclick="console.log('p')">
Click here!
</p>
</div>
答案
-> 依次列印 p 和 div
這道題考查事件流,在事件傳播中,有三個階段,捕獲->目標->冒泡,按照標簽來看,就是 div->p->div,但是默認只在冒泡階段處理事件
如果需要在捕獲階段處理事件,可用 addEventLister 并且傳入第三個引數為 true(默認是 false,即默認冒泡階段處理)
<div id="div">
<p id="p">
Click here!
</p>
</div>
<script>
document.getElementById('div').addEventListener('click', () => console.log('div'), true)
document.getElementById('p').addEventListener('click', () => console.log('p'), true)
</script>
call、apply、bind
源鏈接
以下代碼輸出結果是啥?
const person = { name: 'Lydia' };
function sayHi(age) {
return `${this.name} is ${age}`;
}
console.log(sayHi.call(person, 21));
console.log(sayHi.bind(person, 21));
答案
-> Lydia is 21 [Function: bound sayHi]
這道題主要考查 call 和 bind,當然我們面試的時候一般會把 apply 拿出來一起考察下
call、apply 以及 bind 都是為了改變 this 指向而生,而 call 和 apply 用法比較像,都會在函式呼叫時改變 this 指向,唯一的區別是 apply 的第二個引數是陣列,而 call 從第二個引數開始都是實際傳入該函式中的值,bind 與他們不一樣,它回傳的還是函式
new
源鏈接
以下代碼輸出結果是啥?
function Car() {
this.make = 'Lamborghini';
return { make: 'Maserati' };
}
const myCar = new Car();
console.log(myCar.make);
答案
-> Maserati
這題考查 new,如果對建構式以及 new 比較了解的就會知道,建構式里,如果回傳一個非 null 的物件(包括陣列),則將該物件值賦值給新建的物件
了解更多可以參考 一道有意思的筆試題引發的對于 new 運算子的思考
JSON.stringify
源鏈接
以下代碼輸出結果是啥?
const settings = {
username: 'lydiahallie',
level: 19,
health: 90,
};
const data = https://www.cnblogs.com/lessfish/p/JSON.stringify(settings, ['level', 'health']);
console.log(data);
答案
-> {"level":19,"health":90}
這道題主要考查 JSON.stringify 的第二個引數,當然它還能傳入第三個引數
語法:JSON.stringify(value[, replacer [, space]]),詳見 JSON.stringify
replacer
replacer 引數可以是一個函式或者一個陣列,作為函式,它有兩個引數,鍵(key)和值(value),它們都會被序列化
在開始時,replacer 函式會被傳入一個空字串作為 key 值,代表著要被 stringify 的這個物件,隨后每個物件或陣列上的屬性會被依次傳入
const settings = {
username: 'lydiahallie',
level: 19,
health: 90,
};
const data = https://www.cnblogs.com/lessfish/p/JSON.stringify(settings, (k, v) => {
console.log(k, v);
return v
});
console.log(data);
/*
// 這里其實最開始列印了個空字串,在開始時,replacer 函式會被傳入一個空字串作為 key 值,代表著要被 stringify 的這個物件
{ username:'lydiahallie', level: 19, health: 90 }
username lydiahallie
level 19
health 90
{"username":"lydiahallie","level":19,"health":90}
*/
const settings = {
username: 'lydiahallie',
level: 19,
health: 90,
};
const data = https://www.cnblogs.com/lessfish/p/JSON.stringify(settings, (k, v) => {
if (k ==='level') return v * 2
return v
});
console.log(data);
// {"username":"lydiahallie","level":38,"health":90}
當傳入陣列的時候,相對比較簡單,就是需要 picked 的 key(但是沒法深度 pick,只能 pick 第一層)
space
用來格式化,可以傳入數字或者字串
const settings = {
username: 'lydiahallie',
level: 19,
health: 90,
};
const data = https://www.cnblogs.com/lessfish/p/JSON.stringify(settings, null, 2); // 這時候和傳入' '(length 2)效果一樣
console.log(data);
/*
{
"username": "lydiahallie",
"level": 19,
"health": 90
}
*/
const settings = {
username: 'lydiahallie',
level: 19,
health: 90,
};
const data = https://www.cnblogs.com/lessfish/p/JSON.stringify(settings, null,'hello');
console.log(data);
/*
{
hello"username": "lydiahallie",
hello"level": 19,
hello"health": 90
}
*/
函式引數解構
源鏈接
以下代碼輸出結果是啥?
const myFunc = ({ x, y, z }) => {
console.log(x, y, z);
};
myFunc(1, 2, 3);
答案
-> undefined undefined undefined
這道題比較簡單,myFunc 函式需要的是一個物件引數,并且有 key x y 和 z,但是傳入的并不是物件,所以都取了他們的默認值 undefined
如果傳入物件,但是并沒有對應的 key,也同樣是 undefined
const myFunc = ({ x, y, z }) => {
console.log(x, y, z);
};
myFunc({ x: 1 }); // 1 undefined undefined
函式中的剩余引數
源鏈接
以下代碼輸出結果是啥?
function getItems(fruitList, ...args, favoriteFruit) {
return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange")
答案
-> SyntaxError
這道題就比較腦經急轉彎了,剩余引數只能放在最后,其實我覺得像上面代碼那樣其實也有使用場景,不知道未來會不會支持這樣的寫法
展開運算子
源鏈接
以下代碼輸出結果是啥?
const person = {
name: 'Lydia',
age: 21,
};
const changeAge = (x = { ...person }) => (x.age += 1);
const changeAgeAndName = (x = { ...person }) => {
x.age += 1;
x.name = 'Sarah';
};
changeAge(person);
changeAgeAndName();
console.log(person);
答案
-> { name: 'Lydia', age: 22 }
這道題的考點是展開運算子在物件中的使用,一般我們會用展開運算子來復制一個物件
當呼叫 changeAge(person) 時,引數傳入了 person,所以 (x.age += 1) 實際操作的和 person 是一個物件參考,而呼叫 changeAgeAndName() 時,因為沒有傳入引數,所以 x 其實是 { ...person },而這個其實是 person 的一個淺拷貝
可以考查下將 (x = { ...person }) 改成 (x = person)
||
源鏈接
以下代碼輸出結果是啥?
const one = false || {} || null;
const two = null || false || '';
const three = [] || 0 || true;
console.log(one, two, three);
答案
-> {} "" true
這道題的考點是 || 和假值, || 運算子會回傳第一個真值,如果都為假值,則回傳最后一個值
這里可以順便考查 &&,&& 運算子,一旦遇到了假值,便會停止往后
console.log(true && 0); // 0
console.log(true && 0 && true); // 0
console.log(1 && 2); // 2
console.log(1 && false); // false
console.log('' && false); // ''
falsy
源鏈接
以下哪些值是假值?
0;
new Number(0);
('');
(' ');
new Boolean(false);
undefined;
答案
-> 0 '' undefined
JS 中的假值有以下幾種:
false''NaNundefinednull0/-00n(BigInt(0))
除此之外,new Number、new Boolean 創建的其實都是物件,都是真值
物件中 key 重復
源鏈接
以下代碼輸出結果是啥?
const obj = { a: 'one', b: 'two', a: 'three' };
console.log(obj);
答案
-> { a: 'three', b: 'two' }
這其實更像是一個規范,當物件中 key 重復的時候,value 會被后面的替換,但是 key 的位置會是第一次出現的位置
一般是物件合并的時候會用到:
const oldObj = {
name: 'fish',
age: 30
}
const coverdObj = { name: 'lessfish' }
const newObj = {
...oldObj,
...coverdObj
}
console.log(newObj); // { name: 'lessfish', age: 30 }
Array Operators
有好幾道和陣列操作有關的題
這題 主要考查 push 操作回傳 push 后的陣列長度,這題 也是類似
這題 主要考查哪些陣列操作會改變原來的陣列(splice)
這題 主要考查 reduce 的使用
[1, 2, 3, 4].reduce((x, y) => console.log(x, y));
這題還是比較有意思的,reduce 函式呼叫中如果有第二個引數,則會被當作第一次迭代中的 previous 值(也就是第一個引數 x),如果沒有第二個引數,則陣列第一個元素會被當作第一次迭代的 previous
所以上面的代碼,如果 reduce 有第二個引數,會被迭代 4 次,如果沒有,則是迭代三次
再看代碼,因為沒有第二個引數,所以第一次迭代引數是 1 和 2,reduce 迭代中的回傳會被當作下次迭代的 previous,但是這里沒回傳,所以就是 undefined,而第二個引數 y 就是陣列元素
Object Operators
這題 主要考查用 Object.defineProperty 定義的物件中的屬性默認不可列舉,不能用 Object.keys 拿到
這題 主要考查 Object.freeze,顧名思義它能冰凍住物件,使得物件不能增、刪、修改鍵值對(但是注意,它只是 freeze 了第一層,可以參考 這題)
這題 考查 Object.seal,它能阻止物件新增、洗掉屬性,但是對于已有的屬性依然可以修改其值(注意和 freeze 一樣同樣只是第一層)
delete
源鏈接
以下代碼輸出結果是啥?
const name = 'Lydia';
age = 21;
console.log(delete name);
console.log(delete age);
答案
-> false true
這題考查 delete 操作
首先 delete 回傳一個布林值,代表是否洗掉成功,然后 var const let 定義的變數,都不能被洗掉,只有全域變數才能被洗掉
這里要注意下 name,因為瀏覽器中默認掛了個全域變數 name,所以 delete name 是 ok 的
暫時性死區
- 111
- 129
這兩題都是暫時性死區相關,注意下 var 和 let const 是有差異的,var 的話會變數宣告提升(但是是 undefined),但是 let 和 const 并不會初始化
Object toString
源鏈接
以下代碼輸出結果是啥?
const animals = {};
let dog = { emoji: '??' }
let cat = { emoji: '??' }
animals[dog] = { ...dog, name: "Mara" }
animals[cat] = { ...cat, name: "Sara" }
console.log(animals[dog])
答案
-> { emoji: "??", name: "Sara" }
當物件的 key 也是物件的時候,會自動呼叫 toString 轉為 [object Object]
const animals = {};
let dog = { emoji: '??' }
let cat = { emoji: '??' }
animals[dog] = { ...dog, name: "Mara" }
animals[cat] = { ...cat, name: "Sara" }
console.log(animals[dog] === animals[cat]) // true
這題 大同小異
物件參考
源鏈接
以下代碼輸出結果是啥?
let person = { name: 'Lydia' };
const members = [person];
person = null;
console.log(members);
答案
-> [ { name: 'Lydia' } ]
這題就屬于會者不難了,理解了 JS 中的物件指標參考就行
進階可以看下 糾結的連等賦值
標簽函式
源鏈接
以下代碼輸出結果是啥?
function getPersonInfo(one, two, three) {
console.log(one);
console.log(two);
console.log(three);
}
const person = 'Lydia';
const age = 21;
getPersonInfo`${person} is ${age} years old`;
答案
-> ["", " is ", " years old"] Lydia 21
這個叫做標簽函式,函式呼叫后跟一個模版字串,函式中第一個引數是陣列,回傳模版字串中根據 ${xxx} 分割的字符元素,其余引數就是 ${xxx} 的值
暫時沒想到有什么用,據說 styled-components 內部就是使用的它實作,可以參考下 這篇文章
String.raw
源鏈接
以下代碼輸出結果是啥?
console.log(String.raw`Hello\nworld`);
答案
-> Hello\nworld
顧名思義,會展示 raw string
Number.isNaN & isNaN
源鏈接
以下代碼輸出結果是啥?
const name = 'Lydia Hallie';
const age = 21;
console.log(Number.isNaN(name));
console.log(Number.isNaN(age));
console.log(isNaN(name));
console.log(isNaN(age));
答案
可以參考 這里
簡單說,Number.isNaN 只有傳入 NaN 的時候,才回傳 true,而 isNaN 會強制先將引數轉為 Number 型別(呼叫 Number(xxx))
比如 isNaN('Lydia Hallie'),其實就是 isNaN(Number('Lydia Hallie')),而 Number('Lydia Hallie') 回傳 NaN
ES6 Module
源鏈接
以下代碼輸出結果是啥?
// index.js
console.log('running index.js');
import { sum } from './sum.js';
console.log(sum(1, 2));
// sum.js
console.log('running sum.js');
export const sum = (a, b) => a + b;
答案
-> running sum.js running index.js 3
這題主要考查 ES6 模塊是編譯時輸出介面,而依賴的代碼會被先執行(CommonJS 模塊是運行時加載,所以可以在任何地方 require)
源鏈接
以下代碼輸出結果是啥?
// counter.js
let counter = 10;
export default counter;
// index.js
import myCounter from './counter';
myCounter += 1;
console.log(myCounter);
答案
-> Error
ES6 匯出的內容是只讀的,不能被修改,只能在所匯出模塊中修改
關于 js modules 更多可參考 這里
宏任務、微任務
源鏈接
以下代碼輸出結果是啥?
const myPromise = Promise.resolve(Promise.resolve('Promise'));
function funcOne() {
setTimeout(() => console.log('Timeout 1!'), 0);
myPromise.then(res => res).then(res => console.log(`${res} 1!`));
console.log('Last line 1!');
}
async function funcTwo() {
const res = await myPromise;
console.log(`${res} 2!`)
setTimeout(() => console.log('Timeout 2!'), 0);
console.log('Last line 2!');
}
funcOne();
funcTwo();
答案
簡單點說,await 后的代碼其實可以理解成 Promise.then 后的代碼
先理解下以下代碼輸出:
const myPromise = Promise.resolve(Promise.resolve('Promise'));
function funcOne() {
myPromise.then(res => console.log(`${res} 1!`));
}
async function funcTwo() {
const res = await myPromise;
console.log(`${res} 2!`)
}
funcOne();
funcTwo();
console.log(`${res} 1!`) 和 console.log(`${res} 2!`) 可以理解為微任務佇列的內容,依次執行
稍作修改:
const myPromise = Promise.resolve(Promise.resolve('Promise'));
function funcOne() {
myPromise.then(res => res).then(res => console.log(`${res} 1!`));
}
async function funcTwo() {
const res = await myPromise;
console.log(`${res} 2!`)
}
funcOne();
funcTwo();
這時微任務佇列依次是 res => res 和 console.log(`${res} 2!`),然后前者執行后,將 console.log(`${res} 1!`) 加入微任務佇列中,所以其實是 console.log(`${res} 2!`) 先執行
更多可參考 我以前的總結
箭頭函式
先明確下箭頭函式和普通函式的幾個區別:(參考 箭頭函式)
其中最重要的特性是,箭頭函式沒有自己的 this 物件,它的 this 是定義時上層作用域中的 this
源鏈接
以下代碼輸出結果是啥?
const shape = {
radius: 10,
diameter() {
return this.radius * 2;
},
perimeter: () => 2 * Math.PI * this.radius,
};
console.log(shape.diameter());
console.log(shape.perimeter());
答案
根據 它的 this 是定義時上層作用域中的 this,原題可以改寫成這樣:
var that = this
const shape = {
radius: 10,
diameter() {
return this.radius * 2;
},
perimeter: () => 2 * Math.PI * that.radius,
};
console.log(shape.diameter());
console.log(shape.perimeter());
這時候答案就比較清晰了
我們可以更進一步出題:
const shape = {
radius: 10,
diameter() {
return this.radius * 2;
},
perimeter: () => {
return () => 2 * Math.PI * this.radius
}
};
console.log(shape.diameter());
console.log(shape.perimeter()());
這個時候答案其實沒區別,畢竟第二個箭頭函式 this 來自上層作用域,而上層作用域來自頂層作用域,層層傳遞
但是如果改寫成這樣:
const shape = {
radius: 10,
diameter() {
return this.radius * 2;
},
perimeter: function () {
return () => 2 * Math.PI * this.radius
}
};
console.log(shape.diameter());
console.log(shape.perimeter()());
這就有意思了,因為上層并不是箭頭函式,它的 this 指向 shape,所以箭頭函式的 this 也指向 shape
這道題的考點可以是這個改寫,需要對箭頭函式中的 this 有著深入了解
92 這題的考點是箭頭函式不能作為建構式,所以它并沒有 prototype 屬性
98 這題比較腦經急轉彎,箭頭函式如果回傳一個物件,需要用 () 包下
151 還是 this 相關,和第一題類似
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/545068.html
標籤:JavaScript
上一篇:Quill編輯器實作原理初探
