本文将深入剖析 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/全局,而 window/全局没有 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(); // 1
counter1(); // 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 John
people.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 请求,实现同步编程方式。
题目:实现一个 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 节点,设置回调函数,解析参数拼接 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)
分析:递归实现对象与数组的深拷贝,基本类型直接返回,引用类型递归层层调用深拷贝。
题目:实现一个 add 函数,可以实现 1+2+3 的相加:
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 10
var b = add(1, 2, 3, 4); // f 10
var c = add(1, 2)(3, 4); // f 10
var d = add(1, 2, 3)(4); // f 10
// You can use the characteristics of implicit conversion to participate in calculations
console.log(a + 10); // 20
console.log(b + 20); // 30
console.log(c + 30); // 40
console.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); // 120
console.log(b(10) + 100); // 120
console.log(c(10) + 100); // 120
console.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 操作符也存在一些限制,如:
因此,上述代码提供了一个更一般的 instanceof 函数,可以判断任意两个对象之间的关系。
该函数的实现原理是:
这个函数可用于以下场景:
题目:实现一个防抖函数
答案:
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);
}
}
}
// use
const debouncedFn = debounce(fn, 1000);
const cancel = debouncedFn();
// clear
cancel();
一定要 点赞 并 关注 我哟️