亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

【面試篇】寒冬求職季之你必須要懂的原生JS(中)

Mike617 / 1929人閱讀

摘要:如果你還沒(méi)讀過(guò)上篇上篇和中篇并無(wú)依賴關(guān)系,您可以讀過(guò)本文之后再閱讀上篇,可戳面試篇寒冬求職季之你必須要懂的原生上小姐姐花了近百個(gè)小時(shí)才完成這篇文章,篇幅較長(zhǎng),希望大家閱讀時(shí)多花點(diǎn)耐心,力求真正的掌握相關(guān)知識(shí)點(diǎn)。

互聯(lián)網(wǎng)寒冬之際,各大公司都縮減了HC,甚至是采取了“裁員”措施,在這樣的大環(huán)境之下,想要獲得一份更好的工作,必然需要付出更多的努力。

一年前,也許你搞清楚閉包,this,原型鏈,就能獲得認(rèn)可。但是現(xiàn)在,很顯然是不行了。本文梳理出了一些面試中有一定難度的高頻原生JS問(wèn)題,部分知識(shí)點(diǎn)可能你之前從未關(guān)注過(guò),或者看到了,卻沒(méi)有仔細(xì)研究,但是它們卻非常重要。本文將以真實(shí)的面試題的形式來(lái)呈現(xiàn)知識(shí)點(diǎn),大家在閱讀時(shí),建議不要先看我的答案,而是自己先思考一番。盡管,本文所有的答案,都是我在翻閱各種資料,思考并驗(yàn)證之后,才給出的(絕非復(fù)制粘貼而來(lái))。但因水平有限,本人的答案未必是最優(yōu)的,如果您有更好的答案,歡迎給我留言。

本文篇幅較長(zhǎng),但是滿滿的都是干貨!并且還埋伏了可愛(ài)的表情包,希望小伙伴們能夠堅(jiān)持讀完。

寫(xiě)文超級(jí)真誠(chéng)的小姐姐祝愿大家都能找到心儀的工作。

如果你還沒(méi)讀過(guò)上篇【上篇和中篇并無(wú)依賴關(guān)系,您可以讀過(guò)本文之后再閱讀上篇】,可戳【面試篇】寒冬求職季之你必須要懂的原生JS(上)

小姐姐花了近百個(gè)小時(shí)才完成這篇文章,篇幅較長(zhǎng),希望大家閱讀時(shí)多花點(diǎn)耐心,力求真正的掌握相關(guān)知識(shí)點(diǎn)。

1.說(shuō)一說(shuō)JS異步發(fā)展史

異步最早的解決方案是回調(diào)函數(shù),如事件的回調(diào),setInterval/setTimeout中的回調(diào)。但是回調(diào)函數(shù)有一個(gè)很常見(jiàn)的問(wèn)題,就是回調(diào)地獄的問(wèn)題(稍后會(huì)舉例說(shuō)明);

為了解決回調(diào)地獄的問(wèn)題,社區(qū)提出了Promise解決方案,ES6將其寫(xiě)進(jìn)了語(yǔ)言標(biāo)準(zhǔn)。Promise解決了回調(diào)地獄的問(wèn)題,但是Promise也存在一些問(wèn)題,如錯(cuò)誤不能被try catch,而且使用Promise的鏈?zhǔn)秸{(diào)用,其實(shí)并沒(méi)有從根本上解決回調(diào)地獄的問(wèn)題,只是換了一種寫(xiě)法。

ES6中引入 Generator 函數(shù),Generator是一種異步編程解決方案,Generator 函數(shù)是協(xié)程在 ES6 的實(shí)現(xiàn),最大特點(diǎn)就是可以交出函數(shù)的執(zhí)行權(quán),Generator 函數(shù)可以看出是異步任務(wù)的容器,需要暫停的地方,都用yield語(yǔ)句注明。但是 Generator 使用起來(lái)較為復(fù)雜。

ES7又提出了新的異步解決方案:async/await,async是 Generator 函數(shù)的語(yǔ)法糖,async/await 使得異步代碼看起來(lái)像同步代碼,異步編程發(fā)展的目標(biāo)就是讓異步邏輯的代碼看起來(lái)像同步一樣。

1.回調(diào)函數(shù): callback
//node讀取文件
fs.readFile(xxx, "utf-8", function(err, data) {
    //code
});

回調(diào)函數(shù)的使用場(chǎng)景(包括但不限于):

事件回調(diào)

Node API

setTimeout/setInterval中的回調(diào)函數(shù)

異步回調(diào)嵌套會(huì)導(dǎo)致代碼難以維護(hù),并且不方便統(tǒng)一處理錯(cuò)誤,不能try catch 和 回調(diào)地獄(如先讀取A文本內(nèi)容,再根據(jù)A文本內(nèi)容讀取B再根據(jù)B的內(nèi)容讀取C...)。

fs.readFile(A, "utf-8", function(err, data) {
    fs.readFile(B, "utf-8", function(err, data) {
        fs.readFile(C, "utf-8", function(err, data) {
            fs.readFile(D, "utf-8", function(err, data) {
                //....
            });
        });
    });
});
2.Promise

Promise 主要解決了回調(diào)地獄的問(wèn)題,Promise 最早由社區(qū)提出和實(shí)現(xiàn),ES6 將其寫(xiě)進(jìn)了語(yǔ)言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了Promise對(duì)象。

那么我們看看Promise是如何解決回調(diào)地獄問(wèn)題的,仍然以上文的readFile為例。

function read(url) {
    return new Promise((resolve, reject) => {
        fs.readFile(url, "utf8", (err, data) => {
            if(err) reject(err);
            resolve(data);
        });
    });
}
read(A).then(data => {
    return read(B);
}).then(data => {
    return read(C);
}).then(data => {
    return read(D);
}).catch(reason => {
    console.log(reason);
});

想要運(yùn)行代碼看效果,請(qǐng)戳(小姐姐使用的是VS的 Code Runner 執(zhí)行代碼): https://github.com/YvetteLau/...

思考一下在Promise之前,你是如何處理異步并發(fā)問(wèn)題的,假設(shè)有這樣一個(gè)需求:讀取三個(gè)文件內(nèi)容,都讀取成功后,輸出最終的結(jié)果。有了Promise之后,又如何處理呢?代碼可戳: https://github.com/YvetteLau/...

注: 可以使用 bluebird 將接口 promise化;

引申: Promise有哪些優(yōu)點(diǎn)和問(wèn)題呢?

3.Generator

Generator 函數(shù)是 ES6 提供的一種異步編程解決方案,整個(gè) Generator 函數(shù)就是一個(gè)封裝的異步任務(wù),或者說(shuō)是異步任務(wù)的容器。異步操作需要暫停的地方,都用 yield 語(yǔ)句注明。

Generator 函數(shù)一般配合 yield 或 Promise 使用。Generator函數(shù)返回的是迭代器。對(duì)生成器和迭代器不了解的同學(xué),請(qǐng)自行補(bǔ)習(xí)下基礎(chǔ)。下面我們看一下 Generator 的簡(jiǎn)單使用:

function* gen() {
    let a = yield 111;
    console.log(a);
    let b = yield 222;
    console.log(b);
    let c = yield 333;
    console.log(c);
    let d = yield 444;
    console.log(d);
}
let t = gen();
//next方法可以帶一個(gè)參數(shù),該參數(shù)就會(huì)被當(dāng)作上一個(gè)yield表達(dá)式的返回值
t.next(1); //第一次調(diào)用next函數(shù)時(shí),傳遞的參數(shù)無(wú)效
t.next(2); //a輸出2;
t.next(3); //b輸出3; 
t.next(4); //c輸出4;
t.next(5); //d輸出5;

為了讓大家更好的理解上面代碼是如何執(zhí)行的,我畫(huà)了一張圖,分別對(duì)應(yīng)每一次的next方法調(diào)用:

仍然以上文的readFile為例,使用 Generator + co庫(kù)來(lái)實(shí)現(xiàn):

const fs = require("fs");
const co = require("co");
const bluebird = require("bluebird");
const readFile = bluebird.promisify(fs.readFile);

function* read() {
    yield readFile(A, "utf-8");
    yield readFile(B, "utf-8");
    yield readFile(C, "utf-8");
    //....
}
co(read()).then(data => {
    //code
}).catch(err => {
    //code
});

不使用co庫(kù),如何實(shí)現(xiàn)?能否自己寫(xiě)一個(gè)最簡(jiǎn)的my_co?請(qǐng)戳: https://github.com/YvetteLau/...

PS: 如果你還不太了解 Generator/yield,建議閱讀ES6相關(guān)文檔。

4.async/await

ES7中引入了 async/await 概念。async其實(shí)是一個(gè)語(yǔ)法糖,它的實(shí)現(xiàn)就是將Generator函數(shù)和自動(dòng)執(zhí)行器(co),包裝在一個(gè)函數(shù)中。

async/await 的優(yōu)點(diǎn)是代碼清晰,不用像 Promise 寫(xiě)很多 then 鏈,就可以處理回調(diào)地獄的問(wèn)題。錯(cuò)誤可以被try catch。

仍然以上文的readFile為例,使用 Generator + co庫(kù)來(lái)實(shí)現(xiàn):

const fs = require("fs");
const bluebird = require("bluebird");
const readFile = bluebird.promisify(fs.readFile);


async function read() {
    await readFile(A, "utf-8");
    await readFile(B, "utf-8");
    await readFile(C, "utf-8");
    //code
}

read().then((data) => {
    //code
}).catch(err => {
    //code
});

可執(zhí)行代碼,請(qǐng)戳:https://github.com/YvetteLau/...

思考一下 async/await 如何處理異步并發(fā)問(wèn)題的? https://github.com/YvetteLau/...

如果你有更好的答案或想法,歡迎在這題目對(duì)應(yīng)的github下留言:說(shuō)一說(shuō)JS異步發(fā)展史

2.談?wù)剬?duì) async/await 的理解,async/await 的實(shí)現(xiàn)原理是什么?

async/await 就是 Generator 的語(yǔ)法糖,使得異步操作變得更加方便。來(lái)張圖對(duì)比一下:

async 函數(shù)就是將 Generator 函數(shù)的星號(hào)(*)替換成 async,將 yield 替換成await。

我們說(shuō) async 是 Generator 的語(yǔ)法糖,那么這個(gè)糖究竟甜在哪呢?

1)async函數(shù)內(nèi)置執(zhí)行器,函數(shù)調(diào)用之后,會(huì)自動(dòng)執(zhí)行,輸出最后結(jié)果。而Generator需要調(diào)用next或者配合co模塊使用。

2)更好的語(yǔ)義,async和await,比起星號(hào)和yield,語(yǔ)義更清楚了。async表示函數(shù)里有異步操作,await表示緊跟在后面的表達(dá)式需要等待結(jié)果。

3)更廣的適用性。co模塊約定,yield命令后面只能是 Thunk 函數(shù)或 Promise 對(duì)象,而async 函數(shù)的 await 命令后面,可以是 Promise 對(duì)象和原始類型的值。

4)返回值是Promise,async函數(shù)的返回值是 Promise 對(duì)象,Generator的返回值是 Iterator,Promise 對(duì)象使用起來(lái)更加方便。

async 函數(shù)的實(shí)現(xiàn)原理,就是將 Generator 函數(shù)和自動(dòng)執(zhí)行器,包裝在一個(gè)函數(shù)里。

具體代碼試下如下(和spawn的實(shí)現(xiàn)略有差異,個(gè)人覺(jué)得這樣寫(xiě)更容易理解),如果你想知道如何一步步寫(xiě)出 my_co ,可戳: https://github.com/YvetteLau/...

function my_co(it) {
    return new Promise((resolve, reject) => {
        function next(data) {
            try {
                var { value, done } = it.next(data);
            }catch(e){
                return reject(e);
            }
            if (!done) { 
                //done為true,表示迭代完成
                //value 不一定是 Promise,可能是一個(gè)普通值。使用 Promise.resolve 進(jìn)行包裝。
                Promise.resolve(value).then(val => {
                    next(val);
                }, reject);
            } else {
                resolve(value);
            }
        }
        next(); //執(zhí)行一次next
    });
}
function* test() {
    yield new Promise((resolve, reject) => {
        setTimeout(resolve, 100);
    });
    yield new Promise((resolve, reject) => {
        // throw Error(1);
        resolve(10)
    });
    yield 10;
    return 1000;
}

my_co(test()).then(data => {
    console.log(data); //輸出1000
}).catch((err) => {
    console.log("err: ", err);
});

如果你有更好的答案或想法,歡迎在這題目對(duì)應(yīng)的github下留言:談?wù)剬?duì) async/await 的理解,async/await 的實(shí)現(xiàn)原理是什么?

3.使用 async/await 需要注意什么?

await 命令后面的Promise對(duì)象,運(yùn)行結(jié)果可能是 rejected,此時(shí)等同于 async 函數(shù)返回的 Promise 對(duì)象被reject。因此需要加上錯(cuò)誤處理,可以給每個(gè) await 后的 Promise 增加 catch 方法;也可以將 await 的代碼放在 try...catch 中。

多個(gè)await命令后面的異步操作,如果不存在繼發(fā)關(guān)系,最好讓它們同時(shí)觸發(fā)。

//下面兩種寫(xiě)法都可以同時(shí)觸發(fā)
//法一
async function f1() {
    await Promise.all([
        new Promise((resolve) => {
            setTimeout(resolve, 600);
        }),
        new Promise((resolve) => {
            setTimeout(resolve, 600);
        })
    ])
}
//法二
async function f2() {
    let fn1 = new Promise((resolve) => {
            setTimeout(resolve, 800);
        });
    
    let fn2 = new Promise((resolve) => {
            setTimeout(resolve, 800);
        })
    await fn1;
    await fn2;
}

await命令只能用在async函數(shù)之中,如果用在普通函數(shù),會(huì)報(bào)錯(cuò)。

async 函數(shù)可以保留運(yùn)行堆棧。

/**
* 函數(shù)a內(nèi)部運(yùn)行了一個(gè)異步任務(wù)b()。當(dāng)b()運(yùn)行的時(shí)候,函數(shù)a()不會(huì)中斷,而是繼續(xù)執(zhí)行。
* 等到b()運(yùn)行結(jié)束,可能a()早就* 運(yùn)行結(jié)束了,b()所在的上下文環(huán)境已經(jīng)消失了。
* 如果b()或c()報(bào)錯(cuò),錯(cuò)誤堆棧將不包括a()。
*/
function b() {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, 200)
    });
}
function c() {
    throw Error(10);
}
const a = () => {
    b().then(() => c());
};
a();
/**
* 改成async函數(shù)
*/
const m = async () => {
    await b();
    c();
};
m();

報(bào)錯(cuò)信息如下,可以看出 async 函數(shù)可以保留運(yùn)行堆棧。

如果你有更好的答案或想法,歡迎在這題目對(duì)應(yīng)的github下留言:使用 async/await 需要注意什么?

4.如何實(shí)現(xiàn) Promise.race?

在代碼實(shí)現(xiàn)前,我們需要先了解 Promise.race 的特點(diǎn):

Promise.race返回的仍然是一個(gè)Promise.

它的狀態(tài)與第一個(gè)完成的Promise的狀態(tài)相同。它可以是完成( resolves),也可以是失敗(rejects),這要取決于第一個(gè)Promise是哪一種狀態(tài)。

如果傳入的參數(shù)是不可迭代的,那么將會(huì)拋出錯(cuò)誤。

如果傳的參數(shù)數(shù)組是空,那么返回的 promise 將永遠(yuǎn)等待。

如果迭代包含一個(gè)或多個(gè)非承諾值和/或已解決/拒絕的承諾,則 Promise.race 將解析為迭代中找到的第一個(gè)值。

Promise.race = function (promises) {
    //promises 必須是一個(gè)可遍歷的數(shù)據(jù)結(jié)構(gòu),否則拋錯(cuò)
    return new Promise((resolve, reject) => {
        if (typeof promises[Symbol.iterator] !== "function") {
            //真實(shí)不是這個(gè)錯(cuò)誤
            Promise.reject("args is not iteratable!");
        }
        if (promises.length === 0) {
            return;
        } else {
            for (let i = 0; i < promises.length; i++) {
                Promise.resolve(promises[i]).then((data) => {
                    resolve(data);
                    return;
                }, (err) => {
                    reject(err);
                    return;
                });
            }
        }
    });
}

測(cè)試代碼:

//一直在等待態(tài)
Promise.race([]).then((data) => {
    console.log("success ", data);
}, (err) => {
    console.log("err ", err);
});
//拋錯(cuò)
Promise.race().then((data) => {
    console.log("success ", data);
}, (err) => {
    console.log("err ", err);
});
Promise.race([
    new Promise((resolve, reject) => { setTimeout(() => { resolve(100) }, 1000) }),
    new Promise((resolve, reject) => { setTimeout(() => { resolve(200) }, 200) }),
    new Promise((resolve, reject) => { setTimeout(() => { reject(100) }, 100) })
]).then((data) => {
    console.log(data);
}, (err) => {
    console.log(err);
});

引申: Promise.all/Promise.reject/Promise.resolve/Promise.prototype.finally/Promise.prototype.catch 的實(shí)現(xiàn)原理,如果還不太會(huì),戳:Promise源碼實(shí)現(xiàn)

如果你有更好的答案或想法,歡迎在這題目對(duì)應(yīng)的github下留言:如何實(shí)現(xiàn) Promise.race?

5.可遍歷數(shù)據(jù)結(jié)構(gòu)的有什么特點(diǎn)?

一個(gè)對(duì)象如果要具備可被 for...of 循環(huán)調(diào)用的 Iterator 接口,就必須在其 Symbol.iterator 的屬性上部署遍歷器生成方法(或者原型鏈上的對(duì)象具有該方法)

PS: 遍歷器對(duì)象根本特征就是具有next方法。每次調(diào)用next方法,都會(huì)返回一個(gè)代表當(dāng)前成員的信息對(duì)象,具有value和done兩個(gè)屬性。

//如為對(duì)象添加Iterator 接口;
let obj = {
    name: "Yvette",
    age: 18,
    job: "engineer",
    [Symbol.iterator]() {
        const self = this;
        const keys = Object.keys(self);
        let index = 0;
        return {
            next() {
                if (index < keys.length) {
                    return {
                        value: self[keys[index++]],
                        done: false
                    };
                } else {
                    return { value: undefined, done: true };
                }
            }
        };
    }
};

for(let item of obj) {
    console.log(item); //Yvette  18  engineer
}

使用 Generator 函數(shù)(遍歷器對(duì)象生成函數(shù))簡(jiǎn)寫(xiě) Symbol.iterator 方法,可以簡(jiǎn)寫(xiě)如下:

let obj = {
    name: "Yvette",
    age: 18,
    job: "engineer",
    * [Symbol.iterator] () {
        const self = this;
        const keys = Object.keys(self);
        for (let index = 0;index < keys.length; index++) {
            yield self[keys[index]];//yield表達(dá)式僅能使用在 Generator 函數(shù)中
        } 
    }
};
原生具備 Iterator 接口的數(shù)據(jù)結(jié)構(gòu)如下。

Array

Map

Set

String

TypedArray

函數(shù)的 arguments 對(duì)象

NodeList 對(duì)象

ES6 的數(shù)組、Set、Map 都部署了以下三個(gè)方法: entries() / keys() / values(),調(diào)用后都返回遍歷器對(duì)象。

如果你有更好的答案或想法,歡迎在這題目對(duì)應(yīng)的github下留言:可遍歷數(shù)據(jù)結(jié)構(gòu)的有什么特點(diǎn)?

6.requestAnimationFrame 和 setTimeout/setInterval 有什么區(qū)別?使用 requestAnimationFrame 有哪些好處?

在 requestAnimationFrame 之前,我們主要使用 setTimeout/setInterval 來(lái)編寫(xiě)JS動(dòng)畫(huà)。

編寫(xiě)動(dòng)畫(huà)的關(guān)鍵是循環(huán)間隔的設(shè)置,一方面,循環(huán)間隔足夠短,動(dòng)畫(huà)效果才能顯得平滑流暢;另一方面,循環(huán)間隔還要足夠長(zhǎng),才能確保瀏覽器有能力渲染產(chǎn)生的變化。

大部分的電腦顯示器的刷新頻率是60HZ,也就是每秒鐘重繪60次。大多數(shù)瀏覽器都會(huì)對(duì)重繪操作加以限制,不超過(guò)顯示器的重繪頻率,因?yàn)榧词钩^(guò)那個(gè)頻率用戶體驗(yàn)也不會(huì)提升。因此,最平滑動(dòng)畫(huà)的最佳循環(huán)間隔是 1000ms / 60 ,約為16.7ms。

setTimeout/setInterval 有一個(gè)顯著的缺陷在于時(shí)間是不精確的,setTimeout/setInterval 只能保證延時(shí)或間隔不小于設(shè)定的時(shí)間。因?yàn)樗鼈儗?shí)際上只是把任務(wù)添加到了任務(wù)隊(duì)列中,但是如果前面的任務(wù)還沒(méi)有執(zhí)行完成,它們必須要等待。

requestAnimationFrame 才有的是系統(tǒng)時(shí)間間隔,保持最佳繪制效率,不會(huì)因?yàn)殚g隔時(shí)間過(guò)短,造成過(guò)度繪制,增加開(kāi)銷;也不會(huì)因?yàn)殚g隔時(shí)間太長(zhǎng),使用動(dòng)畫(huà)卡頓不流暢,讓各種網(wǎng)頁(yè)動(dòng)畫(huà)效果能夠有一個(gè)統(tǒng)一的刷新機(jī)制,從而節(jié)省系統(tǒng)資源,提高系統(tǒng)性能,改善視覺(jué)效果。

綜上所述,requestAnimationFrame 和 setTimeout/setInterval 在編寫(xiě)動(dòng)畫(huà)時(shí)相比,優(yōu)點(diǎn)如下:

1.requestAnimationFrame 不需要設(shè)置時(shí)間,采用系統(tǒng)時(shí)間間隔,能達(dá)到最佳的動(dòng)畫(huà)效果。

2.requestAnimationFrame 會(huì)把每一幀中的所有DOM操作集中起來(lái),在一次重繪或回流中就完成。

3.當(dāng) requestAnimationFrame() 運(yùn)行在后臺(tái)標(biāo)簽頁(yè)或者隱藏的