フロントエンドの面接でよく聞かれる JavaScript の質問の完全なリスト

フロントエンドの面接でよく聞かれる JavaScript の質問の完全なリスト

フロントエンドの面接では、手作業によるコードの分解は当然避けられず、大きな割合を占めます。
一般的に言えば、コードが適切に記述されていれば、理論的な知識について十分に明確に答えられなくても、面接に合格する可能性が高くなります。実際、手書きの文字をたくさん書くことで、関連する理論に対する理解度がテストされることがよくあります。

プログラミングに関する質問は、主に以下の種類に分けられます。

  • アルゴリズムに関する質問
  • js の原則と ajax リクエストに関する質問
  • ビジネスシナリオの質問: 特定の機能を備えたコンポーネントを実装する
  • その他 (上級、包括的なコンピューター知識の試験、比較的テストが少ない): サブスクリプション パブリッシャー パターンを実装する、オブジェクト指向プログラミング、手続き型プログラミング、関数型プログラミングを使用して象を冷蔵庫に入れる、など。

最初の 2 つのタイプが最大の割合を占めます。
アルゴリズムの問​​題については、データ構造(スタック、リンクリスト、キュー、ツリー)、動的プログラミング、 DFSBFSに焦点を当てて、毎日1つのleetcodeを練習する習慣を身につけることをお勧めします。

この記事では、主に第 2 のタイプのさまざまな焦点を絞った手書き文字について説明します。

推奨される優先事項:

  • instanceof (プロトタイプチェーンの理解度をテストするため)
  • new (オブジェクトインスタンスを作成するプロセスの理解)
  • call&apply&bind (これが何を指すのか理解する)
  • 手書きのpromise (非同期性の理解)
  • 手書きのネイティブajax (Ajax の原則と HTTP リクエスト メソッドを理解し、get および post リクエストの実装に重点を置く)
  • イベントのサブスクリプションと公開(高頻度テストポイント)
  • その他:配列および文字列 API の実装は比較的簡単です。配列や文字列の一般的なメソッドの使い方さえ理解しておけば、その場で大まかなアウトラインを書き出すことができます。 (追記:配列のreduceメソッドの方が難しいと思います。余裕があれば別途読んでみてください。面接でreduce実装を求められていなくても、他の質問に答えるときにreduceを使うとプラスになります)

1. 手書きのインスタンス

Instanceof関数:

インスタンスがその親または祖先タイプのインスタンスであるかどうかを判断します。

検索プロセス中、instanceof は、右側の変数のプロトタイプが見つかるまで、左側の変数のプロトタイプ チェーンをトラバースします。検索が失敗した場合は、false を返します。

 myInstanceof = (ターゲット、オリジン) => {
     while(ターゲット) {
         (target.__proto__===origin.prototype)の場合{
            真を返す
         }
         ターゲット = ターゲット.__proto__
     }
     偽を返す
 }
 a = [1,2,3]とする
 console.log(myInstanceof(a,Array)); // 真
 console.log(myInstanceof(a,Object)); // 真


2.配列のマップメソッドを実装する

配列のmap()メソッドは、元の配列内の対応する位置にある要素に対して指定された関数を 1 回呼び出したときの戻り値に対応する各要素を持つ新しい配列を返します。

使用法:

定数a = [1, 2, 3, 4];
定数 b = array1.map(x => x * 2);
console.log(b); // 配列 [2, 4, 6, 8]


実装する前に、map メソッドのパラメータを見てみましょう。

mapメソッドには 2 つのパラメータがあります。1 つは配列要素を操作するメソッド fn で、もう 1 つは this ポインタ (オプション) です。fn を使用すると、3 つのパラメータを取得できます。実装時にこれらを忘れないように注意してください。そうすることで、完全な実装とみなすことができます。

ネイティブ実装:

    // 実装 Array.prototype.myMap = function(fn, thisValue) {
            res = [] とします
            この値 = この値||[]
            arr = thisとする
            for(let i=0; i<arr.length; i++) {
                res.push(fn.call(thisValue, arr[i],i,arr)) // パラメータは、このポインタ、現在の配列項目、現在のインデックス、現在の配列です}
            戻り値
        }
        // const a = [1,2,3] を使用します。
        定数 b = a.myMap((a,インデックス)=> {
                a+1 を返します。 
            }
        )
        console.log(b) // 出力は[2, 3, 4]


3. Reduceは配列のmapメソッドを実装する

配列の組み込みreduceメソッドを使用してmapメソッドを実装し、 reduce原則の理解を確認します。

Array.prototype.myMap = function(fn,thisValue){
     var res = [];
     thisValue = thisValue||[];
     this.reduce(関数(pre,cur,index,arr){
         res.push(fn.call(thisValue,cur,index,arr)) を返します。
     },[]);
     res を返します。
}
​
var arr = [2,3,1,5];
arr.myMap(関数(項目,インデックス,arr){
 console.log(アイテム、インデックス、配列);
})


4. 手書き配列の縮小法

reduce()メソッドは関数をアキュムレータとして受け取り、配列内の各値 (左から右へ) を 1 つの値に減らします。これは、ES5 で追加された配列の項目ごとの処理メソッドです。

パラメータ:

  • コールバック (配列内の各項目に対して呼び出される関数。4 つの関数を受け入れます。)
  1. previousValue (最後のコールバック関数呼び出しの戻り値、または初期値)
  2. currentValue (現在処理中の配列要素)
  3. currentIndex (現在処理中の配列要素のインデックス)
  4. 配列(reduce() メソッドが呼び出される配列)
  • initialValue (オプションの初期値。コールバック関数が初めて呼び出されたときに previousValue に渡される値)
 関数reduce(arr, cb, initialValue){
     var num = initValue == 未定義ですか? num = arr[0]: initValue;
     var i = initValue == 未定義? 1: 0
     (i; i< arr.length; i++) の場合{
        num = cb(num,arr[i],i)
     }
     数値を返す
 }
 
 関数 fn(結果, 現在の値, インデックス){
     結果 + 現在の値を返す
 }
 
 var arr = [2,3,4,5]
 var b = 減らす(arr, fn, 10) 
 var c = 減らす(arr, fn)
 コンソール.log(b) // 24


5. 配列の平坦化

配列の平坦化とは、多次元配列を1次元配列に変換することである。

5. 1 es6が提供する新しいメソッドflat(depth)

a = [1,[2,3]]とします。
a.flat(); // [1,2,3]
a.flat(1); //[1,2,3]

実はもっと簡単な方法があります。配列の次元を知らなくても、対象配列を直接 1 次元配列に変換できます。深さの値は Infinity に設定されています。

a = [1,[2,3,[4,[5]]]]とします。
a.flat(Infinity); // [1,2,3,4,5] aは4次元配列です

5.2 cancatの使用

関数flatten(arr){
     var res = [];
     (i = 0、長さ = arr.length、i < length、i++ とします) {
     Array.isArray(arr[i]) の場合 {
     res = res.concat(flatten(arr[i])); //concat は元の配列を変更しません //res.push(...flatten(arr[i])); //またはスプレッド演算子を使用します} else {
         res.push(arr[i]);
       }
     }
     res を返します。
 }
 arr1 = [1, 2,[3,1],[2,3,4,[2,3,4]]]とする
フラット化(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]


補足:ディープフラットを指定

再帰するたびに現在のdeep-1を追加するだけです。0 より大きい場合は、拡張を続行できます。

     関数flat(arr, deep) {
        res = [] とします
        for(let i in arr) {
            if(Array.isArray(arr[i])&&deep) {
                res = res.concat(flat(arr[i],deep-1))
            } それ以外 {
                res.push(arr[i])
            }
        }
        戻り値
    }
    コンソールにログ出力します。


6. 関数カリー化

前回の記事「フロントエンドJavaScriptで関数カリー化を徹底的に理解する」とここで使用されている同じ方法について学ぶことができます。

カリー化の定義は、パラメータの一部を受け取り、残りのパラメータを受け取る関数を返し、十分なパラメータを受け取った後に元の関数を実行することです

カリー化された関数が十分なパラメータを受け取ると、元の関数を実行します。十分なパラメータに達したかどうかをどのように判断するのでしょうか?

2 つのアプローチがあります。

  • 関数の長さプロパティを通じて関数の仮パラメータの数を取得します。仮パラメータの数は、必要なパラメータの数です。
  • カリー化されたユーティリティ関数を呼び出すときに必要な引数の数を手動で指定します。

これら 2 つの点を組み合わせると、単純なカリー関数を実装できます。

/**
 * 関数をカリー化します * @param fn カリー化される元の関数 * @param len 必要なパラメータの数。デフォルトは元の関数の仮パラメータの数です */
関数curry(fn,len = fn.length) {
 _curry.call(this,fn,len) を返します
}
​
/**
 * 転送関数 * @param fn カリー化される元の関数 * @param len 必要なパラメータ数 * @param args 受信したパラメータリスト */
関数_curry(fn,len,...args) {
    関数を返す (...パラメータ) {
         _args = [...args,...params] とします。
         if(_args.length >= len){
             fn.apply(this,_args) を返します。
         }それ以外{
          _curry.call(this,fn,len,..._args) を返します。
         }
    }
}


これを検証してみましょう:

_fn = curry(function(a,b,c,d,e){
 コンソールログ(a,b,c,d,e)
});
​
_fn(1,2,3,4,5); // 出力: 1,2,3,4,5
_fn(1)(2)(3,4,5); // 出力: 1,2,3,4,5
_fn(1,2)(3,4)(5); // 出力: 1,2,3,4,5
_fn(1)(2)(3)(4)(5); // 印刷: 1,2,3,4,5


よく使用されるツール ライブラリlodashcurryメソッドを提供し、プレースホルダーを使用して受信パラメーターの順序を変更する非常に興味深いplaceholder関数を追加します。

たとえば、プレースホルダーを渡すと、この呼び出しで渡されるパラメーターはプレースホルダーをスキップし、プレースホルダーには次の呼び出しのパラメーターが次のように入力されます。

公式サイトの例を見てみましょう。

次にプレースホルダー機能の実装方法を考えてみましょう。

lodashcurry関数の場合、 curry 関数はlodashオブジェクトにマウントされるため、lodash オブジェクトがデフォルトのプレースホルダーとして使用されます。

私たちが独自に実装した curry 関数はどのオブジェクトにもマウントされていないため、 curry 関数をデフォルトのプレースホルダーとして使用します。

プレースホルダを使用する目的は、パラメータを渡す順序を変更することです。そのため、カリー関数の実装では、プレースホルダを使用するかどうかと、プレースホルダが表すパラメータの位置をその都度記録する必要があります。

コード上で直接:

/**
 * @param fn カリー化される関数* @param length 必要なパラメータの数。デフォルトは関数の仮パラメータの数* @param holder プレースホルダ。デフォルトは現在のカリー化関数* @return {Function} カリー化後の関数*/
関数 curry(fn,length = fn.length,holder = curry){
 _curry.call(this,fn,length,holder,[],[]) を返します
}
/**
 * 転送関数* @param fn カリー化の元の関数* @param length 元の関数に必要なパラメータの数* @param holder 受け取ったプレースホルダ* @param args 受け取ったパラメータリスト* @param holders 受け取ったプレースホルダの位置リスト* @return {Function} カリー化を続行する関数または最終結果*/
関数 _curry(fn,長さ,ホルダー,引数,ホルダー){
 関数(..._args)を返す{
 // 同じ関数に対する複数の操作による混乱を避けるためにパラメータをコピーします。let params = args.slice();
 //プレースホルダーの位置リストをコピーし、新しく追加されたプレースホルダーをここに追加します。let _holders = holders.slice();
 //パラメータをループし、パラメータを追加したり、placeholders_args.forEach((arg,i)=>{
 //実パラメータの前にプレースホルダーがあります。プレースホルダーを実パラメータに置き換えます。if (arg !== holder && holders.length) {
     index = holders.shift() とします。
     _holders.splice(_holders.indexOf(インデックス),1);
     パラメータ[インデックス] = 引数;
 }
 //実際のパラメータの前にプレースホルダはありません。パラメータをパラメータリストに追加します。else if (arg !== holder && !holders.length) {
     パラメータをプッシュします。
 }
 //プレースホルダーが渡されます。前にプレースホルダーがない場合は、プレースホルダーの位置を記録します。else if (arg === holder && !holders.length) {
     パラメータをプッシュします。
     _holders.push(params.length - 1);
 }
 //渡されたプレースホルダーの前にプレースホルダーがある場合は、元のプレースホルダーの位置を削除します。そうでない場合は、(arg === holder && holders.length) {
    ホルダー.shift();
 }
 });
 // params の最初の length レコードにはプレースホルダーが含まれていないので、関数 if(params.length >= length && params.slice(0,length).every(i=>i!==holder)){ を実行します。
 fn.apply(this,params); を返します。
 }それ以外{
 _curry.call(this,fn,length,holder,params,_holders) を返します。
 }
 }
}


確認します:

fn = 関数(a, b, c, d, e) {
 コンソールにログ出力します。
}
​
let _ = {}; // プレースホルダーを定義する let _fn = curry(fn,5,_); // 関数をカリー化し、必要なパラメータ数を指定し、必要なプレースホルダーを指定する​
_fn(1, 2, 3, 4, 5); // 出力: 1,2,3,4,5
_fn(_, 2, 3, 4, 5)(1); // 出力: 1,2,3,4,5
_fn(1, _, 3, 4, 5)(2); // 出力: 1,2,3,4,5
_fn(1, _, 3)(_, 4,_)(2)(5); // 出力: 1,2,3,4,5
_fn(1, _, _, 4)(_, 3)(2)(5); // 出力: 1,2,3,4,5
_fn(_, 2)(_, _, 4)(1)(3)(5); // 出力: 1,2,3,4,5


今のところcurry機能を完全実装しました〜〜

7. 浅いコピーと深いコピーの実装

ディープ コピーとシャロー コピーは、 ObjectArrayなどの参照データ型にのみ適用されます。

7.1 浅いコピーと深いコピーの違い

浅いコピー:元のオブジェクトのプロパティ値の正確なコピーを持つ新しいオブジェクトを作成します。プロパティがプリミティブ型の場合、プリミティブ型の値がコピーされます。プロパティが参照型の場合、メモリ アドレスがコピーされます。オブジェクトの 1 つが参照型のプロパティを変更すると、他のオブジェクトにも影響します。

ディープ コピー:メモリからオブジェクトを完全にコピーし、それを保存するためにヒープ メモリ内に新しい領域を開きます。この方法では、コピー値を変更しても古いオブジェクトには影響しません。

浅いコピーの実装:

方法1:

関数 shallowCopy(ターゲット、オリジン){
    for(let item in origin) target[item] = origin[item];
    ターゲットを返します。
}


その他のメソッド(組み込み API):

(1) オブジェクト.assign

var obj={a:1,b:[1,2,3],c:function(){console.log('i am c')}}
var tar = {};
オブジェクトに tar を代入します。


もちろん、この方法はオブジェクト型にのみ適しています。配列の場合は、 sliceconcatメソッドを使用できます。

(2)配列.プロトタイプ.スライス

var arr=[1,2,[3,4]];
var newArr = arr.slice(0);
配列.プロトタイプ.連結
var arr=[1,2,[3,4]];
var newArr = arr.concat();


(3)配列.プロトタイプ.連結

var arr=[1,2,[3,4]];
var newArr = arr.concat();

テストは上記と同じです(assignはオブジェクトでテストされ、slice concatは配列でテストされます)。浅いコピーと深いコピーの概念を組み合わせて理解すると良いでしょう。

ディープコピーの実装:

方法1:

json形式に変換して解析する

const a = JSON.parse(JSON.stringify(b))

方法2:

// ディープコピー再帰関数を実装する deepCopy(newObj,oldObj){
     for(var k in oldObj){
         アイテムを oldObj[k] とします。
         // 配列、オブジェクト、または単純型であるかどうかを判断します。
         if(item インスタンス配列){
             新しいオブジェクト[k]=[]
             deepCopy(新しいオブジェクト[k]、アイテム)
         }else if(item instanceof Object){
             新しいオブジェクト[k] = {}
             deepCopy(新しいオブジェクト[k]、アイテム)
         }else{ //単純なデータ型、newObj[k]=itemを直接割り当てる
         }
     }
}


8. 手書きの呼び出し、適用、バインド

8.1 手書きの呼び出し

Function.prototype.myCall=function(context=window){ // Function メソッドなので、Function プロトタイプ オブジェクトに記述します。if(typeof this !=="function"){ // here が実際には不要な場合は、自動的にエラーがスローされます。throw new Error("not a function")
 }
 const obj=context||window //ここでES6メソッドを使用してパラメータのデフォルト値を追加できます。js strictモードのグローバルスコープは未定義です
 obj.fn=this //これは呼び出しコンテキストで、関数です。この関数をobjのメソッドとして使用します。const arg=[...arguments].slice(1) //最初のものはobjなので、これを削除し、疑似配列を配列に変換します。res=obj.fn(...arg)
 delete obj.fn // 削除に失敗すると、コンテキスト属性がどんどん増えていきます。res を返します。
}
// 使用法: f.call(obj,arg1)
関数 f(a,b){
 コンソールログ(a+b)
 console.log(この名前)
}
obj = {
 名前:1
}
f.myCall(obj,1,2) //それ以外の場合はウィンドウを指します

 
obj.greet.call({name: 'Spike'}) //出力はSpike

8.2 手書きの apply(arguments[this, [parameter 1, parameter 2.....] ])

Function.prototype.myApply = function(context) { // アロー関数には引数オブジェクトはありません。 ! ! ! !ここでは矢印関数を書くことはできません。let obj=context||window
 obj.fn=これ
 const arg=arguments[1]||[] //パラメータがある場合、結果は配列になります。let res=obj.fn(...arg)
 obj.fn を削除します
 戻り値
} 
関数 f(a,b){
 コンソールログ(a,b)
 console.log(この名前)
}
obj = {
 名前:「張三」
}
f.myApply(obj,[1,2]) //引数[1]


8.3 手書き製本

この値 = 2
var foo = {
 値: 1
};
var bar = function(名前, 年齢, 学校){
 console.log(name) // 'An'
 コンソール.log(年齢) // 22
 console.log(school) // 'ホームスクール大学'
}
var result = bar.bind(foo, 'An') //いくつかのパラメータをプリセットします 'An'
result(22, 'Home University') //このパラメータはプリセットパラメータとマージされ、バーに配置されます

シンプルバージョン

Function.prototype.bind = function(context, ...outerArgs) {
 var fn = this;
 return function(...innerArgs) { //関数を返します。...restは実際に呼び出すときに渡されるパラメータです。 return fn.apply(context,[...outerArgs, ...innerArgs]); //これを変更した関数を返します。
 //パラメータのマージ}
}


新しい失敗の理由:

例:

// コンテキストを宣言する let thovino = {
 名前: 'thovino'
}
​
// コンストラクタを宣言する let eat = function (food) {
 this.food = 食べ物
 console.log(`${this.name} は ${this.food} を食べます`)
}
eat.prototype.sayFuncName = 関数 () {
 console.log('関数名: eat')
}
​
// バインド let thovinoEat = eat.bind(thovino)
let instance = new thovinoEat('orange') // 実際にはオレンジはthovinoに入れられます console.log('instance:', instance) // {}


生成されたインスタンスは空のオブジェクトです

new演算子が実行されると、 thovinoEat関数は次のように表示されます。

関数 thovinoEat (...innerArgs) {
 eat.call(thovino, ...外側の引数, ...内側の引数)
}


new 演算子が 3 番目のステップthovinoEat.call(obj, ...args ) に到達すると、ここでの obj は new 演算子自体によって作成された単純な空のオブジェクト {} ですが、実際にはthovinoEat関数内のコンテキスト オブジェクト thovino を置き換えません。これは call の機能を超えています。置き換える必要があるのは、 thovinoEat関数内の this ポインターではなく、 thovinoオブジェクトであるためです。

言い換えれば、私たちが望んでいるのは、 new 演算子がeat内の this を、演算子自身によって作成された空のオブジェクトにポイントすることです。しかし、実際にはthovinoを指しており、 new演算子の 3 番目のステップは成功しません。

新しいバージョンと継承可能なバージョン

Function.prototype.bind = function (context, ...outerArgs) {
 that = this とする;
​
関数 res (...innerArgs) {
     if (このインスタンスのres) {
         // new 演算子が実行されると // ここで、これは new 演算子の 3 番目のステップで new 自身によって作成された単純な空のオブジェクトを指します {}
         that.call(this, ...outerArgs, ...innerArgs)
     } それ以外 {
         // 通常のバインド
         that.call(コンテキスト、...外側の引数、...内側の引数)
     }
     }
     res.prototype = this.prototype //! ! !
     戻り値
}


9. 手動で新しいものを実装する

新しいプロセスのテキスト説明:

  1. 空のオブジェクト obj を作成します。
  2. 空のオブジェクトの暗黙的なプロトタイプをコンストラクターのプロトタイプに設定します。
  3. この参照を変更するには呼び出しを使用します
  4. 戻り値がないか、オブジェクト以外の値が返された場合は、obj が新しいオブジェクトとして返されます。戻り値が新しいオブジェクトの場合は、オブジェクトが直接返されます。
関数 Person(名前,年齢){
 this.name=名前
 this.age=年齢
}
Person.prototype.sayHi=関数(){
 console.log('こんにちは! 私は '+this.name です)
}
p1=new Person('张三',18) とします。
​
////手動で新しいものを実装する
関数create(){
 obj={} とします
 //コンストラクターを取得します。let fn=[].shift.call(arguments) //arguments オブジェクトを配列に変換します。arguments は配列ではなくオブジェクトです。 ! !このメソッドは、引数配列の最初の要素を削除します。 !空の配列に要素が入っているかどうかは関係なく、argumentsの結果には影響しません。let arg = [].slice.call(arguments,1)
 obj.__proto__ = fn.prototype
 let res = fn.apply(obj, arguments) //これを変更して、インスタンスにメソッドとプロパティを追加します //オブジェクトが返されることを確認します(fnがコンストラクターでない場合)
 戻り値の型 res==='object'?res:obj
}
​
let p2=create(Person,'李四',19)
p2.こんにちは()


詳細:

[].shift.call(arguments) は次のように書くこともできます:
 arg=[...引数]とします
 let fn=arg.shift() //引数が配列メソッドを呼び出すことを可能にします。最初のパラメータはコンストラクタです。obj.__proto__=fn.prototype
 //このポインタを変更してインスタンスにメソッドと属性を追加します。let res=fn.apply(obj,arg)


10. 手書きの約束 (promise.all、promise.race でテストされることが多い)

// Promise/A+仕様で規定された3つの状態 const STATUS = {
 保留中: 「保留中」、
 FULFILLED: 「達成された」、
 拒否: '拒否'
}
​
クラス MyPromise {
 // コンストラクタは実行コールバックを受け取ります。constructor(executor) {
     this._status = STATUS.PENDING // Promise の初期ステータス this._value = undefined // then コールバック値 this._resolveQueue = [] // 解決によってトリガーされた成功キュー this._rejectQueue = [] // 拒否によってトリガーされた失敗キュー
 // これを修正するには矢印関数を使用します (解決関数はエグゼキュータでトリガーされます。そうでない場合は、これを見つけることができません)
 const 解決 = 値 => {
     定数実行 = () => {
         // Promise/A+仕様では、Promise状態は保留から履行までしかトリガーできないと規定されています
         if (this._status === STATUS.PENDING) {
             this._status = STATUS.FULFILLED // ステータスを変更 this._value = value // コールバックの現在の値を保存します​
             // 解決コールバックを実行する while (this._resolveQueue.length) {
                 定数コールバック = this._resolveQueue.shift()
                 コールバック(値)
             }
         }
     }
     // 解決コールバック操作を関数にカプセル化し、setTimeout に入れて、promise 非同期呼び出し機能を実装します (仕様では microtask、ここでは macrotask)
     setTimeout(実行)
 }
​
 // 解決と同じ
 const 拒否 = 値 => {
     定数実行 = () => {
         if (this._status === STATUS.PENDING) {
         this._status = STATUS.REJECTED
         this._value = 値
        ​
         while (this._rejectQueue.length) {
             定数コールバック = this._rejectQueue.shift()
             コールバック(値)
         }
     }
 }
     setTimeout(実行)
 }

     // new Promise() が呼び出されると、executor が直ちに実行され、resolve と reject が渡されます。
     実行者(解決、拒否)
 }
​
 // then メソッド、成功したコールバックと失敗したコールバック関数を受け取る then(onFulfilled, onRejected) {
  // 仕様によれば、then のパラメータが関数でない場合は無視され、値が渡され、チェーン呼び出しが実行を継続します typeof onFulfilled !== 'function' ? onFulfilled = value => value : null
  typeof onRejected !== 'function' ? onRejected = error => error : null

  // 新しいプロミスを返す
  新しいMyPromiseを返します((resolve,reject) => {
    constresolveFn = 値 => {
      試す {
        定数 x = onFulfilled(値)
        // 戻り値を分類します。Promise の場合は、Promise ステータスが変化するまで待機し、そうでない場合は直接解決します。
        MyPromise の x インスタンス? x.then(resolve, deny) : 解決(x)
      } キャッチ(エラー){
        拒否(エラー)
      }
    }
  }
}
​
  const 拒否Fn = エラー => {
      試す {
        定数 x = onRejected(エラー)
        MyPromise の x インスタンス? x.then(resolve, deny) : 解決(x)
      } キャッチ(エラー){
        拒否(エラー)
      }
    }

    スイッチ(this._status) {
      ケースステータス。保留中:
        this._resolveQueue.push(resolveFn)
        this._rejectQueue.push(rejectFn)
        壊す;
      ケースステータス。完了:
        解決Fn(this._value)
        壊す;
      ケースステータス。拒否:
        拒否Fn(this._value)
        壊す;
    }
 })
 }
 キャッチ(rejectFn){
  this.then(undefined, rejectFn) を返します
}
// promise.finally メソッド finally(callback) {
  this.then(value => MyPromise.resolve(callback()).then(() => value), error => { を返します。
    MyPromise.resolve(callback()).then(() => エラー)
  })
}

 // 静的解決メソッド static resolve(value) {
      戻り値 instanceof MyPromise ? value : new MyPromise(resolve => resolve(value))
  }

 // 静的拒否メソッド static deny(error) {
      新しい MyPromise を返します ((resolve, 拒否) => 拒否 (エラー))
    }

 // 静的 all メソッド static all(promiseArr) {
      カウントを 0 にする
      結果 = []
      新しいMyPromiseを返します((resolve,reject) => {
        (!promiseArr.length)の場合{
          解決(結果)を返す
        }
        promiseArr.forEach((p, i) => {
          MyPromise.resolve(p).then(値 => {
            カウント++
            結果[i] = 値
            (count === promiseArr.length)の場合{
              解決(結果)
            }
          }, エラー => {
            拒否(エラー)
          })
        })
      })
    }

 // 静的レースメソッド static race(promiseArr) {
      新しいMyPromiseを返します((resolve,reject) => {
        promiseArr.forEach(p => {
          MyPromise.resolve(p).then(値 => {
            解決(値)
          }, エラー => {
            拒否(エラー)
          })
        })
      })
    }
}

11. 手書きネイティブ AJAX

ステップ:

  • XMLHttpRequestインスタンスの作成
  • HTTPリクエストの作成
  • サーバーはXML形式の文字列を返します
  • JSはXMLを解析し、部分的なページを更新します

しかし、歴史が進むにつれて、XML は廃止され、JSON に置き換えられました。

プロパティとメソッドを理解した後、AJAX の手順に従って最も単純な GET リクエストを記述します。

バージョン 1.0:

myButton.addEventListener('click', 関数() {
  アヤックス()
})

関数ajax() {
  let xhr = new XMLHttpRequest() //インスタンス化してメソッド xhr.open('get', 'https://www.google.com') を呼び出します //パラメーター 2、url。パラメーター 3: 非同期 xhr.onreadystatechange = () => { //この関数は、readyState プロパティが変更されるたびに呼び出されます。
    if (xhr.readyState === 4) { //XMLHttpRequest プロキシの現在の状態。
      if (xhr.status >= 200 && xhr.status < 300) { //200-300 リクエスト成功 let string = request.responseText
        //JSON.parse() メソッドは JSON 文字列を解析し、文字列で記述された JavaScript 値またはオブジェクトを構築するために使用されます。let object = JSON.parse(string)
      }
    }
  }
  request.send() // 実際に HTTP リクエストを発行するために使用されます。パラメータなしのGETリクエスト}

約束の履行

関数ajax(url) {
  const p = new Promise((resolve, deny) => {
    xhr = 新しい XMLHttpRequest() を作成します。
    xhr.open('get', URL) を実行します。
    xhr.onreadystatechange = () => {
      xhr.readyState == 4の場合{
        xhr.status >= 200 && xhr.status <= 300 の場合 {
          解決(JSON.parse(xhr.responseText))
        } それ以外 {
          拒否('リクエストエラー')
        }
      }
    }
    xhr.send() //hpptリクエストを送信する})
  戻るp
}
url = '/data.json' とします
ajax(url).then(res => console.log(res))
  .catch(理由 => console.log(理由))


12. 手書きのスロットルと手ぶれ防止機能

関数スロットリングと関数アンチシェイクは、どちらも関数の実行頻度を制限することを目的としています。これらは、 windowオブジェクトのresizescrollイベント、ドラッグ中のmousemoveイベント、テキスト入力や自動補完のkeyupイベントなどのパフォーマンス最適化ソリューションです。
スロットリング:イベントを継続的にトリガーしますが、関数は n 秒に 1 回だけ実行します。

例えば、(連続的な動きを呼び出す必要がある場合に使用し、時間間隔を設定します)DOMドラッグのように、デバウンスを使用すると、停止したときに1回しか実行されないため、詰まり感があります。このとき、スロットリングを使用して、一定時間内に複数回実行すると、よりスムーズになります。

手ぶれ防止:イベントがトリガーされてから n 秒以内に関数を 1 回だけ実行できることを意味します。イベントが n 秒以内に再度トリガーされると、関数の実行時間が再計算されます。

例えば、(連続してトリガーされたときは呼び出されず、トリガー後一定時間後に呼び出される)Baidu 検索を模倣し、手ぶれ補正を使用する必要があります。連続して入力すると、リクエストは送信されません。一定時間入力しないと、リクエストが 1 回送信されます。この時間よりも短い時間入力を続けると、時間が再計算され、リクエストは送信されません。

12.1 手ぶれ補正の実装

関数デバウンス(fn, 遅延) {
     if(typeof fn!=='関数') {
        新しい TypeError をスローします ('fn は関数ではありません')
     }
     let timer; // タイマーを維持する
     関数を返す(){
         var _this = this; // デバウンス実行スコープの this (元の関数がマウントされているオブジェクト) を取得します。
         var args = 引数;
         if (タイマー) {
            タイマーをクリアします。
         }
         タイマー = setTimeout(関数() {
            fn.apply(_this, args); // apply を使用して、debounce を呼び出すオブジェクトを指します。これは、_this.fn(args); と同等です。
         }、 遅れ);
     };
}

// 電話
input1.addEventListener('keyup', デバウンス(() => {
 console.log(入力1.値)
})、600)


12.2 スロットリングの実装

関数スロットル(fn, 遅延) {
  タイマーを設定します。
  関数を返す(){
    var _this = これ;
    var args = 引数;
    if (タイマー) {
      戻る;
    }
    タイマー = setTimeout(関数() {
      fn.apply(_this, args); // ここでargsは外部から返された関数のパラメータを受け取り、引数は使用できません
      // fn.apply(_this, arguments); 注: Chrome 14 および Internet Explorer 9 では、配列のようなオブジェクトはまだ受け入れられません。配列のようなオブジェクトが渡されると例外がスローされます。
      timer = null; // 遅延後に fn を実行した後、タイマーをクリアします。このとき、タイマーは false であり、スロットルトリガーはタイマーに入ることができます}, delay)
  }
}

div1.addEventListener('drag', throttle((e) => {
  コンソールログ(e.offsetX, e.offsetY)
}, 100))

13. 手書きの約束を写真に載せる

関数 getData(url) {
  新しい Promise を返します ((resolve, reject) => {
    $.ajax({
      URL、
      成功(データ) {
        解決(データ)
      },
      エラー(err) {
        拒否(エラー)
      }
    })
  })
}
定数 url1 = './data1.json'
定数 url2 = './data2.json'
定数 url3 = './data3.json'
getData(url1).then(data1 => {
  コンソールログ(データ1)
  getData(url2) を返す
}).then(data2 => {
  コンソール.log(データ2)
  getData(url3) を返す
}).then(データ3 =>
  コンソールログ(データ3)
).catch(エラー =>
  コンソール.エラー(err)
)


14. この関数は1秒ごとに数値を出力する

(!!! この質問は、最近 ByteDance のキャンパス採用面接で尋ねられました。var が何を出力するのかを尋ねました。なぜ let に変更できるのでしょうか?
これを実現する他の方法はありますか? 2番目のletなしの書き方をブログに書いたのですが、忘れてしまいました~~~無駄に勉強しました)

ES6: letブロックスコープの原則に従って実装

for(let i=0;i<=10;i++){ //varを使用して11を出力します
 タイムアウトを設定します(()=>{
    コンソールにログ出力します。
 },1000*i)
}


letを使わない書き方:原則は、すぐに実行される関数でブロックレベルのスコープを作成することです

(var i = 1; i <= 10; i++){
    (関数 (i) {
        setTimeout(関数() {
            コンソールにログ出力します。
        }, 1000 * i)
    })(私);
}


15. 10 個のタグを作成し、クリックすると対応するシリアル番号がポップアップ表示されるようにしますか?

var a
(i=0;i<10;i++とします){
 a = ドキュメント.createElement('a')
 a.innerHTML=i+'<br>'
 a.addEventListener('click',function(e){
     console.log(this) //これは現在クリックされている <a> です
     e.preventDefault() //このメソッドが呼び出されると、デフォルトのイベント動作はトリガーされなくなります。
     //たとえば、このメソッドを実行した後、リンク (タグ) をクリックしても、ブラウザは新しい URL にジャンプしません。 event.isDefaultPrevented() を使用して、このメソッドが (そのイベント オブジェクトで) 呼び出されたかどうかを判断できます。
     警告(i)
 })
 const d = document.querySelector('div')
 d.appendChild(a) //append は既存の要素に要素を追加します。
}


16. イベントのサブスクリプションとパブリッシュを実装する (eventBus)

イベント リスナーのバインドとアンバインド、1 回の実行後のイベントのアンバインド、およびイベント リスナーのトリガーに対応する、 on off once trigger関数を備えた EventBus クラスを実装します。 この質問はByteDanceとKuaishouの両方から寄せられています。最近忙しいので、回答は後ほど更新されます。

クラスEventBus {
    on(イベント名、リスナー) {}
    off(イベント名、リスナー) {}
    once(イベント名、リスナー) {}
    トリガー(イベント名) {}
}

定数 e = 新しい EventBus();
// 関数1 関数2
e.on('e1', fn1)
e.once('e1', fn2)
e.trigger('e1') // fn1() fn2()
e.trigger('e1') // fn1()
e.off('e1', fn1)
e.trigger('e1') // ヌル

成し遂げる:

      //クラスを宣言する class EventBus {
        コンストラクタ() {
          this.eventList = {} //イベントを収集するためのオブジェクトを作成する}
        //イベントを公開 $on(eventName, fn) {
          // イベント名が公開されているかどうかを判断します。公開の追加: 公開を作成して追加します this.eventList[eventName]
            ? this.eventList[イベント名].push(fn)
            : (this.eventList[イベント名] = [fn])
        }
        //イベントをサブスクライブ $emit(eventName) {
          if (!eventName) throw new Error('イベント名を渡してください')
          //サブスクリプションパラメータを取得する const data = [...arguments].slice(1)
          if (this.eventList[イベント名]) {
            this.eventList[イベント名].forEach((i) => {
              試す {
                i(...data) //ポーリングイベント} catch (e) {
                console.error(e + 'eventName:' + eventName) //実行中にエラーを収集する}
            })
          }
        }
        // 1回実行$once(eventName, fn) {
          const _this = これ
          関数onceHandle() {
            fn.apply(null, 引数)
            _this.$off(eventName, onceHandle) // 実行が成功したら監視をキャンセルします}
          this.$on(イベント名、onceHandle)
        }
        // 登録解除 $off(eventName, fn) {
          //パラメータが渡されなかった場合はすべてのサブスクリプションをキャンセルします if (!arguments.length) {
            戻り値 (this.eventList = {})
          }
          //eventNameが配列として渡された場合、複数のサブスクリプションをキャンセルします。if (Array.isArray(eventName)) {
            イベント名.forEach((イベント) => { を返します
              this.$off(イベント、fn)
            })
          }
          //fnが渡されない場合、イベント名の下にあるすべてのキューをキャンセルします。if (arguments.length === 1 || !fn) {
            this.eventList[イベント名] = []
          }
          //イベント名の下のfnをキャンセルする
          this.eventList[イベント名] = this.eventList[イベント名].filter(
            (f) => f !== fn
          )
        }
      }
      定数イベント = 新しいEventBus()

      b = 関数 (v1, v2, v3) とします。
        コンソールログ('b', v1, v2, v3)
      }
      関数()を次のように記述します。
        コンソールログ('a')
      }
      イベント.$once('テスト', a)
      イベント.$on('テスト', b)
      イベント.$emit('テスト', 1, 2, 3, 45, 123)

      イベント.$off(['テスト'], b)

      イベント.$emit('テスト', 1, 2, 3, 45, 123)

フロントエンド面接におけるjsの高頻度手書きについての記事はこれで終わりです。jsの高頻度手書きに関する関連コンテンツについては、123WORDPRESS.COMの過去の記事を検索するか、以下の関連記事を引き続き閲覧してください。今後とも123WORDPRESS.COMをよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JS は高頻度の連続クリックを禁止するメソッドを実装します [ES6 構文に基づく]
  • JavaScript でイベント関数の高頻度トリガーと高頻度呼び出しを防ぐ方法
  • JavaScript ループトラバーサルの 24 種類のメソッドをすべてご存知ですか?
  • JavaScript配列重複排除の詳細な説明
  • jsを使用してシンプルなカルーセル効果を実現する
  • jsを使ってシンプルなディスククロックを実現する
  • 航空機戦争ゲームを実装するためのJavaScript
  • 初心者でもjsのtypeofとinstanceofの違いを理解できます

<<:  MySQL マスタースレーブレプリケーションのいくつかのレプリケーション方法の概要

>>:  Nginx プロキシを使用してフロントエンドのクロスドメイン問題を解決する方法

推薦する

MySQLにおける分散ロックの考え方をDBの助けを借りて詳しく説明します

序文スタンドアロン ロックであっても分散ロックであっても、共有データに基づいて現在の操作の動作を判断...

uni-appがNFC読み取り機能を実装

この記事では、参考までに、NFC読み取り機能を実装するためのuni-appの具体的なコードを紹介しま...

要素を中央に配置するための配置方法 (Web ページ レイアウトのヒント)

ブラウザウィンドウの中央に要素を配置する方法まず、コード ブロックを示します。すでにコードを理解して...

テーブルのネストと境界の結合の問題に対する解決策

【質問】外側のテーブルと内側のテーブルがネストされていて、内側のテーブルと外側のテーブルの両方に境界...

VSCode 開発 UNI-APP 構成チュートリアルとプラグイン

目次前面に書かれた予防開発環境構築開発構成に関する注意事項前面に書かれたuni-app は、Vue....

パーソナライズされた検索エンジンを使用して、必要なパーソナライズされた情報を検索します

現在、多くの人がインターネット上で生活しており、インターネットで情報を検索することは日常的な作業とな...

Linux システム ディレクトリ sys、tmp、usr、var の詳細な説明。

Linux 初心者から Linux マスターへの成長の道: Linux システム ディレクトリ s...

複数の .sql ファイルを MySQL に効率的にインポートする方法の詳細な説明

MySQL には、複数の .sql ファイル (SQL ステートメントを含む) をインポートする方法...

Vue3コンポーネントの開発詳細

目次1. はじめに2. コンポーネント開発1. コンポーネントの構成2. ヘッダーコンポーネントの開...

MySQL フルテキスト検索の中国語ソリューションとサンプルコード

MySQL 全文検索中国語ソリューション最近、会社のプロジェクトで、データベースで中国語を検索する機...

Element PlusはAffixを実装します

目次1. コンポーネントの紹介2. ソースコード分析2.1 テンプレート2.2 スクリプト2.3 実...

W3C チュートリアル (10): W3C XQuery アクティビティ

XQuery は、XML ドキュメントからデータを抽出するための言語です。 XQuery は、XML...

画像の盗難を防ぐために Nginx で Referer を設定する方法

サーバーの画像が他のウェブサイトからホットリンクされると、サーバーの帯域幅とアクセス速度に影響します...

JavaScript の for ループと二重 for ループの詳細な説明

forループfor ループは配列の要素をループします。文法: for (初期化変数; 条件式; 繰り...

MySQL の文字セット utf8 を utf8mb4 に変更する方法

MySQL 5.5 の場合、文字セットが設定されていない場合、MySQL のデフォルトの文字セットは...