チェーン呼び出しとイベントループ--JavaScript面接問題の思考
9770 ワード
前言:昨日グループの中で(jin)論(chui)技(niu)術(pi)を求めて、ある鉄さんは彼の面のある会社の面接問題を出して、ついでに保存しました.今朝、少し時間をかけてこの問題を作りましたが、面白いことに気づき、今日まじめに仕事をする前に、私の解題案と思考過程をみんなと分かち合うことにしました.
テーマは以下の通りです(自分で考えてみると、私より良い方法が考えられるかもしれません):
小さな目で見ると、これらのニーズはチェーン呼び出しを実現することですが、チェーン呼び出しで最も一般的なのはjQueryであり、私たちがよく知っているPromiseです.
jQueryにおけるチェーン呼び出しの原理は、関数の末尾
このコースを知ったら、次にこのコースに従って最初の小さなニーズに合った関数をすばやく書くことができます.
わずか3行のコードしかありませんが、エラーは報告されず、実行が速く、最初の小さなニーズを完璧に実現しました.
ある通行人:“待って、、1つの簡単な関数ではありませんて、方法はどこに使いますか?”
ああ、あなたに発見されました.若者はいいですね.はい、今チェーン調で2番目の小さな需要を実現しました.
ブラウザの中をほうり出して走ってみると,赤い棒が飛び出した.
ナニー、
しかし、Googleエンジニアはそんなに頼りにならないわけではないでしょう.まさか私が書き間違えたの?
コードをスキャンすると、
もうちょっと走って、赤いストライプはありません.ねえ.
しかし、よく見ると、需要の順序と一致していません.私たちの現在の出力はこうです.
Emmmmmさん、今JavaScriptのハードな腕を出さなければなりませんね.
JavaScriptには同期タスクと非同期タスクがあり、同期タスクは私たちが作成した順序で実行スタックに押し込み、一歩一歩実行します.
よくわからなかったら、この文章を参考にして、JavaScriptの実行メカニズムを徹底的に理解して、とても分かりやすく書くことができます.
この知識を知った後、卵を並べて、それは私たちに必要なコードを書くことができません.の
空気が静かになって数十分後、私はまだ何の見当もつかず、コップを手に取って、起きてコップの水圧を倒す準備をしていたが、突然稲妻が私に当たったように、vueの中で
実行が終わって、出力は需要とそっくりで、へへへ.
次に、3番目の小さなニーズに従って実行します.結果は次のとおりです.
間違いなく、いいですが、順番がまた間違っています...これは困りますね.空気がまた静かになるのを見て、私は消耗することができなくて、いくつかの常用の方法を使うことを決定して、例えば
走って、3番目の小さな需要の出力とそっくりで、ねえへへ、おかずは一皿です.
最後に、この小さな要件において、チェーン呼び出しには
優先度に基づいて操作できるデータ構造は、私の知っている範囲では優先キューのみであり、優先キューは配列で実現することができ、soは、優先度
袖を引っ張って乾かし続け、数分後に次の関数がありました.
私の考えでは、
私の考えは正しいはずですが、中に含まれている赤いメモをぼんやりと感じて、まずブラウザをなくして走ってみました.
結果は次のとおりです.
やはり、必要な順序で実行されていません.ここでは
待って...私はさっき何を言ったのか、「優先度」、私たちは上にひっくり返して、私は前にこの言葉を言ったことがあるようです.
そう、
ブラウザをなくして実行して、完全に問題がなくて、nodeの中でも同様に、欧耶、完璧です.
最後にいくつかの粗い図を描いて、この実行の過程を簡単に説明します.
チェーン呼び出しなので、チェーン上のはスタックに入って実行されます.ええ、実行スタックにはsleepとsleepFirstが描かれていません.の
ここでsettimeoutのhandlerはマクロタスクであり、
その後、実行スタックが空になり、
セットタイムアウトが1つあるので、handlerを
前のマイクロタスクが実行されるとスタックが出て、このとき
このとき
関数の内部にsettimeoutがあるので、handlerを
その後、実行スタックを空にして、次のマクロタスクを続行します.
実行スタックが空で、最後のマクロタスクをスタックに投げ込んで実行します.
最後にまとめると、この問題の難点は
また、普段あまりペンを動かさない(例えば私)、最初から文章を書くと喉が詰まってしまい、多くの内容が漏れてしまいます.だから、普段は時間があればもっとペンを動かして、文章を書く必要がありますが、東拼西凑一篇ではなく、本当に自分の思考と悟りを持っていなければなりません.
最后に各位の见官の殿さまに1つの小さい需要の练习手を追加します:
テーマは以下の通りです(自分で考えてみると、私より良い方法が考えられるかもしれません):
小さな目で見ると、これらのニーズはチェーン呼び出しを実現することですが、チェーン呼び出しで最も一般的なのはjQueryであり、私たちがよく知っているPromiseです.
jQueryにおけるチェーン呼び出しの原理は、関数の末尾
return this
(すなわち、このオブジェクト自体を返す)において、オブジェクトが自身の関数を呼び出し続け、チェーン呼び出しをサポートすることができるようにすることである.このコースを知ったら、次にこのコースに従って最初の小さなニーズに合った関数をすばやく書くことができます.
const LazyMan = function (name) {
console.log(`Hi i am ${name}`);
}
LazyMan('Tony')
// Hi i am Tony
わずか3行のコードしかありませんが、エラーは報告されず、実行が速く、最初の小さなニーズを完璧に実現しました.
ある通行人:“待って、、1つの簡単な関数ではありませんて、方法はどこに使いますか?”
ああ、あなたに発見されました.若者はいいですね.はい、今チェーン調で2番目の小さな需要を実現しました.
const LazyMan = function (name) {
console.log(`Hi i am ${name}`);
class F {
sleep(timeout) {
setTimeout(function () {
console.log(` ${timeout} `);
return this;
}, timeout)
};
eat(food) {
console.log(`I am eating ${food}`);
return this;
}
}
return new F();
}
LazyMan('Tony').sleep(10).eat('lunch')
ブラウザの中をほうり出して走ってみると,赤い棒が飛び出した.
Uncaught TypeError: Cannot read property 'eat' of undefined
ナニー、
eat
はなぜundefined
で呼び出されたのか、私はsleep
の中でthis
に戻ったのではないでしょうか!?Chromeがまたこっそり更新して新しいバグを追加したのか、、、しかし、Googleエンジニアはそんなに頼りにならないわけではないでしょう.まさか私が書き間違えたの?
コードをスキャンすると、
return this
はsetTimeout
の処理関数で返され、sleep
ではなく、少し変更されます.// ...
sleep(timeout) {
setTimeout(function () {
console.log(` ${timeout} ....`);
}, timeout)
return this;
};
// ...
もうちょっと走って、赤いストライプはありません.ねえ.
しかし、よく見ると、需要の順序と一致していません.私たちの現在の出力はこうです.
Hi i am Tony
I am eating lunch
10
Emmmmmさん、今JavaScriptのハードな腕を出さなければなりませんね.
JavaScriptには同期タスクと非同期タスクがあり、同期タスクは私たちが作成した順序で実行スタックに押し込み、一歩一歩実行します.
setTimeout
は非同期のタスクであり、ブラウザではタイミングトリガスレッドが担当し、このスレッドはタイミングを計時し、計時が完了するとこのイベントのhandler
をタスクキューに押し込み、タスクキュー内のタスクは、実行スタックが空の場合、キュー内のタスクを実行スタックに捨てて実行する必要がある(ここからもhandler
が時間通りに実行できないことが分かる).(手当たり次第にスケッチを描いたので、ちょっと醜いですが、私が表現したい意味には影響しないはずです)よくわからなかったら、この文章を参考にして、JavaScriptの実行メカニズムを徹底的に理解して、とても分かりやすく書くことができます.
この知識を知った後、卵を並べて、それは私たちに必要なコードを書くことができません.の
空気が静かになって数十分後、私はまだ何の見当もつかず、コップを手に取って、起きてコップの水圧を倒す準備をしていたが、突然稲妻が私に当たったように、vueの中で
nextTick
を実現する方法で実現したコードが頭に浮かんだ.コードははっきりしていないが(私は全然覚えていない)、私はこれが何か問題を解決するのに役立つはずだ.so、私はコップを置いて、熟練してあるhubを開けて、中でnextTick
の実現コード(ここでnext-tick.js)を見つけました.最初の行から最後の行まですばやくスキャンすると、実行する必要がある関数をcallbacks
配列で格納し、micro task
およびmacro task
の優先度特性を利用して、DOMレンダリングの前にcallbacks
のコールバックを実行することができます.Emmmmm、私の今の需要とは何の関係もないようで、何の役にも立たない.しかし、実行する必要がある関数を配列に追加して、最後に実行することもできます.やると言ったらすぐに、次のコードを書くことができます. const LazyMan = function (name) {
console.log(`Hi i am ${name}`);
function _eat(food){
console.log(`I am eating ${food}`);
}
const callbacks = [];
class F {
sleep(timeout) {
setTimeout(function () {
console.log(` ${timeout} ....`);
callbacks.forEach(cb=>cb())
}, timeout);
return this;
};
eat(food) {
callbacks.push(_eat.bind(null,food));
return this;
}
}
return new F();
};
LazyMan('Tony').sleep(10).eat('lunch')
// Hi i am Tony
// 10 ....
// I am eating lunch
実行が終わって、出力は需要とそっくりで、へへへ.
次に、3番目の小さなニーズに従って実行します.結果は次のとおりです.
//...
LazyMan('Tony').eat('lunch').sleep(10).eat('dinner')
// Hi i am Tony
// 10
// I am eating lunch
// I am eating dinner
//...
間違いなく、いいですが、順番がまた間違っています...これは困りますね.空気がまた静かになるのを見て、私は消耗することができなくて、いくつかの常用の方法を使うことを決定して、例えば
flag
をプラスして、sleep
の後で実行する必要があるかどうかを区別して、書き換えた後に以下のようにします: const LazyMan = function (name) {
console.log(`Hi i am ${name}`);
function _eat(food) {
console.log(`I am eating ${food}`);
}
const callbacks = [];
let isNeedSleep = false;
class F {
sleep(timeout) {
setTimeout(function () {
console.log(` ${timeout} `);
callbacks.forEach(cb => cb())
}, timeout);
isNeedSleep = true;
return this;
};
eat(food) {
if (isNeedSleep) {
callbacks.push(_eat.bind(null, food));
} else {
_eat.call(null, food);
}
return this;
}
}
return new F();
};
走って、3番目の小さな需要の出力とそっくりで、ねえへへ、おかずは一皿です.
最後に、この小さな要件において、チェーン呼び出しには
sleepFirst
が追加され、sleep
がチェーン呼び出しの最前端に持ち上げられて実行される、すなわちsleepFirst
の優先度が最も高いという効果がある.優先度に基づいて操作できるデータ構造は、私の知っている範囲では優先キューのみであり、優先キューは配列で実現することができ、soは、優先度
callbacks
の呼び出しを配列で実現することができるか、すなわちネスト配列で実現することができるかを考えてみましょう.答えて曰わく、君の考えは間違っていない.袖を引っ張って乾かし続け、数分後に次の関数がありました.
const LazyMan = function (name) {
console.log(`Hi i am ${name}`);
function _eat(food) {
console.log(`I am eating ${food}`);
}
const callbackQueue = [];
let index = 0;
class F {
sleep(timeout) {
const _callbacks = callbackQueue.shift();
_callbacks && _callbacks.forEach(cb => cb());
setTimeout(function () {
console.log(` ${timeout} ....`);
const _callbacks = callbackQueue.shift();
_callbacks && _callbacks.forEach(cb => cb())
}, timeout);
index ++;
return this;
};
eat(food) {
if(!callbackQueue[index]) callbackQueue[index] = [];
callbackQueue[index].push(_eat.bind(null, food));
return this;
};
sleepFirst(timeout){
setTimeout(function () {
console.log(` ${timeout} ....`);
const _callbacks = callbackQueue.shift();
_callbacks && _callbacks.forEach(cb => cb())
}, timeout);
index ++;
return this;
}
}
return new F();
};
私の考えでは、
sleep
が1回通過するたびに、index
が+1され、新しいcallback
のグループがあることを示し、eat
が実行されると、現在のindex
に対応する配列が存在するか否かを判断し、存在しない場合は対応する空の配列を作成し、対応する呼び出す必要がある関数をこの配列に追加し、最後にこの配列をcallbackQueue
に保存し、追加が完了すると、callbackQueue
から順番に一歩一歩取り出して実行される.私の考えは正しいはずですが、中に含まれている赤いメモをぼんやりと感じて、まずブラウザをなくして走ってみました.
結果は次のとおりです.
Hi i am Tony
I am eating lunch
I am eating dinner
5 ....
10 ....
やはり、必要な順序で実行されていません.ここでは
sleepFirst
の優先度を処理できるという根本的な問題はありませんから.の待って...私はさっき何を言ったのか、「優先度」、私たちは上にひっくり返して、私は前にこの言葉を言ったことがあるようです.
そう、
vue
のnextTick
で使用されています.これを参考にして、Event Loop
のmicro task
とmacro task
で実行される優先度を利用して、この問題を解決することができます. const LazyMan = function (name) {
console.log(`Hi i am ${name}`);
function _eat(food) {
console.log(`I am eating ${food}`);
}
const callbackQueue = [];
let index = 0;
class F {
sleep(timeout) {
setTimeout(() => {
const _callbacks = callbackQueue.shift();
_callbacks && _callbacks.forEach(cb => cb());
setTimeout(function () {
console.log(` ${timeout} ....`);
const _callbacks = callbackQueue.shift();
_callbacks && _callbacks.forEach(cb => cb())
}, timeout);
})
index++;
return this;
};
eat(food) {
if (!callbackQueue[index]) callbackQueue[index] = [];
callbackQueue[index].push(_eat.bind(null, food));
return this;
};
sleepFirst(timeout) {
Promise.resolve().then(() => {
const _callbacks = callbackQueue.shift();
setTimeout(function () {
console.log(` ${timeout} ....`);
_callbacks && _callbacks.forEach(cb => cb())
}, timeout);
})
index++;
return this;
}
}
return new F();
};
ブラウザをなくして実行して、完全に問題がなくて、nodeの中でも同様に、欧耶、完璧です.
最後にいくつかの粗い図を描いて、この実行の過程を簡単に説明します.
チェーン呼び出しなので、チェーン上のはスタックに入って実行されます.ええ、実行スタックにはsleepとsleepFirstが描かれていません.の
Hi i am Tony
ここでsettimeoutのhandlerはマクロタスクであり、
marco task
キューに参加する.Promise.resolve().then
のコールバックはマイクロタスクになり、micro task
キューに追加されます.その後、実行スタックが空になり、
micro task
の空になっていないタスクが実行スタックに加わって実行され、セットタイムアウトが1つあるので、handlerを
macro task
に追加します.前のマイクロタスクが実行されるとスタックが出て、このとき
macro task
の最初のタスクが実行スタックに入って実行されます.このとき
callbacks
があれば実行されます関数の内部にsettimeoutがあるので、handlerを
macro task
に追加します.その後、実行スタックを空にして、次のマクロタスクを続行します.
5 ....
I am eating lunch
I am eating dinner
実行スタックが空で、最後のマクロタスクをスタックに投げ込んで実行します.
10 ....
I am eating junk food
最後にまとめると、この問題の難点は
event loop
で解決したいかどうかで、この方向に考えれば簡単です.また、普段あまりペンを動かさない(例えば私)、最初から文章を書くと喉が詰まってしまい、多くの内容が漏れてしまいます.だから、普段は時間があればもっとペンを動かして、文章を書く必要がありますが、東拼西凑一篇ではなく、本当に自分の思考と悟りを持っていなければなりません.
最后に各位の见官の殿さまに1つの小さい需要の练习手を追加します:
LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(10).eat('junk food').eat('healthy food')
// Hi i am Tony
// 5
// I am eating lunch
// I am eating dinner
// 10
// I am eating junk food
// I am eating healthy food