写在前面
文末有我在前端面试多年的经验文章,分享给大家!!!
防抖
// 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
// 防抖(debounce)函数是指在一定时间内,事件被触发多次,只执行最后一次。这在处理诸如输入框实时搜索等场景时非常有用,避免了频繁触发事件导致性能问题。
const debounce = (Fn, wait) => {
let timer;
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => {
Fn.apply(this,args)
},wait)
}
}
// 使用示例
const handleSearch = event => {
console.log("search:", event.target.value);
};
const debouncedSearch = debounce(handleSearch, 300);
节流
// 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
// 适用场景:
// 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
// 缩放场景:监控浏览器resize
// 动画场景:避免短时间内多次触发动画引起性能问题
const throttle = (Fn, wait = 500) => {
let lastTime = 0
return function (...args) {
let now = +new Date()
if (now - lastTime > wait) {
lastTime = now
Fn.apply(this,args)
}
}
}
const fn = throttle(() => {
console.log(3);
}, 3000)
setInterval(fn, 2000)
Instanceof
function myInstanceof(left, right) {
// 获取 right 的 prototype
let prototype = right.prototype;
// 获取 left 的 __proto__
let proto = left.__proto__;
// 遍历原型链,直到找到相同的原型或到达 null
while (proto !== null) {
if (proto === prototype) {
return true;
}
proto = proto.__proto__;
}
// 如果没有找到相同的原型,则返回 false
return false;
}
// 示例
class Animal {}
class Dog extends Animal {}
const dog = new Dog();
console.log(myInstanceof(dog, Dog)); // true
console.log(myInstanceof(dog, Animal)); // true
console.log(myInstanceof(dog, Object)); // true
console.log(myInstanceof(dog, String)); // false
手写new
function myNew(constructor, ...args) {
// 1. 创建一个新的空对象
let obj = {};
// 2. 将这个新对象的 __proto__ 属性链接到构造函数的 prototype 对象
obj.__proto__ = constructor.prototype;
// 3. 将这个新对象作为 this 的上下文
// 使用 Function.prototype.call 或 Function.prototype.apply 调用构造函数
// 并将参数传递给它
let result = constructor.apply(obj, args);
// 4. 如果构造函数返回了一个对象,那么就返回这个对象;如果没有返回对象,那么就返回新创建的对象 obj。
return result instanceof Object ? result : obj;
}
// 使用示例
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
};
let person = myNew(Person, "Alice", 30);
person.greet(); // 输出: Hello, my name is Alice and I'm 30 years old.
数组去重
var array = [1, 2, 1, 1, "1"];
// Array.from去重
function uniques(array) {
return Array.from(new Set(array));
}
// 简化
function SampleUniques(array) {
return [...new Set(array)];
}
console.log(uniques(array));
// 也可以使用es5中的indexOf方法
function es5Uniques(array) {
let res = array.filter(function (item, index, array) {
return array.indexOf(item) === index;
});
return res;
}
console.log("es5去重");
console.log(es5Uniques(array));
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = [];
const map = new Map();
for (const item of array) {
if (!map.has(item)) {
map.set(item, true);
uniqueArray.push(item);
}
}
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
深拷贝
深拷贝(Deep Copy)是指完全复制一个对象,包括其所有嵌套的子对象。深拷贝与浅拷贝(Shallow Copy)不同,浅拷贝只复制对象的第一层属性,对于嵌套的对象仍然是引用。
在JavaScript中,有多种方法可以实现深拷贝。以下是几种常见的方法:
方法一:使用 JSON.parse
和 JSON.stringify
这是最简单的方法,但它有一些限制,比如不能处理函数、undefined
、Symbol
、循环引用等。
const obj = {a: 1,b: {c: 2,d: 3
}
};
const deepCopy = JSON.parse(JSON.stringify(obj));
console.log(deepCopy); // 输出: { a: 1, b: { c: 2, d: 3 } }
function deepClone(obj) {
if(typeof obj != 'object' && obj != null){
return obj;
}
let result;
if(obj instanceof Array){
result = []
}else{
result = {};
}
for(let key in obj){
if(obj.hasOwnProperty(key)){
result[key] = deepClone(obj[key]);
}
}
return result;
}
拍平数组
const arr = [1, 2, [3, 4, [5, 6]]];
function flatten(arr) {
while (arr.some((item) => Array.isArray(item))) {
arr = arr.flatMap((item) => item);
}
return arr;
}
const flattened = flatten(arr); // [1, 2, 3, 4, 5, 6]
function flattenArray(arr) {
let result = [];
arr.forEach(item => {
if (Array.isArray(item)) {
result = result.concat(flattenArray(item));
} else {
result.push(item);
}
});
return result;
}
// 测试 flattenArray
const array = [1, [2, [3, [4, 5]]]];
const flatArray = flattenArray(array);
console.log(flatArray); // 输出: [1, 2, 3, 4, 5]
手写promiseAll
Promise.all
是一个非常有用的工具,它接受一个包含多个 Promise 的可迭代对象(通常是数组),并返回一个新的 Promise。这个新的 Promise 在所有输入的 Promise 都成功时解析,并且解析值是一个包含所有输入 Promise 解析值的数组。如果任何一个输入的 Promise 拒绝(reject),则返回的 Promise 也会立即拒绝,并且拒绝原因是第一个拒绝的 Promise 的原因。
function promiseAll(promises){
if(!Array.isArray(promises)){
throw new TypeError("promises must be an array")
}
return new Promise(function(resolve, reject){
// 数组长度
let promiseNum = promises.length;
// 成功的数量
let resolveCount = 0;
// 成功的值的数组
let resolveValues = new Array(promiseNum);
// 先遍历
for(let i=0; i<promiseNum; i++){
// 为什么不直接 promise[i].then, 因为promise[i]可能不是一个promise
Promise.resolve(promises[i]).then(function(value){
resolveValues[i] = value;
resolveCount++;
if(resolveCount == promiseNum){
return resolve(resolveValues)
}
},
function(err){
return reject(err);
}
)
}
})
}
手写promiseRace
Promise.race
是一个非常有用的工具,它接受一个包含多个 Promise 的可迭代对象(通常是数组),并返回一个新的 Promise。这个新的 Promise 在第一个输入的 Promise 解析或拒绝时解析或拒绝,并且解析值或拒绝原因是第一个完成的 Promise 的值或原因。
解释
- 输入验证:首先,我们检查传入的参数是否是一个数组。如果不是数组,我们立即拒绝返回的 Promise,并抛出一个
TypeError
。 - 遍历输入的 Promise:我们使用
for...of
遍历输入的 Promise 数组。对于每个 Promise,我们使用Promise.resolve
将其转换为一个标准的 Promise(以防输入的某个值本身不是一个 Promise)。 - 处理每个 Promise:
- 如果 Promise 成功解析,我们立即调用
resolve
,并传入解析值。 - 如果 Promise 拒绝,我们立即调用
reject
,并传入拒绝原因。
- 如果 Promise 成功解析,我们立即调用
注意事项
Promise.resolve(promise)
确保即使传入的值不是一个 Promise,也会被转换为一个 Promise。- 一旦有任何一个 Promise 解析或拒绝,我们立即解析或拒绝返回的 Promise,并传入相应的值或原因。
这个实现基本上覆盖了 Promise.race
的核心功能,可以处理大多数常见的使用场景。
function promiseRace(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'));
}
for (const promise of promises) {
Promise.resolve(promise)
.then(resolve)
.catch(reject);
}
});
}
// 测试 promiseRace
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
promiseRace([promise1, promise2])
.then(value => {
console.log(value); // 输出: 'two'
})
.catch(error => {
console.error(error);
});
快速排序
function QuickSort(arr) {
const n = arr.length;
if (n <= 1) return arr;
let pivot = arr[0];
let left = [];
let right = [];
for (let i = 1; i < n; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
// return QuickSort(left).concat([pivot], QuickSort(right));
return [...QuickSort(left), pivot, ...QuickSort(right)];
}
let list = [4, 6, 8, 5, 9, 1, 2, 3, 2];
let sortArr = QuickSort(list);
console.log("快速排序", sortArr);
在JavaScript中,apply
、call
和bind
是Function对象的三个方法,用于改变函数的执行上下文(即this
的指向)。我们可以使用ES6的特性来手动实现这些方法。以下是它们的实现:
实现 apply
分析:如何在函数执行时绑定this
- 如
var obj = {x:100,fn() { this.x }}
- 执行
obj.fn()
,此时fn
内部的this
就指向了obj
- 可借此来实现函数绑定
this
apply
方法调用一个函数,并指定this
和参数数组。
Function.prototype.myApply = function(context, args) {
context = context || globalThis; // globalThis 是 ES2020 引入的全局对象
const fnSymbol = Symbol(); // 使用 Symbol 防止属性冲突
context[fnSymbol] = this; // this 指向当前函数
const result = context[fnSymbol](...args); // 展开参数数组并调用函数
delete context[fnSymbol]; // 删除临时属性
return result;
};
// 测试 myApply
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const person = { name: 'Alice' };
console.log(greet.myApply(person, ['Hello', '!'])); // 输出: "Hello, Alice!"
实现 call
call
方法与apply
类似,但它接受的是参数列表而不是参数数组。
Function.prototype.myCall = function(context, ...args) {
context = context || globalThis;
const fnSymbol = Symbol();
context[fnSymbol] = this;
const result = context[fnSymbol](...args);
delete context[fnSymbol];
return result;
};
// 测试 myCall
console.log(greet.myCall(person, 'Hi', '?')); // 输出: "Hi, Alice?"
实现 bind
bind
方法返回一个新的函数,该函数在调用时将this
设置为提供的值,并在调用新函数时预设一定数量的参数。
Function.prototype.myBind = function(context, ...args) {
const fn = this;
return function(...newArgs) {
return fn.apply(context, [...args, ...newArgs]);
};
};
// 测试 myBind
const boundGreet = greet.myBind(person, 'Hey');
console.log(boundGreet('!!!')); // 输出: "Hey, Alice!!!"
好的,让我们一步一步地实现Object.create()
和Object.assign()
,并在代码中添加注释进行解释。
实现 Object.create()
首先,我们来实现 Object.create()
。这个方法需要创建一个新对象,并将其原型设置为指定的 proto
,同时可以选择性地定义一些属性。
Object.myCreate = function (proto, defineProperties) {
// 检查 proto 是否是对象或函数
if ((typeof proto === 'object' && proto !== null) || typeof proto === 'function') {
// 创建一个空对象
let obj = {};
// 设置新对象的原型为 proto
Object.setPrototypeOf(obj, proto);
// 如果 defineProperties 存在,使用 Object.defineProperties 定义属性
if (defineProperties !== undefined) {
Object.defineProperties(obj, defineProperties);
}
// 返回新对象
return obj;
} else {
// 如果 proto 不是对象或函数,抛出类型错误
throw new TypeError('类型错误');
}
}
类型检查:首先检查 proto
是否是对象或函数。如果不是,抛出类型错误。
创建空对象:使用字面量 {}
创建一个空对象。
设置原型:使用 Object.setPrototypeOf(obj, proto)
将新对象的原型设置为 proto
。
定义属性:如果 defineProperties
存在,使用 Object.defineProperties(obj, defineProperties)
定义新对象的属性。
返回新对象:返回新创建的对象。
实现 Object.assign()
接下来,我们来实现 Object.assign()
。这个方法需要将一个或多个源对象的可枚举属性复制到目标对象中。
Object.myAssign = function (target, ...sources) {
// 检查 target 是否是对象或函数
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
// 将 target 转换为对象
let to = Object(target);
// 遍历每个源对象
for (let i = 0; i < sources.length; i++) {
let nextSource = sources[i];
// 如果源对象不为 null 或 undefined
if (nextSource != null) {
// 遍历源对象的所有可枚举属性
for (let key in nextSource) {
// 只复制源对象自身的属性
if (Object.prototype.hasOwnProperty.call(nextSource, key)) {
to[key] = nextSource[key];
}
}
}
}
// 返回目标对象
return to;
}
类型检查:首先检查 target
是否为 null
或 undefined
。如果是,抛出类型错误。
转换为对象:使用 Object(target)
将 target
转换为对象。
遍历源对象:使用 for
循环遍历每个源对象。
检查源对象:如果源对象不为 null
或 undefined
,继续处理。
遍历属性:使用 for...in
循环遍历源对象的所有可枚举属性。
复制属性:使用 Object.prototype.hasOwnProperty.call(nextSource, key)
检查属性是否为源对象自身的属性。如果是,将其复制到目标对象 to
中。
返回目标对象:返回目标对象 to
接下来给大家推荐一篇我在前端面试多年的经验文章,希望大家看完以后都可以领取到心仪的offer哦!
文章:《聊聊前端面试那些事儿》
标签:function,const,对象,return,面试,算法,Promise,obj,大全 From: https://blog.csdn.net/2401_84489283/article/details/141021124