【壹題1-10】

第 1 題:(滴滴、餓了麼)寫 React / Vue 項目時爲何要在列表組件中寫 key,其做用是什麼?

在react和vue中,數據因用戶行爲發生改變時,會生成新的虛擬DOM,而後會比較新舊的虛擬DOM,經過diff算法找到區別,key的做用就是經過該惟一的標識,快速定位鎖定變化,避免沒必要要的遍歷查找,浪費性能。javascript

可是在vue中對於key的效率又有了分支,由於有時候在沒有的key的狀況下,diff算法是速度甚至會更快。在沒有綁定key值的狀況下,並且在遍歷一些簡單的模板的狀況下,因爲會發生節點的複用(就地複用),diff算法速度更快,在vue中被稱爲默認模式。而key存在時,就不存在節點複用了,而是增刪節點,相對於簡單的模板而言是比較費時的。html

可是這種沒有key的默認模式存在反作用,例如:在經過表單輸入值時,會發生狀態錯位(示例連接:https://www.jianshu.com/p/4bd5e745ce95vue

總結:java

key是給每個vnode的惟一id,能夠依靠key,更準確,更快的拿到oldVnode中對應的vnode節點。node

1. 更準確react

由於帶key就不是就地複用了,在sameNode函數 a.key === b.key對比中能夠避免就地複用的狀況。因此會更加準確。算法

2. 更快api

利用key的惟一性生成map對象來獲取對應節點,比遍歷方式更快。(這個觀點,就是我最初的那個觀點。從這個角度看,map會比遍歷更快。)數組

 

第 2 題:`['1', '2', '3'].map(parseInt)` what & why ?

其實際執行的代碼是:promise

['1', '2', '3'].map((item, index) => { return parseInt(item, index) })

返回的值是:

parseInt('1', 0) // 1 parseInt('2', 1) // NaN parseInt('3', 2) // NaN, 3 不是二進制

緣由:

parseInt() 函數解析一個字符串參數,並返回一個指定基數的整數 (數學系統的基礎)。

const intValue = parseInt(string[, radix]);

string: 要被解析的值。若是參數不是一個字符串,則將其轉換爲字符串(使用 ToString 抽象操做)。字符串開頭的空白符將會被忽略。

radix: 一個介於2和36之間的整數(數學系統的基礎),表示上述字符串的基數。默認爲10。

返回值:返回一個整數或NaN

第 3 題:(挖財)什麼是防抖和節流?有什麼區別?如何實現?

防抖:觸發高頻事件後 n 秒內函數只會執行一次,若是 n 秒內高頻事件再次被觸發,則從新計算時間;

思路:每次觸發事件時都取消以前的延時調用方法

function debounce(fn) {
let timeout = null; // 建立一個標記用來存放定時器的返回值
return function () {
clearTimeout(timeout); // 每當用戶輸入的時候把前一個 setTimeout clear 掉
timeout = setTimeout(() => { // 而後又建立一個新的 setTimeout, 這樣就能保證輸入字符後的 interval 間隔內若是還有字符輸入的話,就不會執行 fn 函數
fn.apply(this, arguments);
}, 1000);
};
}
function sayHi() {
console.log('防抖成功');
}
var inp = document.getElementById('button');
button.addEventListener('click', debounce(sayHi)); // 防抖

 

節流:高頻事件觸發,但在 n 秒內只會執行一次,因此節流會稀釋函數的執行頻率。

思路:每次觸發事件時都判斷當前是否有等待執行的延時函數。

function throttle(fn) {
let canRun = true; // 經過閉包保存一個標記
return function () {
if (!canRun) return; // 在函數開頭判斷標記是否爲 true,不爲 true 則 return
canRun = false; // 當即設置爲 false
setTimeout(() => { // 將外部傳入的函數的執行放在 setTimeout 中
fn.apply(this, arguments);
// 最後在 setTimeout 執行完畢後再把標記設置爲 true(關鍵) 表示能夠執行下一次循環了。當定時器沒有執行的時候標記永遠是 false,在開頭被 return 掉
canRun = true;
}, 1000);
};
}
function sayHi() {
console.log('節流成功');
}
button.addEventListener('click', throttle(sayHi));

 

第 4 題:介紹下 Set、Map、WeakSet 和 WeakMap 的區別?

四者都是ES6提供的新的數據結構類型。

Set:相似於數組,它的特色是成員惟一沒有重複值。set自己是一個構造函數,用來生成數據結構。它能夠接受一個數組做爲參數(或者具備 iterable 接口的其餘數據結構),用來初始化。

const s=new Set([1,2,3,4,5,5,5]);
console.log(s)//Set(5) {1, 2, 3, 4, 5}
const s1=new Set("hello");
console.log(s1);//Set(4) {"h", "e", "l", "o"}

WeakSet:和set相似,也是不重複的值的集合。可是,它與 Set 有兩個區別。首先,WeakSet 的成員只能是對象,而不能是其餘類型的值。其次,WeakSet 中的對象都是弱引用,即垃圾回收機制不考慮 WeakSet 對該對象的引用,也就是說,若是其餘對象都再也不引用該對象,那麼垃圾回收機

制會自動回收該對象所佔用的內存,不考慮該對象還存在於 WeakSet 之中。

Map:JavaScript 的對象(Object),本質上是鍵值對的集合(Hash 結構),可是傳統上只能用字符串看成鍵。這給它的使用帶來了很大的限制。

爲了解決這個問題,ES6 提供了 Map 數據結構。它相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。也就是說,Object 結構提供了「字符串—值」的對應,Map 結構提供了「值—值」的對應,是一種更完善的 Hash 結構實現。若是你須要「鍵值

對」的數據結構,Map 比 Object 更合適。

const m=new Map([["name","lily"],["age","18"]]);
console.log(m)//Map(2) {"name" => "lily", "age" => "18"}

let a=new Map([[{d:'3',f:'6'},{e:'5'}]]);
a.set({a:"1",b:"2"},{c:"4"});
console.log(a)
//0: {Object => Object} key: {d: "3", f: "6"} value: {e: "5"}
//1: {Object => Object} key: {a: "1", b: "2"} value: {c: "4"}

WeakMap:它和map的區別相似於set和WeakSet的區別。WeakMap的鍵名只接受對象,不接受其餘類型。鍵名是弱引用,鍵值能夠是任意的,鍵名所指向的對象能夠被垃圾回收,此時鍵名是無效的;

第 5 題:介紹下深度優先遍歷和廣度優先遍歷,如何實現?

答案徹底引用自:http://www.javashuo.com/article/p-pxgslbgq-cr.html

深度優先遍歷:

假設給定圖G的初態是全部頂點均不曾訪問過。在G中任選一頂點v爲初始出發點(源點),則深度優先遍歷可定義以下:首先訪問出發點v,並將其標記爲已訪問過;而後依次從v出發搜索v的每一個鄰接點w。若w不曾訪問過,則以w爲新的出發點繼續進行深度優先遍歷,直至圖中全部和源點v有路徑相通的頂點(亦稱爲從源點可達的頂點)均已被訪問爲止。若此時圖中仍有未訪問的頂點,則另選一個還沒有訪問的頂點做爲新的源點重複上述過程,直至圖中全部頂點均已被訪問爲止。

圖的深度優先遍歷相似於樹的前序遍歷。採用的搜索方法的特色是儘量先對縱深方向進行搜索。這種搜索方法稱爲深度優先搜索(Depth-First Search)。相應地,用此方法遍歷圖就很天然地稱之爲圖的深度優先遍歷。

說白了深度優先遍歷就是一種不撞南牆不會頭的算法,他會把一條路走完以後再回溯到有分叉的節點繼續遍歷。

如圖:

  1. 首先標記點0,而後按字典序尋找未標記的相鄰點進行遍歷(點1)。
  2. 標記點1,按字典序尋找未標記的相鄰點繼續遍歷(點4)。
  3. 同步驟2,直到遍歷到點3,由於與他相鄰的點(點0,點6)都被標記過,因此回溯到點6,繼續尋找點6的未標記的相鄰點繼續遍歷(點7)。
  4. 標記點7,同步驟3,回溯點6。
  5. 這時點6的全部相鄰點都被標記,回溯點4。
  6. 同步驟5,繼續回溯到點1。
  7. 按字典序尋找點1的未標記的相鄰點繼續遍歷(點2)。
  8. 同步驟2,遍歷點5。
  9. 同步驟5,回溯到點0,此時整個圖的點都被遍歷過,結束。

 

廣度優先遍歷:

  1. 從圖中某個頂點V0出發,並訪問此頂點;
  2. 從V0出發,訪問V0的各個不曾訪問的鄰接點W1,W2,…,Wk;而後,依次從W1,W2,…,Wk出發訪問各自未被訪問的鄰接點;
  3. 重複步驟2,直到所有頂點都被訪問爲止。

這是一種層層遞進的算法,與樹的層序遍歷相似。

在廣度優先搜索時,會從起點開始「一層一層」擴展的方法來遍歷,擴展時每發現一個點就將這個點加入到隊列,直到整張圖都被遍歷過位置。

第 6 題:請分別用深度優先思想和廣度優先思想實現一個拷貝函數?

(暫無答案)

第 7 題:ES5/ES6 的繼承除了寫法之外還有什麼區別?

ES5:經過把被繼承構造函數的實例賦值給要繼承的構造函數的原型對象,是經過原型或構造函數機制來實現的,其實質上是先建立子類的實例對象,而後再將父類的方法添加到this上。

ES6:經過class類來替代了構造函數的寫法,其包含有構造方法,類和類之間經過extends來實現繼承。()ES6繼承機制不一樣於ES5,它是經過先建立父類的實例對象this,而後再在子類中修改this,因此,子類必須在constructor方法中調用super方法,由於子類沒有本身的this對象,而是繼承了父類的this對象。

總結:

ES5和ES6繼承最大的區別就是在於:

1.ES5先建立子類,在實例化父類並添加到子類this中

2.ES6先建立父類,在實例化子集中經過調用super方法訪問父級後,在經過修改this實現繼承

 

第 8 題:setTimeout、Promise、Async/Await 的區別

js主線程會不斷的循環往復的從任務列表中讀取任務,執行任務,這種運行機制稱爲事件循環(event loop)。

執行異步任務有兩種類型,分別是microtasks和macrotasks。microtasks的優先級要高於macrotasks。

每個event loop都有一個microtasks queue;每個event loop都有一個或多個macrotasks queue(也稱task queue)。

每一次event loop,會先執行microtasks queue,執行完畢以後,會提取macrostaks queue中的一個任務加入到microtasks queue中,繼續執行microtasks queue,依次執行下去直到結束。

microtasks 包含如下api:process.nextTick、promise、Object.observe (廢棄)、MutationObserver。

macrostaks包含如下api:setTimeout、setImmerdiate、setInterval、I/O、UI 渲染。

因而可知在執行順序上promise的優先級要高於setTimeout。promise的性能要優於setTimeout,後者會發生兩次UI重渲染。

promise自己是同步的當即執行函數,當執行到resolve和reject的時候,此時是異步的操做,會放入到任務隊列中。(以下)

console.log('script start')
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () {
console.log('promise2')
})
setTimeout(function(){
console.log('settimeout')
})
console.log('script end')
// 輸出順序: script start->promise1->promise1 end->script end->promise2->settimeout

async/await是創建在promise之上的,其返回的也是一個promise對象,當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的異步操做完成,再執行函數體內後面的語句。能夠理解爲,是讓出了線程,跳出了 async 函數體。(以下:)

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

console.log('script start');
async1();
console.log('script end')

// 輸出順序:script start->async1 start->async2->script end->async1 end

async/await相對於promise的優勢以下:

1. 使用async/await可使代碼簡潔優雅,不須要書寫.then(),不須要新建一個匿名函數處理響應,也不須要再把數據賦值給一個咱們其實並不須要的變量。

2.在錯誤處理上,錯誤會出如今promise內部,由此須要多嵌套一個try/catch。

3.業務若是須要先獲取一個數據,根據該數據來決定是否獲取更多的數據。若是使用promise,只能經過嵌套來完成,若是這種業務迭代很深,那麼嵌套也會很深(聞到了回調地獄的味道)。

const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
}
else {
console.log(data)
return data
}
})
}

若是用async/await就簡單不少,只須要在獲取條件數據的方法前面使用await便可。

const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData)
return moreData
}
else {
console.log(data)
return data
}
}

 

第 9 題:(頭條、微醫)Async/Await 如何經過同步的方式實現異步

(該題歡迎指正)

async本質也是返回了一個promise對象,而promise對象自己是一個同步當即執行的函數,當執行到.then()時,纔會放入到異步隊列中,而async當執行到await時,會執行其後面的方法,而後跳出當前async函數體,等待下次被提取。

第 10 題:(頭條)異步筆試題

請寫出下面代碼的運行結果

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');

 

結果:

// script start

// async1 start

// async2

// promise1

// script end

// async1 end

//promise2

//setTimeout

相關文章
相關標籤/搜索