フロントエンドの面接では、手作業によるコードの分解は当然避けられず、大きな割合を占めます。 プログラミングに関する質問は、主に以下の種類に分けられます。
最初の 2 つのタイプが最大の割合を占めます。 この記事では、主に第 2 のタイプのさまざまな焦点を絞った手書き文字について説明します。 推奨される優先事項:
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.配列のマップメソッドを実装する配列の 使用法: 定数a = [1, 2, 3, 4]; 定数 b = array1.map(x => x * 2); console.log(b); // 配列 [2, 4, 6, 8] 実装する前に、map メソッドのパラメータを見てみましょう。 ネイティブ実装: // 実装 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メソッドを実装する配列の組み込み 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(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]]とします。 実はもっと簡単な方法があります。配列の次元を知らなくても、対象配列を直接 1 次元配列に変換できます。深さの値は Infinity に設定されています。 a = [1,[2,3,[4,[5]]]]とします。 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] 補足:ディープフラットを指定 再帰するたびに現在の 関数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 よく使用されるツール ライブラリ たとえば、プレースホルダーを渡すと、この呼び出しで渡されるパラメーターはプレースホルダーをスキップし、プレースホルダーには次の呼び出しのパラメーターが次のように入力されます。 公式サイトの例を見てみましょう。 次にプレースホルダー機能の実装方法を考えてみましょう。 私たちが独自に実装した 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 今のところ 7. 浅いコピーと深いコピーの実装ディープ コピーとシャロー コピーは、 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 を代入します。 もちろん、この方法はオブジェクト型にのみ適しています。配列の場合は、 (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形式に変換して解析する
方法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) // {} 生成されたインスタンスは空のオブジェクトです
関数 thovinoEat (...innerArgs) { eat.call(thovino, ...外側の引数, ...内側の引数) } new 演算子が 3 番目のステップ 言い換えれば、私たちが望んでいるのは、 new 演算子が 新しいバージョンと継承可能なバージョン 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. 手動で新しいものを実装する新しいプロセスのテキスト説明:
関数 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ステップ:
しかし、歴史が進むにつれて、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. 手書きのスロットルと手ぶれ防止機能関数スロットリングと関数アンチシェイクは、どちらも関数の実行頻度を制限することを目的としています。これらは、 例えば、(連続的な動きを呼び出す必要がある場合に使用し、時間間隔を設定します)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 に変更できるのでしょうか? 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 回の実行後のイベントのアンバインド、およびイベント リスナーのトリガーに対応する、 クラス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をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
<<: MySQL マスタースレーブレプリケーションのいくつかのレプリケーション方法の概要
>>: Nginx プロキシを使用してフロントエンドのクロスドメイン問題を解決する方法
序文スタンドアロン ロックであっても分散ロックであっても、共有データに基づいて現在の操作の動作を判断...
この記事では、参考までに、NFC読み取り機能を実装するためのuni-appの具体的なコードを紹介しま...
ブラウザウィンドウの中央に要素を配置する方法まず、コード ブロックを示します。すでにコードを理解して...
【質問】外側のテーブルと内側のテーブルがネストされていて、内側のテーブルと外側のテーブルの両方に境界...
目次前面に書かれた予防開発環境構築開発構成に関する注意事項前面に書かれたuni-app は、Vue....
現在、多くの人がインターネット上で生活しており、インターネットで情報を検索することは日常的な作業とな...
Linux 初心者から Linux マスターへの成長の道: Linux システム ディレクトリ s...
MySQL には、複数の .sql ファイル (SQL ステートメントを含む) をインポートする方法...
目次1. はじめに2. コンポーネント開発1. コンポーネントの構成2. ヘッダーコンポーネントの開...
MySQL 全文検索中国語ソリューション最近、会社のプロジェクトで、データベースで中国語を検索する機...
目次1. コンポーネントの紹介2. ソースコード分析2.1 テンプレート2.2 スクリプト2.3 実...
XQuery は、XML ドキュメントからデータを抽出するための言語です。 XQuery は、XML...
サーバーの画像が他のウェブサイトからホットリンクされると、サーバーの帯域幅とアクセス速度に影響します...
forループfor ループは配列の要素をループします。文法: for (初期化変数; 条件式; 繰り...
MySQL 5.5 の場合、文字セットが設定されていない場合、MySQL のデフォルトの文字セットは...