JavaScript知识手写题
实现一个自己的new操作符
new 操作符用于创建一个实例对象,它执行了以下步骤:
创建一个新的空对象
:new 操作符首先会创建一个新的空对象。设置对象的原型链接
:新创建的对象的 [[Prototype]](隐式原型)被设置为构造函数的 prototype 属性。将构造函数的上下文设置为新对象
:在调用构造函数时,将 this 关键字绑定到新创建的对象,使构造函数内部的代码可以操作新对象。执行构造函数的代码
:构造函数内部的代码被执行,可能会在新对象上设置属性和方法。如果构造函数没有返回对象,则返回新对象
:如果构造函数没有显式返回一个对象,则 new 操作符会自动返回新创建的对象;如果构造函数返回一个非对象的值,则不会影响返回结果。
自己实现代码
//定义一个构造函数
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function () {
console.log('My name is' + this.name)
}
function myNew(constructor, ...args) {
// 创建一个新对象,使其__proto__指向构造函数的prototype
const newObject = Object.create(constructor.prototype)
// 执行构造函数,将属性或方法添加到创建的空对象上
const result = constructor.apply(newObject, args)
// 如果构造函数返回一个对象,则返回该对象,否则返回创建的新对象
return (typeof result === 'object' && result !== null) ? result : newObject
}
const p1 = myNew(Person, 'Tom', 18)
console.log(p1)
实现一个防抖函数
防抖函数可以帮助你限制高频率触发的事件,确保只有在一定时间间隔内没有新的触发时,才会执行特定的操作
// 创建一个防抖函数
function debounce(fn, delay) {
// 定时器
let timer = null
// 返回一个函数,这个函数会在一个时间区间结束后的 delay 毫秒时执行 fn 函数
return (...args) => {
// 每次这个返回的函数被调用,就清除定时器,以保证不执行 fn
clearTimeout(timer)
// 当返回的函数被最后一次调用后(也就是用户停止了某个连续的操作),再过 delay 毫秒就执行 fn
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
手写节流函数
在一些频繁触发的事件中,执行某个函数,会导致性能问题。节流函数可以帮助我们使频繁触发的事件时需要执行的函数,在指定时间间隔内只触发一次。
// 创建一个节流函数
function throttle(fn, delay) {
// 记录上一次函数触发的时间
var lastTime = 0
return (...args) => {
// 记录当前函数触发的时间
var nowTime = Date.now()
// 用当前时间减去上一次函数触发的时间,结果大于设置的时间间隔
// 第一次会立即执行
if (nowTime - lastTime > delay) {
// 修正this指向问题
fn.apply(this, args)
// 同步时间
lastTime = nowTime
}
}
}
// 处理函数
function handle(value1, value2) {
console.log(value1, value2)
}
// 鼠标移动事件
window.addEventListener('mousemove', throttle(handle.bind(this,'abc', 123), 1000))
实现一个深拷贝,并解决循环引用问题?
深拷贝是在创建一个对象的副本时,递归地复制其所有嵌套属性和子对象。在深拷贝过程中,如果对象存在循环引用,即某个对象引用了自身或与其他对象形成了循环引用关系,那么简单的递归拷贝可能会导致无限循环,甚至造成内存溢出。为了解决循环引用问题,你可以在深拷贝过程中使用一些策略。以下是一种常见的方法:
- 使用一个缓存对象来跟踪已经被拷贝的对象,以及它们在新对象中的引用。这可以防止无限递归。
- 在每次拷贝一个对象之前,先检查缓存对象,如果该对象已经被拷贝,则直接返回缓存中的引用,而不是递归拷贝。
function deepCopyWithCycles(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (cache.has(obj)) {
return cache.get(obj);
}
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
const copy = Array.isArray(obj) ? [] : {};
cache.set(obj, copy);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopyWithCycles(obj[key], cache);
}
}
return copy;
}
const objA = {
name: 'Object A'
};
const objB = {
name: 'Object B'
};
objA.circularRef = objA;
objB.circularRef = objA;
const copiedObjA = deepCopyWithCycles(objA);
console.log(copiedObjA.name); // 输出: Object A
console.log(copiedObjA.circularRef.name); // 输出: Object A
console.log(copiedObjA.circularRef === copiedObjA); // 输出: true
console.log(copiedObjA.circularRef === copiedObjA.circularRef.circularRef); // 输出: true
在上面的示例中,deepCopyWithCycles 函数使用了一个 WeakMap 来缓存已经拷贝的对象,以及它们在新对象中的引用。这样,即使对象存在循环引用,也可以正确地处理。同时,这个函数也考虑了处理特殊类型如 Date 和 RegExp。
实现自己的call,apply,bind函数
- call 和 apply 是用于立即执行函数并传递参数的方法,其中 apply 接受参数的形式是数组。
- bind 是用于创建一个新函数,该函数会在稍后的调用中使用绑定的上下文。
实现call函数
Function.prototype.myCall = function(context, ...args) {
context = context || window // 如果未传入上下文,则使用全局对象
const uniqueKey = Symbol() // 使用一个唯一的键来避免覆盖原有属性
context[uniqueKey] = this; // 将当前函数设置为上下文的属性
const result = context[uniqueKey](...args) // 调用函数并传递参数
delete context[uniqueKey] // 删除添加的属性
return result // 返回函数执行的结果
}
function greet(name) {
console.log(`Hello, ${name}! I am ${this.name}.`)
}
const person = { name: "Alice" }
greet.myCall(person, "Bob") // 输出:Hello, Bob! I am Alice.
实现apply函数
Function.prototype.myApply = function(context, args) {
context = context || window;
const uniqueKey = Symbol();
context[uniqueKey] = this;
const result = context[uniqueKey](...args);
delete context[uniqueKey];
return result;
};
function greet(name) {
console.log(`Hello, ${name}! I am ${this.name}.`);
}
const person = { name: "Alice" };
greet.myApply(person, ["Bob"]); // 输出:Hello, Bob! I am Alice.
实现bind函数
Function.prototype.myBind = function(context, ...args1) {
const originalFunction = this;
return function(...args2) {
context = context || window;
const combinedArgs = args1.concat(args2); // 合并传入的参数
return originalFunction.apply(context, combinedArgs);
};
};
function greet(name) {
console.log(`Hello, ${name}! I am ${this.name}.`);
}
const person = { name: "Alice" };
const boundGreet = greet.myBind(person);
boundGreet("Bob"); // 输出:Hello, Bob! I am Alice.
实现一个使数组乱序的函数
你可以使用 Fisher-Yates 洗牌算法来实现一个将数组乱序的函数。这个算法通过遍历数组,不断地交换当前元素与之前位置的随机元素来打乱数组的顺序。
function shuffleArray(array) {
const shuffledArray = [...array]; // 创建一个数组的副本,以保持原始数组不受影响
const n = shuffledArray.length;
for (let i = n - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); // 生成一个随机索引
[shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]]; // 交换元素
}
return shuffledArray;
}
const originalArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const shuffledArray = shuffleArray(originalArray);
console.log(shuffledArray); // 输出一个乱序的数组
在上面的示例中,shuffleArray 函数接受一个数组作为参数,并返回一个经过洗牌的新数组。该函数使用了 Fisher-Yates 洗牌算法,通过循环遍历数组,不断地将当前元素与随机位置的元素进行交换,从而打乱数组的顺序。
实现一个数组去重的方法
思路:创建一个空数组,循环原数组,依次将原数组元素push到新数组中,push之前判断新数组中是否包含当前元素,若包含则不用push
function uniqueArray(array) {
const unique = [];
for (const item of array) {
if (!unique.includes(item)) {
unique.push(item);
}
}
return unique;
}
const originalArray = [1, 2, 2, 3, 4, 4, 5];
const uniqueArrayResult = uniqueArray(originalArray);
console.log(uniqueArrayResult); // 输出: [1, 2, 3, 4, 5]
实现一个数组降维的方法
递归方法
function flattenArrayRecursive(arr) {
let result = [];
arr.forEach(item => {
if (Array.isArray(item)) {
result = result.concat(flattenArrayRecursive(item));
} else {
result.push(item);
}
});
return result;
}
const nestedArray = [1, [2, [3, 4], 5], 6];
const flattenedArray = flattenArrayRecursive(nestedArray);
console.log(flattenedArray); // 输出: [1, 2, 3, 4, 5, 6]
迭代方法
function flattenArrayIterative(arr) {
const stack = [...arr];
const result = [];
while (stack.length) {
const next = stack.pop();
if (Array.isArray(next)) {
stack.push(...next);
} else {
result.unshift(next);
}
}
return result;
}
const nestedArray = [1, [2, [3, 4], 5], 6];
const flattenedArray = flattenArrayIterative(nestedArray);
console.log(flattenedArray); // 输出: [1, 2, 3, 4, 5, 6]
实现数组的push,filter,map方法
实现自己的 push 方法
Array.prototype.myPush = function(...items) {
const length = this.length;
for (let i = 0; i < items.length; i++) {
this[length + i] = items[i];
}
return this.length;
};
const arr = [1, 2, 3];
arr.myPush(4, 5);
console.log(arr); // 输出: [1, 2, 3, 4, 5]
实现自己的 filter 方法
Array.prototype.myFilter = function(callback) {
const filteredArray = [];
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) {
filteredArray.push(this[i]);
}
}
return filteredArray;
};
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.myFilter(num => num % 2 === 0);
console.log(evenNumbers); // 输出: [2, 4]
实现自己的 map 方法
Array.prototype.myMap = function(callback) {
const mappedArray = [];
for (let i = 0; i < this.length; i++) {
mappedArray.push(callback(this[i], i, this));
}
return mappedArray;
};
const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = numbers.myMap(num => num * num);
console.log(squaredNumbers); // 输出: [1, 4, 9, 16, 25]
实现‘观察者模式’和‘发布订阅模式’
实现一个eventEmitter方法
实现一个简单的redux
实现个自己的Object.create函数
Object.create() 方法用于创建一个新对象,其中新对象的原型链被设置为指定的原型对象
function myCreate(proto) {
if (proto === null || typeof proto !== 'object') {
throw new TypeError('Object prototype may only be an Object or null');
}
function F() {}
F.prototype = proto;
return new F();
}
const personPrototype = {
greet: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
const person = myCreate(personPrototype);
person.name = 'Alice';
person.greet(); // 输出: Hello, my name is Alice
在上面的示例中,myCreate 函数模拟了 Object.create() 方法的行为。它接受一个原型对象作为参数,并创建一个新函数 F,将其原型设置为传入的原型对象。然后通过 new F() 创建一个新对象,使其原型链继承传入的原型对象。
实现一个自己的instanceof方法
instanceof 操作符用于检查一个对象是否是某个构造函数的实例
function myInstanceOf(obj, constructor) {
if (typeof constructor !== 'function') {
throw new TypeError('Right-hand side of instanceof is not callable');
}
let proto = Object.getPrototypeOf(obj);
while (proto !== null) {
if (proto === constructor.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
function Person(name) {
this.name = name;
}
const person = new Person('Alice');
console.log(myInstanceOf(person, Person)); // 输出: true
console.log(myInstanceOf(person, Object)); // 输出: true
console.log(myInstanceOf(person, Array)); // 输出: false
在上面的示例中,myInstanceOf 函数模拟了 instanceof 操作符的行为。它接受一个对象和一个构造函数作为参数,然后通过递归地遍历对象的原型链,检查是否存在与构造函数的原型相等的原型。如果找到匹配的原型,就返回 true,否则返回 false。
实现一个自己的Promise函数
实现一个完整的 Promise 函数涉及到异步操作、状态管理(pending、fulfilled、rejected)、then 方法链式调用等等
function MyPromise(executor) {
// 初始状态为 pending
this.status = 'pending';
// 用于存储 resolve 和 reject 时的值
this.value = undefined;
// 存储 then 方法中的回调函数
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(callback => callback(this.value));
}
};
const reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected';
this.value = reason;
this.onRejectedCallbacks.forEach(callback => callback(this.value));
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
if (this.status === 'fulfilled') {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
});
}
if (this.status === 'rejected') {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
const result = onRejected(this.value);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
});
}
if (this.status === 'pending') {
return new MyPromise((resolve, reject) => {
this.onFulfilledCallbacks.push((value) => {
setTimeout(() => {
try {
const result = onFulfilled(value);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push((reason) => {
setTimeout(() => {
try {
const result = onRejected(reason);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
});
});
}
};
// 示例使用
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('Promise resolved!');
}, 1000);
});
promise
.then(value => {
console.log(value); // 输出: Promise resolved!
return value + ' And then...';
})
.then(value => {
console.log(value); // 输出: Promise resolved! And then...
});
实现一个自己的Promise.all
Promise.all() 是一个用于并行执行多个 Promise 对象,并在所有 Promise 都成功(resolved)时返回一个包含所有结果的 Promise
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completedCount = 0;
promises.forEach((promise, index) => {
promise.then(
result => {
results[index] = result;
completedCount++;
if (completedCount === promises.length) {
resolve(results);
}
},
reason => {
reject(reason);
}
);
});
});
}
// 示例使用
const promise1 = new Promise(resolve => setTimeout(() => resolve('Promise 1'), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve('Promise 2'), 500));
const promise3 = new Promise(resolve => setTimeout(() => resolve('Promise 3'), 800));
myPromiseAll([promise1, promise2, promise3])
.then(results => {
console.log(results); // 输出: ['Promise 1', 'Promise 2', 'Promise 3']
})
.catch(error => {
console.error(error);
});
myPromiseAll 函数接受一个 Promise 数组作为参数,并返回一个新的 Promise。它通过遍历每个 Promise 对象,在每个 Promise 成功时将结果存储在 results 数组中,当所有 Promise 都成功时,将 results 数组传递给新的 Promise 的 resolve 方法。
实现一个自己的Promise.race
Promise.race() 方法用于返回一个 Promise,它将在传入的多个 Promise 中有一个被 resolved 或 rejected 时,立即返回该 Promise 的状态和值
function myPromiseRace(promises) {
return new Promise((resolve, reject) => {
promises.forEach(promise => {
promise.then(
result => {
resolve(result);
},
reason => {
reject(reason);
}
);
});
});
}
// 示例使用
const promise1 = new Promise(resolve => setTimeout(() => resolve('Promise 1'), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve('Promise 2'), 500));
const promise3 = new Promise((resolve, reject) => setTimeout(() => reject('Promise 3 Error'), 800));
myPromiseRace([promise1, promise2, promise3])
.then(result => {
console.log(result); // 输出: Promise 2
})
.catch(error => {
console.error(error); // 不会执行,因为第一个 resolved 的 Promise 是 promise2
});
myPromiseRace 函数接受一个 Promise 数组作为参数,并返回一个新的 Promise。它遍历每个 Promise 对象,当其中一个 Promise 被 resolved 或 rejected 时,即立即将状态和值传递给新的 Promise 的 resolve 或 reject 方法
实现一个完整的Ajax请求
实现一个完整的 AJAX 请求涉及创建 XMLHttpRequest 对象、设置请求参数、处理响应等多个步骤
function makeAjaxRequest(method, url, data, successCallback, errorCallback) {
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
successCallback(xhr.responseText);
} else {
errorCallback(xhr.statusText);
}
}
};
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Accept', 'application/json');
if (method === 'GET') {
xhr.send();
} else {
xhr.send(JSON.stringify(data));
}
}
// 示例使用
const apiUrl = 'https://jsonplaceholder.typicode.com/posts/1';
makeAjaxRequest('GET', apiUrl, null,
function(response) {
const data = JSON.parse(response);
console.log('Success:', data);
},
function(error) {
console.error('Error:', error);
}
);
makeAjaxRequest 函数封装了一个 AJAX 请求的基本流程。它接受请求的方法、URL、数据、成功回调函数和错误回调函数作为参数。内部创建了一个 XMLHttpRequest 对象,并设置了请求方法、URL、请求头等。在 onreadystatechange 事件中,检查请求状态和响应状态,根据情况调用相应的回调函数。
实现使用Promise封装Ajax请求
使用 Promise 封装 AJAX 请求可以让异步操作更加清晰和可读
function makeAjaxRequest(method, url, data) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(xhr.statusText);
}
}
};
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Accept', 'application/json');
if (method === 'GET') {
xhr.send();
} else {
xhr.send(JSON.stringify(data));
}
});
}
// 示例使用
const apiUrl = 'https://jsonplaceholder.typicode.com/posts/1';
makeAjaxRequest('GET', apiUrl)
.then(response => {
const data = JSON.parse(response);
console.log('Success:', data);
})
.catch(error => {
console.error('Error:', error);
});
makeAjaxRequest 函数返回一个 Promise 对象。在 Promise 的构造函数中,执行异步的 AJAX 请求操作,当请求成功时调用 resolve,当请求失败时调用 reject。
实现一个字符串翻转的方法
使用数组的 reverse 方法
function reverseString(str) {
return str.split('').reverse().join('');
}
const originalStr = 'Hello, world!';
const reversedStr = reverseString(originalStr);
console.log(reversedStr); // 输出: "!dlrow ,olleH"
使用循环迭代
function reverseString(str) {
let reversed = '';
for (let i = str.length - 1; i >= 0; i--) {
reversed += str[i];
}
return reversed;
}
const originalStr = 'Hello, world!';
const reversedStr = reverseString(originalStr);
console.log(reversedStr); // 输出: "!dlrow ,olleH"
使用递归
function reverseString(str) {
if (str === '') {
return '';
}
return reverseString(str.substr(1)) + str[0];
}
const originalStr = 'Hello, world!';
const reversedStr = reverseString(originalStr);
console.log(reversedStr); // 输出: "!dlrow ,olleH"
实现一个将数字每千分位用逗号隔开的方法(考虑有小数点)
你可以使用 JavaScript 提供的一些内置函数来实现将数字每千分位用逗号隔开的功能
function formatNumberWithCommas(number) {
const parts = number.toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return parts.join('.');
}
const number1 = 1234567;
const formattedNumber1 = formatNumberWithCommas(number1);
console.log(formattedNumber1); // 输出: "1,234,567"
const number2 = 1234567.89;
const formattedNumber2 = formatNumberWithCommas(number2);
console.log(formattedNumber2); // 输出: "1,234,567.89"
实现函数柯里化add(1)(2)(3)
function add(x) {
return function(y) {
if (typeof y === 'undefined') {
return x;
}
return add(x + y);
};
}
console.log(add(1)(2)(3)); // 输出: 6
实现一个查找文章中出现频率最高的单词的方法
我们按照每个单词之间使用单个空格隔开的规则实现,并排除常用介词,处理标点符号
function findMostFrequentWord(article) {
const stopWords = ["a", "an", "and", "the", "in", "on", "of", "for", "to", "with", "it", "this", "that"];
const words = article.toLowerCase().split(/\s+/);
const wordCounts = {};
words.forEach(word => {
// 排除停用词和处理标点符号
const cleanWord = word.replace(/[.,!?'"()]/g, '');
if (cleanWord && !stopWords.includes(cleanWord)) {
if (cleanWord in wordCounts) {
wordCounts[cleanWord]++;
} else {
wordCounts[cleanWord] = 1;
}
}
});
let mostFrequentWord = '';
let maxCount = 0;
for (const word in wordCounts) {
if (wordCounts[word] > maxCount) {
mostFrequentWord = word;
maxCount = wordCounts[word];
}
}
return mostFrequentWord;
}
const articleText = "This is a sample text. It's a simple text for demonstration purposes. Simple text is good.";
const mostFrequent = findMostFrequentWord(articleText);
console.log("Most frequent word:", mostFrequent); // 输出: "text"
实现一个简单的双向绑定
使用Object.defineProperty
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input id="inputElement" type="text">
<div id="outputElement"></div>
</body>
</html>
<script>
let state = {}
const inputElement = document.getElementById('inputElement')
const outputElement = document.getElementById('outputElement')
Object.defineProperty(state, 'value', {
configurable: true,
enumerable: true,
get: () => {
console.log('get value')
},
set: (newValue) => {
console.log('set value: ', newValue)
inputElement.value = newValue
outputElement.innerHTML = newValue
}
})
inputElement.addEventListener('keyup', (event) => {
state.value = event.target.value
})
</script>
使用proxy
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input id="inputElement" type="text">
<div id="outputElement"></div>
</body>
</html>
<script>
let state = {}
const inputElement = document.getElementById('inputElement')
const outputElement = document.getElementById('outputElement')
const handler = {
get: (target, key) => {
return target[key]
},
set: (target, key, value) => {
target[key] = value
inputElement.value = value
outputElement.innerHTML = value
return true
}
}
const proxy = new Proxy(state, handler)
inputElement.addEventListener('keyup', (event) => {
proxy.value = event.target.value
})
</script>
实现一个类的继承
class Animal {
constructor(name) {
this.name = name;
}
sayName() {
console.log(`My name is ${this.name}`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用基类构造函数以继承属性
this.breed = breed;
}
bark() {
console.log('Woof woof!');
}
}
// 示例使用
const dog = new Dog('Buddy', 'Golden Retriever');
dog.sayName(); // 输出: "My name is Buddy"
dog.bark(); // 输出: "Woof woof!"