第 1 題:(滴滴芯急、餓了么)寫 React / Vue 項(xiàng)目時(shí)為什么要在列表組件中寫 key感帅,其作用是什么强饮?
1. 更準(zhǔn)確
因?yàn)閹ey就不是就地復(fù)用了圆雁,在sameNode函數(shù) a.key === b.key對(duì)比中可以避免就地復(fù)用
的情況忍级。所以會(huì)更加準(zhǔn)確。
2. 更快
利用key的唯一性生成map對(duì)象來(lái)獲取對(duì)應(yīng)節(jié)點(diǎn)伪朽,比遍歷方式更快轴咱。主要是為了提升diff【同級(jí)比較】的效率。自己想一下自己要實(shí)現(xiàn)前后列表的diff,如果對(duì)列表的每一項(xiàng)增加一個(gè)key朴肺,即唯一索引,那就可以很清楚的知道兩個(gè)列表誰(shuí)少了誰(shuí)沒(méi)變。而如果不加key的話嫉嘀,就只能一個(gè)個(gè)對(duì)比了泉沾。
vue和react都是采用diff算法來(lái)對(duì)比新舊虛擬節(jié)點(diǎn),從而更新節(jié)點(diǎn)鞍盗。在vue的diff函數(shù)中(建議先了解一下diff算法過(guò)程)需了。 在交叉對(duì)比中,當(dāng)新節(jié)點(diǎn)跟舊節(jié)點(diǎn)頭尾交叉對(duì)比沒(méi)有結(jié)果時(shí)般甲,會(huì)根據(jù)新節(jié)點(diǎn)的key去對(duì)比舊節(jié)點(diǎn)數(shù)組中的key肋乍,從而找到相應(yīng)舊節(jié)點(diǎn)(這里對(duì)應(yīng)的是一個(gè)key => index 的map映射)。如果沒(méi)找到就認(rèn)為是一個(gè)新增節(jié)點(diǎn)敷存。而如果沒(méi)有key墓造,那么就會(huì)采用遍歷查找的方式去找到對(duì)應(yīng)的舊節(jié)點(diǎn)。一種一個(gè)map映射历帚,另一種是遍歷查找滔岳。相比而言。map映射的速度更快挽牢。 vue部分源碼如下:
解析:第 1 題
第 2 題:['1', '2', '3'].map(parseInt)
what & why ?
首先讓我們回顧一下谱煤,map函數(shù)的第一個(gè)參數(shù)callback:
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
這個(gè)callback一共可以接收三個(gè)參數(shù),其中第一個(gè)參數(shù)代表當(dāng)前被處理的元素禽拔,而第二個(gè)參數(shù)代表該元素的索引刘离。
而parseInt則是用來(lái)解析字符串的,使字符串成為指定基數(shù)的整數(shù)睹栖。
parseInt(string, radix)
接收兩個(gè)參數(shù)硫惕,第一個(gè)表示被處理的值(字符串),第二個(gè)表示為解析時(shí)的基數(shù)野来。
了解這兩個(gè)函數(shù)后恼除,我們可以模擬一下運(yùn)行情況
parseInt('1', 0) //radix為0時(shí),且string參數(shù)不以“0x”和“0”開(kāi)頭時(shí)曼氛,按照10為基數(shù)處理豁辉。這個(gè)時(shí)候返回1
parseInt('2', 1) //基數(shù)為1(1進(jìn)制)表示的數(shù)中,最大值小于2舀患,所以無(wú)法解析徽级,返回NaN
parseInt('3', 2) //基數(shù)為2(2進(jìn)制)表示的數(shù)中,最大值小于3聊浅,所以無(wú)法解析餐抢,返回NaN
map函數(shù)返回的是一個(gè)數(shù)組现使,所以最后結(jié)果為[1, NaN, NaN]
解析:第 2 題
第 3 題:(挖財(cái))什么是防抖和節(jié)流?有什么區(qū)別旷痕?如何實(shí)現(xiàn)碳锈?
JavaScript專題之跟著underscore學(xué)防抖
JavaScript專題之跟著 underscore 學(xué)節(jié)流
防抖
觸發(fā)高頻事件后n秒內(nèi)函數(shù)只會(huì)執(zhí)行一次,如果n秒內(nèi)高頻事件再次被觸發(fā)苦蒿,則重新計(jì)算時(shí)間
思路
每次觸發(fā)事件時(shí)都取消之前的延時(shí)調(diào)用方法
/* 防抖 */
function dou(fn, wait) {
var time = null;
return function () {
clearTimeout(time)
// time = setTimeout(function () {
// console.log(this)//window
// fn.apply(this, arguments)//這樣的話 this為window和直接 fn()調(diào)用是一樣的效果殴胧,因?yàn)樗麄兊膖his都是window
// }, wait);
time = setTimeout(() => {
// console.log(this)//div
fn.apply(this, arguments)//確保dou函數(shù)的this(上下文還是div)
}, wait);
}
}
function demo() {
console.log('防抖啦')
}
// 用句柄事件綁定調(diào)用dou事件,所以this為div節(jié)點(diǎn)對(duì)象
document.querySelector('div').addEventListener('scroll', dou(demo, 1000))
節(jié)流
高頻事件觸發(fā)佩迟,但在n秒內(nèi)只會(huì)執(zhí)行一次团滥,所以節(jié)流會(huì)稀釋函數(shù)的執(zhí)行頻率
思路
每次觸發(fā)事件時(shí)都判斷當(dāng)前是否有等待執(zhí)行的延時(shí)函數(shù)
/* 節(jié)流 */
function throttle(func, wait) {
var previous = 0;
return function () {
var now = +new Date();
if (now - previous > wait) {
func.apply(this, arguments);
previous = now;
}
}
}
function getUserAction() {
console.log(`每秒1秒內(nèi)打印一次`)
}
document.querySelector('div').addEventListener('click', throttle(getUserAction, 1000))
解析:第 3 題
第 4 題:介紹下 Set、Map报强、WeakSet 和 WeakMap 的區(qū)別灸姊?
Set
成員唯一、無(wú)序且不重復(fù)
[value, value]秉溉,鍵值與鍵名是一致的(或者說(shuō)只有鍵值力惯,沒(méi)有鍵名)
可以遍歷,方法有:add召嘶、delete父晶、has
WeakSet
成員都是對(duì)象
成員都是弱引用,可以被垃圾回收機(jī)制回收弄跌,可以用來(lái)保存DOM節(jié)點(diǎn)甲喝,不容易造成內(nèi)存泄漏
不能遍歷,方法有add铛只、delete埠胖、has
Map
本質(zhì)上是鍵值對(duì)的集合,類似集合
可以遍歷淳玩,方法很多可以跟各種數(shù)據(jù)格式轉(zhuǎn)換
WeakMap
只接受對(duì)象作為鍵名(null除外)直撤,不接受其他類型的值作為鍵名
鍵名是弱引用,鍵值可以是任意的蜕着,鍵名所指向的對(duì)象可以被垃圾回收谋竖,此時(shí)鍵名是無(wú)效的
不能遍歷,方法有g(shù)et承匣、set蓖乘、has、delete
解析:第 4 題
第 5 題:介紹下深度優(yōu)先遍歷和廣度優(yōu)先遍歷悄雅,如何實(shí)現(xiàn)驱敲?
解析:第 5 題
第 6 題:請(qǐng)分別用深度優(yōu)先思想和廣度優(yōu)先思想實(shí)現(xiàn)一個(gè)拷貝函數(shù)铁蹈?
解析:第 6 題
第 7 題:ES5/ES6 的繼承除了寫法以外還有什么區(qū)別宽闲?
先看ES5的繼承(原型鏈繼承)
function a() {
this.name = 'a';
}
a.prototype.getName = function getName() {
return this.name
}
function b() {}
b.prototype = new a();
console.log(b.prototype.__proto__ === a.prototype); // true
console.log(b.__proto__ === a); // false
console.log(b.__proto__); // [Function]
ES6繼承
class A {
constructor(a) {
this.name = a;
}
getName() {
return this.name;
}
}
class B extends A{
constructor() {
super();
}
}
console.log(B.prototype.__proto__ === A.prototype); // true
console.log(B.__proto__ === A); // true
console.log(B.__proto__); // [Function: A]
對(duì)比代碼可以知道众眨,子類的繼承都是成功的,但是問(wèn)題出在容诬,子類的 __proto__
指向不一樣娩梨。
ES5
的子類和父類一樣,都是先創(chuàng)建好览徒,再實(shí)現(xiàn)繼承的狈定,所以它們的指向都是 [Function]
。
ES6
則得到不一樣的結(jié)果习蓬,它指向父類纽什,那么我們應(yīng)該能推算出來(lái),它的子類是通過(guò) super
來(lái)改造的躲叼。
根據(jù) es6--阮一峰 在class繼承里面的說(shuō)法芦缰,是這樣子的:
引用阮一峰的 ECMAScript6入門
的class繼承篇
子類必須在
constructor
方法中調(diào)用super
方法,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)枫慷。這是因?yàn)樽宇愖约旱?code>this對(duì)象让蕾,必須先通過(guò)父類的構(gòu)造函數(shù)完成塑造,得到與父類同樣的實(shí)例屬性和方法或听,然后再對(duì)其進(jìn)行加工探孝,加上子類自己的實(shí)例屬性和方法。如果不調(diào)用super
方法誉裆,子類就得不到this
對(duì)象顿颅。ES5 的繼承,實(shí)質(zhì)是先創(chuàng)造子類的實(shí)例對(duì)象
this
找御,然后再將父類的方法添加到this
上面(Parent.apply(this)
)元镀。ES6 的繼承機(jī)制完全不同,實(shí)質(zhì)是先將父類實(shí)例對(duì)象的屬性和方法霎桅,加到this
上面(所以必須先調(diào)用super
方法)栖疑,然后再用子類的構(gòu)造函數(shù)修改this
。
1滔驶、class 聲明會(huì)提升遇革,但不會(huì)初始化賦值。Foo 進(jìn)入暫時(shí)性死區(qū)揭糕,類似于 let萝快、const 聲明變量。
2著角、class 聲明內(nèi)部會(huì)啟用嚴(yán)格模式揪漩。
3、class 的所有方法(包括靜態(tài)方法和實(shí)例方法)都是不可枚舉的吏口。
4奄容、class 的所有方法(包括靜態(tài)方法和實(shí)例方法)都沒(méi)有原型對(duì)象 prototype冰更,所以也沒(méi)有[[construct]],不能使用 new 來(lái)調(diào)用昂勒。
5蜀细、必須使用 new 調(diào)用 class。
6戈盈、class 內(nèi)部無(wú)法重寫類名
解析:第 7 題
第 8 題:setTimeout奠衔、Promise、Async/Await 的區(qū)別
其中settimeout的回調(diào)函數(shù)放到宏任務(wù)隊(duì)列里塘娶,等到執(zhí)行棧清空以后執(zhí)行归斤; promise.then里的回調(diào)函數(shù)會(huì)放到相應(yīng)宏任務(wù)的微任務(wù)隊(duì)列里,等宏任務(wù)里面的同步代碼執(zhí)行完再執(zhí)行刁岸;async函數(shù)表示函數(shù)里面可能會(huì)有異步方法官册,await后面跟一個(gè)表達(dá)式,async方法執(zhí)行時(shí)难捌,遇到await會(huì)立即執(zhí)行表達(dá)式膝宁,然后把表達(dá)式后面的代碼放到微任務(wù)隊(duì)列里,讓出執(zhí)行棧讓同步代碼先執(zhí)行根吁。
解析:第 8 題
第 9 題:(頭條员淫、微醫(yī))Async/Await 如何通過(guò)同步的方式實(shí)現(xiàn)異步
async await 用于把異步請(qǐng)求變?yōu)橥秸?qǐng)求的方式,第一個(gè)請(qǐng)求的返回值作為后面一個(gè)請(qǐng)求的參數(shù),其中每一個(gè)參數(shù)都是一個(gè)promise對(duì)象
例如:這種情況工作中會(huì)經(jīng)常遇到
(async () => {
var a = await A();
var b = await B(a);
var c = await C(b);
var d = await D(c);
})();
解析:第 9 題
第 10 題:(頭條)異步筆試題
請(qǐng)寫出下面代碼的運(yùn)行結(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(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
復(fù)制代碼
解析:第 10 題
第 11 - 20 題
第 11 題:(攜程)算法手寫題
已知如下數(shù)組:
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
編寫一個(gè)程序?qū)?shù)組扁平化去并除其中重復(fù)部分?jǐn)?shù)據(jù),最終得到一個(gè)升序且不重復(fù)的數(shù)組
var arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];
// 方法一
console.log(Array.from(new Set(arr.flat(Infinity))).sort((a, b) => a - b))
// 方法二
console.log(Array.from(new Set(arr.toString().split(','))).map(Number).sort((a, b) => a - b))
// 方法三
// 第一步:扁平化
let newArr = [];
function flat(originArr) {
if ({}.toString.call(originArr) === '[object Array]') {
for (let i of originArr) {
if ({}.toString.call(i) === '[object Array]') {
arguments.callee(i)
} else {
newArr.push(i)
}
}
}
return newArr;
}
console.log(flat(arr))
// 第二步:去重
var newArr1 = [];
for (let i of newArr) {
if (!newArr1.includes(i)) newArr1.push(i);
}
// 第三步:排序 可以采用相關(guān)算法處理
console.log(newArr1.sort((a, b) => a - b))
解析:第 11 題
第 12 題:(滴滴击敌、挖財(cái)介返、微醫(yī)、何纸铮康)JS 異步解決方案的發(fā)展歷程以及優(yōu)缺點(diǎn)圣蝎。
1. 回調(diào)函數(shù)(callback)
setTimeout(() => {
// callback 函數(shù)體
}, 1000)
缺點(diǎn):回調(diào)地獄,不能用 try catch 捕獲錯(cuò)誤衡瓶,不能 return`
回調(diào)地獄的根本問(wèn)題在于
缺乏順序性: 回調(diào)地獄導(dǎo)致的調(diào)試?yán)щy徘公,和大腦的思維方式不符
嵌套函數(shù)存在耦合性,一旦有所改動(dòng)哮针,就會(huì)牽一發(fā)而動(dòng)全身关面,即(控制反轉(zhuǎn),嵌套函數(shù)過(guò)多的多話,很難處理錯(cuò)誤
ajax('XXX1', () => {
// callback 函數(shù)體
ajax('XXX2', () => {
// callback 函數(shù)體
ajax('XXX3', () => {
// callback 函數(shù)體
})
})
})
優(yōu)點(diǎn):解決了同步的問(wèn)題(只要有一個(gè)任務(wù)耗時(shí)很長(zhǎng)十厢,后面的任務(wù)都必須排隊(duì)等著等太,會(huì)拖延整個(gè)程序的執(zhí)行。)
2. Promise
Promise就是為了解決callback的問(wèn)題而產(chǎn)生的蛮放。
Promise 實(shí)現(xiàn)了鏈?zhǔn)秸{(diào)用缩抡,也就是說(shuō)每次 then 后返回的都是一個(gè)全新 Promise,如果我們?cè)?then 中 return 包颁,return 的結(jié)果會(huì)被Promise.resolve() 包裝
優(yōu)點(diǎn):解決了回調(diào)地獄的問(wèn)題
ajax('XXX1')
.then(res => {
// 操作邏輯
return ajax('XXX2')
}).then(res => {
// 操作邏輯
return ajax('XXX3')
}).then(res => {
// 操作邏輯
})
缺點(diǎn):無(wú)法取消 Promise 瞻想,錯(cuò)誤需要通過(guò)回調(diào)函數(shù)來(lái)捕獲
3. Generator
特點(diǎn):可以控制函數(shù)的執(zhí)行挎塌,可以配合 co 函數(shù)庫(kù)使用
function* fetch() {
yield ajax('XXX1', () => { })
yield ajax('XXX2', () => { })
yield ajax('XXX3', () => { })
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
4. Async / await
async、await 是異步的終極解決方案
優(yōu)點(diǎn)是:代碼清晰内边,不用像 Promise 寫一大堆 then 鏈,處理了回調(diào)地獄的問(wèn)題
缺點(diǎn):await 將異步代碼改造成同步代碼待锈,如果多個(gè)異步操作沒(méi)有依賴性而使用 await 會(huì)導(dǎo)致性能上的降低漠其。
async function test() {
// 以下代碼沒(méi)有依賴性的話,完全可以使用 Promise.all 的方式
// 如果有依賴性的話竿音,其實(shí)就是解決回調(diào)地獄的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
解析:第 12 題
第 13 題:(微醫(yī))Promise 構(gòu)造函數(shù)是同步執(zhí)行還是異步執(zhí)行和屎,那么 then 方法呢?
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
執(zhí)行結(jié)果是:1243
promise構(gòu)造函數(shù)是同步執(zhí)行的春瞬,then方法是異步執(zhí)行的
解析:第 13 題
第 14 題:(兌吧)情人節(jié)福利題柴信,如何實(shí)現(xiàn)一個(gè) new
先理清楚 new 關(guān)鍵字調(diào)用函數(shù)都的具體過(guò)程,那么寫出來(lái)就很清楚了
首先創(chuàng)建一個(gè)空的對(duì)象宽气,空對(duì)象的 ___proto____屬性指向構(gòu)造函數(shù)的原型對(duì)象
把上面創(chuàng)建的空對(duì)象賦值構(gòu)造函數(shù)內(nèi)部的this随常,用構(gòu)造函數(shù)內(nèi)部的方法修改空對(duì)象
如果構(gòu)造函數(shù)返回一個(gè)非基本類型的值,則返回這個(gè)值萄涯,否則上面創(chuàng)建的對(duì)象
function _new(fn, ...arg) {
var obj = Object.create(fn.prototype);
const result = fn.apply(obj, ...arg);
return Object.prototype.toString.call(result) == '[object Object]' ? result : obj;
}
解析:第 14 題
第 15 題:(網(wǎng)易)簡(jiǎn)單講解一下http2的多路復(fù)用
解析:第 15 題
第 16 題:談?wù)勀銓?duì)TCP三次握手和四次揮手的理解
解析:第 16 題
第 17 題:A绪氛、B 機(jī)器正常連接后,B 機(jī)器突然重啟涝影,問(wèn) A 此時(shí)處于 TCP 什么狀態(tài)
如果A 與 B 建立了正常連接后枣察,從未相互發(fā)過(guò)數(shù)據(jù),這個(gè)時(shí)候 B 突然機(jī)器重啟燃逻,問(wèn) A 此時(shí)處于 TCP 什么狀態(tài)序目?如何消除服務(wù)器程序中的這個(gè)狀態(tài)?(超綱題伯襟,了解即可)
解析:第 17 題
第 18 題:(微醫(yī))React 中 setState 什么時(shí)候是同步的猿涨,什么時(shí)候是異步的?
解析:第 18 題
第 19 題:React setState 筆試題姆怪,下面的代碼輸出什么嘿辟?
class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null;
}
};
復(fù)制代碼
解析:第 19 題
第 20 題:介紹下 npm 模塊安裝機(jī)制,為什么輸入 npm install 就可以自動(dòng)安裝對(duì)應(yīng)的模塊片效?
解析:第 20 題