今天我將帶你深入了解 14 個常見的 JavaScript 高級面試問題。這些問題涵蓋了 JavaScript 的面向對象、事件循環機制、Promise 等高級概念,以及函數柯里化和深度復制等實用技術。
我們不僅從概念層面對每個問題進行了分析,還提供了具體的代碼實現。
那我們現在就開始吧。
this關鍵字指向當前執行上下文中的一個對象。在函數中,this關鍵字通常指向函數的調用者。
問題:以下代碼輸出什么?為什么?
const obj = { name: 'obj', getName: function() { return function() { return this.name; } }}const fn = obj.getName();fn();
答案:undefined
分析:因為getName函數內部是在全局作用域內執行的,這里的this指向window/global,而window/global沒有name屬性,所以返回undefined。
如果想讓內部函數的this也指向obj,可以使用箭頭函數或者bind綁定this:
const obj = { name: 'obj', getName: function() { return () => { return this.name; } }}
問題:實現一個計數器工廠函數:
function createCounter() { let count = 0; return function() { return count++; }} const counter1 = createCounter();const counter2 = createCounter();counter1(); // 1counter1(); // 2 counter2(); // 1
解析:不同的計數器之所以能獨立遞增,是因為利用了閉包的特性。createCounter函數創建了一個閉包,在其外層作用域中可以訪問變量count,counter1和counter2引用不同的閉包函數實例,從而實現了計數獨立性。
問:對事件循環機制做一下解釋說明。
答:事件循環機制主要有以下幾個流程:
事件循環允許在同一個線程中交替執行同步任務和異步任務,充分利用CPU資源。這對于支持UI交互和響應性的JavaScript來說很重要。
問題:實現Promise的簡單版本:
Promise對象是處理異步事件的異步編程解決方案。Promise對象可以表示異步操作的狀態,包括:
Promise對象的實現如下:
class MyPromise { constructor(executor) { this._state = "pending"; this._value = undefined; this._reason = undefined; this._onFulfilledCallbacks = []; this._onRejectedCallbacks = []; executor(this.resolve.bind(this), this.reject.bind(this)); } resolve(value) { if (this._state !== "pending") { return; } this._state = "fulfilled"; this._value = value; setTimeout(() => { for (const callback of this._onFulfilledCallbacks) { callback(value); } }); } reject(reason) { if (this._state !== "pending") { return; } this._state = "rejected"; this._reason = reason; setTimeout(() => { for (const callback of this._onRejectedCallbacks) { callback(reason); } }); } then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { if (this._state === "pending") { this._onFulfilledCallbacks.push((value) => { setTimeout(() => { try { const result = onFulfilled(value); resolve(result); } catch (error) { reject(error); } }); }); this._onRejectedCallbacks.push((reason) => { setTimeout(() => { try { const result = onRejected(reason); resolve(result); } catch (error) { reject(error); } }); }); } else { setTimeout(() => { try { if (this._state === "fulfilled") { const result = onFulfilled(this._value); resolve(result); } else { const result = onRejected(this._reason); resolve(result); } } catch (error) { reject(error); } }); } }); } catch(onRejected) { return this.then(null, onRejected); } isFulfilled() { return this._state === "fulfilled"; } isRejected() { return this._state === "rejected"; }}
分析:
原型鏈是每個對象的一個屬性,它指向該對象構造函數的原型對象。一個構造函數的原型對象指向另一個構造函數的原型對象,依此類推。
問題:要實現一個 People 類,可以通過構造函數或 new 操作符實例化對象。同時,它有一個繼承 Person 類的方法。Person 類有一個 sayHi 方法:
class Person { constructor(name) { this.name = name; } sayHi() { console.log(`Hello ${this.name}`) }}class People extends Person { constructor(name) { super(name); } method() { console.log('people method') }}const people = new People('John')people.sayHi() // Hello Johnpeople.method() // people method
分析:通過構造函數調用super繼承屬性,原型鏈實現方法繼承。
問:簡述MVC與MVVM的概念和區別?
答:MVC模式中:
MVVM模式中:
區別在于:
問題:實現一個ajax請求函數:
ajax('/api/users', { method: 'GET' }).then(data => { console.log(data)})
回答:
function ajax(url, options) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); const method = options.method || 'GET'; const headers = options.headers || {}; const body = options.body || null; const timeout = options.timeout || 0; xhr.open(method, url); xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.response); } else { reject(new Error(xhr.statusText)); } }; xhr.onerror = () => reject(xhr.error); xhr.ontimeout = () => reject(new Error('Request timeout')); xhr.timeout = timeout; for (const [header, value] of Object.entries(headers)) { xhr.setRequestHeader(header, value); } xhr.send(body); });}
實現一個支持Promise的ajax請求函數:
onerror直接reject
分析:使用Promise封裝異步ajax請求,實現同步編程風格。
問題:實現一個JSONP跨域請求:
jsonp('/api/data', { params: { name: 'jsonp' } })
回答:
function jsonp(url, options) { return new Promise((resolve, reject) => { const script = document.createElement('script'); const callbackName = `jsonpCallback_${Date.now()}_${Math.floor(Math.random() * 10000)}`; const timer = setTimeout(() => { cleanup(); reject(new Error('JSONP request timeout')); }, options.timeout || 5000); function cleanup() { delete window[callbackName]; clearTimeout(timer); script.remove(); } window[callbackName] = function(data) { cleanup(); resolve(data); }; options.params = options.params || {}; options.params['callback'] = callbackName; const paramsArr = Object.keys(options.params).map(key => { return `${encodeURIComponent(key)}=${encodeURIComponent(options.params[key])}`; }); script.src = `${url}?${paramsArr.join('&')}`; script.onerror = () => { cleanup(); reject(new Error('JSONP request error')); }; document.body.appendChild(script); });}
分析:創建腳本節點script.src,設置回調函數callbackName,解析參數并拼接URL,動態插入body中實現JSONP跨域請求,返回Promise接口。
問題:實現一個函數deepClone,實現對象的深度克隆:
答案:
function deepClone(source, clonedMap) { clonedMap = clonedMap || new Map(); if (source === null || typeof source !== 'object') { return source; } if (clonedMap.has(source)) { return clonedMap.get(source); } var result; var type = getType(source); if (type === 'object' || type === 'array') { result = type === 'array' ? [] : {}; clonedMap.set(source, result); for (var key in source) { if (source.hasOwnProperty(key)) { result[key] = deepClone(source[key], clonedMap); } } } else { result = source; } return result;}function getType(source) { return Object.prototype.toString .call(source) .replace(/^/[object (.+)/]$/, '$1') .toLowerCase();}const obj = { a: 1, b: { c: 2 }}const clone = deepClone(obj)
分析:遞歸實現對象和數組的深度克隆,直接返回基本類型,引用類型遞歸分層調用深度克隆。
問題:實現一個可以把1+2+3加起來的add函數:
function add() { // When executing for the first time, define an array specifically to store all parameters var _args = [].slice.call(arguments); // Declare a function internally, use the characteristics of closure to save _args and collect all parameter values var adder = function () { var _adder = function() { // [].push.apply(_args, [].slice.call(arguments)); _args.push(...arguments); return _adder; }; //Using the characteristics of implicit conversion, implicit conversion is performed when it is finally executed, and the final value is calculated and returned. _adder.toString = function () { return _args.reduce(function (a, b) { return a + b; }); } return _adder; } // return adder.apply(null, _args); return adder(..._args);}var a = add(1)(2)(3)(4); // f 10var b = add(1, 2, 3, 4); // f 10var c = add(1, 2)(3, 4); // f 10var d = add(1, 2, 3)(4); // f 10// You can use the characteristics of implicit conversion to participate in calculationsconsole.log(a + 10); // 20console.log(b + 20); // 30console.log(c + 30); // 40console.log(d + 40); // 50// You can also continue to pass in parameters, and the result will be calculated using implicit conversion again.console.log(a(10) + 100); // 120console.log(b(10) + 100); // 120console.log(c(10) + 100); // 120console.log(d(10) + 100); // 120//In fact, the add method in Shangli is the curried function of the following function, but we do not use the general formula to convert, but encapsulate it ourselves.function add(...args) { return args.reduce((a, b) => a + b);}
分析:add函數的柯里化是通過遞歸調用一個不斷接受參數的函數來實現的。
問題:實現一個myAll方法,類似Promise.all:
myAll([ myPromise1, myPromise2]).then(([res1, res2]) => { //...})
回答:
function myAll(promises) { return new Promise((resolve, reject) => { const result = new Array(promises.length); let count = 0; promises.forEach((p, index) => { p.then(res => { result[index] = res; count++; if (count === promises.length) { resolve(result); } }) .catch(reject); }); });}
分析:利用Promise.all原理,通過計數器和結果數組同步Promise狀態。
問題:實現一個instanceof操作符
答案:
function instanceof(left, right) { if (arguments.length !== 2) { throw new Error("instanceof requires exactly two arguments."); } if (left === null) { return false; } if (typeof left !== "object") { return false; } let proto = Object.getPrototypeOf(left); while (proto !== null) { if (right.prototype === proto) { return true; } proto = Object.getPrototypeOf(proto); } return false;}
以上代碼用于判斷一個對象是否是另一個對象的實例。
JavaScript 中的 instanceof 運算符可用于判斷一個對象是否是另一個對象的實例。但是,instanceof 運算符有一些限制,例如:
該函數可以在以下場景中使用:
問題:實現 debounce 防抖功能
答案:
function debounce(fn, delay = 500) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); // Return to clear function return () => { clearTimeout(timer); } }}// useconst debouncedFn = debounce(fn, 1000);const cancel = debouncedFn();// clearcancel();
本文鏈接:http://www.tebozhan.com/showinfo-26-100982-0.html13個 JavaScript 面試難題及代碼實現
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com