【秋招准备】面试手撕大集合

【秋招准备】面试手撕大集合

Simon Lv1

所有遍历数组的方法

forEach

// 自定义 forEach 函数,添加到 Array 原型链
Array.prototype.myForEach = function (callback, thisArg) {

// 类型检查:确保 callback 是一个函数
if (typeof callback !== 'function') {
throw new TypeError('第一个参数必须是一个函数');
}

// 遍历数组,调用 callback 函数
for (let i = 0; i < this.length; i++) {
// 如果数组的当前元素是 undefined 或 null, 则跳过
if (i in this) {//对于数组而言,in运算符用于检查是否有这个索引值
callback.call(thisArg, this[i], i, this);
}
}
};

map

// 自定义 Map 函数,添加到 Array 原型链
Array.prototype.myMap = function (callback) {

// 类型检查:确保 callback 是一个函数
if (typeof callback !== 'function') {
throw new TypeError('第一个参数必须是一个函数');
}
let res = [];

// 遍历数组,调用 callback 函数
for (let i = 0; i < this.length; i++) {
// 如果数组的当前元素是 undefined 或 null, 则跳过
if (i in this) {
res.push(callback(this[i], i, this));
}
}
return res
};

Filter

Array.prototype.my_filter = function (cb) {
let newArr = []
for (var i = 0; i < this.length; i++) {
if (cb(this[i], i, this)) {
newArr.push(this[i])
}
}
return newArr
}

every(判断数组中的每一个元素是否都满足某个条件)

Array.prototype.my_every = function (cb) {
for (let i = 0; i < this.length; i++) {
if (!cb(this[i], i, this)) {
return false;
}
}
return true;
}

some(判断数组中是否存在一个元素满足某个条件)

Array.prototype.my_some = function (cb) {
for (let i = 0; i < this.length; i++) {
if (cb(this[i], i, this)) {
return true;
}
}
return false;
}

reduce

Array.prototype.my_reduce = function (cb, ...args) {
let start, index = 0
if (args.length) {
start = args[0]
} else {
start = this[0];
index = 1
}
for (let i = index; i < this.length; i++) {
start = cb(start, this[i], i, this);
}
return start;
}

includes

Array.prototype.my_includes = function (item, ...args) {
let index
if (args.length && !Number.isNaN(Number(args[0])) && Number(args[0])>0) {
index = Number(args[0])
} else {
index = 0;
}
for (let i = index; i < this.length; i++) {
if (this[i] === item) return true;
}
return false
}

splice

Array.prototype.mySplice = function (start, len, ...args) {
let newArr = [], resArr = [];
if (arguments.length === 0) return resArr;

if (typeof len === 'undefined') {
len = 0;
}
if (typeof start === 'undefined') {
start = 0;
} else if (start < 0) {
start = start + this.length;
start = start < 0 ? 0 : start;
} else if (start >= this.length) {
start = this.length;
}

for (let i = 0; i < this.length; ++i) {
if (i < start || i >= start + len) {
newArr.push(this[i]);
} else {
resArr.push(this[i]);
}
if (i === start || start + i === this.length * 2 - 1) {
newArr.push(...args);
}
}
while (this.length) {
this.pop();
}
this.push(...newArr);
return resArr;
}

JS内置方法、对象以及运算符

new运算符

function myNew (Func, ...arg){
if (fn.prototype === undefined) throw new TypeError('function is not a constructor');
let obj = {} //定义了一个对象。
obj.__proto__ = Func.prototype
//将Func.prototype赋值为对象的__proto__属性,即原型链的概念
let res = Func.call(obj, ...arg) //更改Func的this指向
return res instanceof Object ? res : obj
}

手撕curry

const my_curry = (fn, ...args) =>
args.length >= fn.length
? fn(...args)
: (...args1) => my_curry(fn, ...args, ...args1);

function adder(x, y, z) {
return x + y + z;
}
const add = my_curry(adder);
console.log(add(1, 2, 3)); //6
console.log(add(1)(2)(3)); //6
console.log(add(1, 2)(3)); //6
console.log(add(1)(2, 3)); //6

手写compose函数

const compose = (...fns)=>(x)=>fns.reduceRight((v, fn)=>fn(v),x);
const doubleAndSuare = compose(double, square);//先翻倍再平方
console.log(doubleAndSuare(3));

手撕vue3响应式代理

//测试代码
class Depend {
constructor() {
this.reactiveFns = new Set()//依赖组
}

// 更好的收集依赖
depend() {
if (activeReactiveFn) this.reactiveFns.add(activeReactiveFn)
}

//对所有依赖进行统一通知处理
notify() {
console.log(this.reactiveFns)
this.reactiveFns.forEach(fn => {
//遍历依赖处理
if (fn) fn()
})
}
}

// 封装响应式函数
let activeReactiveFn = null
function watchFn(fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}

// 封装一个获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
// 1、根据target对象获取map的过程
let map = targetMap.get(target)
if (!map) {
map = new Map()
targetMap.set(target, map)
}
// 2、根据对象属性获取depend依赖
let depend = map.get(key)
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}


//使用Proxy监听对象变化
function reactive(obj) {
return new Proxy(obj, {
get: function (target, key, receiver) {
const depend = getDepend(target, key)
depend.depend()
return Reflect.get(target, key, receiver)
},
set: function (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
const depend = getDepend(target, key)
depend.notify()
}
})
}

const obj = {
name: "coderwhy",//depend实例对象
age: 18//depend实例对象
}

const info = {
name:"小余",
age:18
}
//响应式开关媒介
const objRef = reactive({
name: "coderwhy",
age: 18
})
const infoRef = reactive({
name:"小余",
age:18
})

watchFn(() => {
console.log(infoRef.name);
})
watchFn(() => {
console.log(objRef.name);
})

infoRef.name = '响应式-小余'
objRef.name = '响应式-coderwhy'

// 小余
// coderwhy
// Set(1) { [Function (anonymous)] }
// 响应式-小余
// Set(1) { [Function (anonymous)] }
// 响应式-coderwhy

手撕instanceof

const myInstanceOf=(Left,Right)=>{
if(!Left){
return false
}
while(Left){
if(Left.__proto__===Right.prototype){
return true
}else{
Left=Left.__proto__
}
}
return false
}

//验证
console.log(myInstanceOf({},Array)); //false

call,bind,apply

function foo(x,y){
console.log(this.a,x+y);
}

const obj={
a:1
}

Function.prototype.myCall=function(context,...args){
if(typeof this !== 'function') return new TypeError('is not a function')
const fn = Symbol('fn') //使用Symbol尽可能降低myCall对其他的影响
context[fn] = this //this指向foo
const res = context[fn](...args) //解构,调用fn
delete context[fn] //不要忘了删除obj上的工具函数fn
return res //将结果返回
}

//验证
foo.myCall(obj,1,2) //1,3

function foo(x,y){
console.log(this.a,x+y);
}

const obj={
a:1
}

Function.prototype.myApply=function(context,args){
if(typeof this !== 'function') return new TypeError('is not a function')
const fn = Symbol('fn') //使用Symbol尽可能降低myCall对其他的影响
context[fn] = this //this指向foo
const res = context[fn](...args) //解构,调用fn
delete context[fn] //不要忘了删除obj上的工具函数fn
return res //将结果返回
}

//验证
foo.myApply(obj,1,2) //1,3

function foo(x,y,z){
this.name='zt'
console.log(this.a,x+y+z);
}

const obj={
a:1
}

Function.prototype.myBind=function(context,...args){
if(typeof this !== 'function') return new TypeError('It is not a function');
context = context || window; // 上下文环境
const _this = this; // 当前的函数的上下文this
return function F(...arg) {
//判断返回出去的F有没有被new,有就要把foo给到new出来的对象
if (this instanceof F) {
return new _this(...args, ...arg);
} else {
_this.call(this, ...args, ...arg);
}
}
}

//验证
const bar=foo.myBind(obj,1,2)
console.log(new bar(3)); //undefined 6 foo { name: 'zt' }

值相等

function compare(data1, data2) {
if (typeof data1 != typeof data2) {
return false;
}

if (typeof data1 != "object" && typeof data1 == typeof data2) {
return data1 === data2;
}

if ((!data1 instanceof Array && data2 instanceof Array) || (data1 instanceof Array && !data2 instanceof Array)) {
return false;
} else if (data1 instanceof Array && data2 instanceof Array) {
if (data1.length == data2.length) {
for (let i = 0; i < data1.length; i++) {
if (!compare(data1[i], data2[i])) {
return false;
}
}
} else {
return false;
}
} else {
if (Object.keys(data1).length == Object.keys(data2).length) {
for (let key in data1) {
if (!data2[key] || !compare(data1[key], data2[key])) {
return false;
}
}
} else {
return false;
}
}
return true;
}

深拷贝

function isObject(value) {
const valueType = typeof value
return (value !== null) && (valueType === "object" || valueType === "function")
}

function deepClone(originValue) {
// 判断传入的originValue是否是一个对象类型
if (!isObject(originValue)) {
return originValue
}

const newObject = {}
for (const key in originValue) {
newObject[key] = deepClone(originValue[key])
}
//返回通过递归深层遍历赋值后,全新的数据对象
return newObject
}

实现Object.create()

创建一个空对象,定义其原型对象并设置其枚举属性

// proto 可以是object 或者function
Object.myCreate = function (proto, defineProperties){
if((typeof proto === 'object' && proto !== null) || typeof proto === 'function'){
let obj = {};

// obj.__proto__ = proto;
Object.setPrototypeOf(obj, proto);
Object.defineProperty(obj, defineProperties);
return obj;
}else {
throw new TypeError('类型错误');
}
}

实现Object.assign(浅拷贝)

function myAssign(target,...objs){
if(target === null || target === undefined){
throw new TypeError("can not convert null or undefined to object")
}
let res = Object(target)
objs.forEach(obj => {
'use strict'
if(obj != null && obj != undefined){
for(let key in obj){
//hasOwnProperty用来判断一个属性是定义在对象本身而不是继承自原型链的
if(Object.prototype.hasOwnProperty.call(obj,key)){
res[key] = obj[key]
}
}
}
})
return res
}
Object.defineProperty(Object,'myAssign',{
value: myAssign,
writable: true,
configurable: true,
enumerable: false
})

trim方法

function myTrim(str) {
const reg = /^\s+|\s+$/g;
return str.replace(reg, '');
}

模板字符串

const render = (template, data) => {
const reg = /\$\{(.*?)\}/g;
template = template.replace(reg, (match, key) => {
let keys = key.trim().split('.');
return keys.reduce((obj, k) => obj?.[k], data) || '';
})
return template;
}

sleep函数

function sleep(ms) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('sleep...')
resolve()
}, ms);
})
}

async function test(){
console.log('1');
await sleep(400)
console.log('2')
}

test();

利用setTimeout实现setInterval

function coustomSetInterval(callback, time) {
let intervalId = null;
function loop() {
intervalId = setTimeout(() => {
callback();
loop();
}, time)
}
loop();
return () => clearTimeout(intervalId)
}
const interval = coustomSetInterval(() => {
console.log('想你了')
}, 1000)

setTimeout(() => {
interval()
}, 5000)

🚀 Promise 源码实现完全指南 - 从零手写 Promise

🎯 阅读本节你将收获

  • 深入理解 Promise 内部运行机制
  • 手写一个符合 Promise/A+ 规范的 Promise
  • 掌握面试中 Promise 相关的核心考点
  • 理解 async/await 的实现原理

📌 开篇:为什么要手写 Promise?

在面试中,Promise 相关的题目可以说是必考题。但很多同学只会用,不知道其内部原理。今天我们就一起来揭开 Promise 的神秘面纱,手写一个完整的 Promise!

一、Promise 基础架构 - 搭建地基 🏗️

1.1 最简单的 Promise 结构

我们先从最简单的结构开始,就像盖房子要先打地基:

class MyPromise {
constructor(executor) {
// executor 是使用者传入的函数,形如 (resolve, reject) => {}

// 定义 resolve 函数
const resolve = (value) => {
console.log('调用了 resolve,值为:', value);
}

// 定义 reject 函数
const reject = (reason) => {
console.log('调用了 reject,原因为:', reason);
}

// 立即执行 executor
executor(resolve, reject);
}
}

// 测试一下
new MyPromise((resolve, reject) => {
resolve('成功啦!');
});

1.2 Promise 的三种状态 - 状态机 🚦

Promise 就像一个有三种状态的交通灯:

  • 🟡 pending(等待态):初始状态,既不是成功,也不是失败
  • 🟢 fulfilled(成功态):操作成功完成
  • 🔴 rejected(失败态):操作失败

重要特性

  1. 状态只能从 pendingfulfilledpendingrejected
  2. 状态一旦改变,就永远不会再变(这就是为什么叫 “Promise” - 承诺)

让我们加上状态管理:

class MyPromise {
constructor(executor) {
// 初始状态为 pending
this.status = 'pending';
// 成功的值
this.value = undefined;
// 失败的原因
this.reason = undefined;

// resolve 函数:将状态从 pending 改为 fulfilled
const resolve = (value) => {
// 只有在 pending 状态才能改变状态(保证状态只改变一次)
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
}
}

// reject 函数:将状态从 pending 改为 rejected
const reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected';
this.reason = reason;
}
}

// 立即执行 executor,并传入 resolve 和 reject
try {
executor(resolve, reject);
} catch (error) {
// 如果执行器抛出异常,Promise 应该被拒绝
reject(error);
}
}
}

二、实现 then 方法 - Promise 的灵魂 ✨

2.1 then 方法的基本实现

then 方法是 Promise 的核心,它用来注册当 Promise 状态改变时的回调函数。

class MyPromise {
// ... 前面的代码

then(onFulfilled, onRejected) {
// 如果状态是 fulfilled,执行成功回调
if (this.status === 'fulfilled') {
onFulfilled(this.value);
}

// 如果状态是 rejected,执行失败回调
if (this.status === 'rejected') {
onRejected(this.reason);
}
}
}

// 测试同步情况
const promise = new MyPromise((resolve, reject) => {
resolve('成功!');
});

promise.then(
value => console.log('成功:', value),
reason => console.log('失败:', reason)
);

2.2 处理异步情况 - 发布订阅模式 📢

上面的代码有个问题:如果 executor 中有异步操作怎么办?

const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('异步成功!');
}, 1000);
});

// 此时状态还是 pending,then 方法不会执行任何回调!
promise.then(value => console.log(value));

解决方案:使用发布订阅模式,先把回调存起来,等状态改变时再执行:

class MyPromise {
constructor(executor) {
this.status = 'pending';
this.value = undefined;
this.reason = undefined;

// 存储成功回调的数组
this.onFulfilledCallbacks = [];
// 存储失败回调的数组
this.onRejectedCallbacks = [];

const resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
// 状态改变时,执行所有的成功回调
this.onFulfilledCallbacks.forEach(fn => fn());
}
}

const reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected';
this.reason = reason;
// 状态改变时,执行所有的失败回调
this.onRejectedCallbacks.forEach(fn => fn());
}
}

try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}

then(onFulfilled, onRejected) {
if (this.status === 'fulfilled') {
onFulfilled(this.value);
}

if (this.status === 'rejected') {
onRejected(this.reason);
}

// 如果是 pending 状态,将回调存储起来
if (this.status === 'pending') {
this.onFulfilledCallbacks.push(() => {
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
});
}
}
}

2.3 链式调用的实现 - then 返回 Promise 🔗

Promise 最强大的特性之一就是链式调用。要实现链式调用,then 方法必须返回一个新的 Promise:

then(onFulfilled, onRejected) {
// then 方法返回一个新的 Promise
return new MyPromise((resolve, reject) => {
// 封装一个执行函数,统一处理成功和失败的情况
const fulfilledMicrotask = () => {
// 使用 queueMicrotask 创建微任务,保证异步执行
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
// 处理返回值(详见下一节)
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};

const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};

if (this.status === 'fulfilled') {
fulfilledMicrotask();
} else if (this.status === 'rejected') {
rejectedMicrotask();
} else if (this.status === 'pending') {
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
}
});
}

2.4 处理 then 的返回值 - resolvePromise 🎯

这是实现 Promise 最复杂的部分,需要处理各种情况:

function resolvePromise(promise2, x, resolve, reject) {
// 如果 x === promise2,会造成循环引用
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected'));
}

// 如果 x 是 Promise 实例
if (x instanceof MyPromise) {
// 等待 x 的状态改变,然后递归处理
x.then(
value => resolvePromise(promise2, value, resolve, reject),
reason => reject(reason)
);
return;
}

// 如果 x 是对象或函数(可能是 thenable)
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let then;
try {
then = x.then;
} catch (error) {
return reject(error);
}

// 如果 then 是函数,认为 x 是 thenable
if (typeof then === 'function') {
let called = false; // 防止多次调用
try {
then.call(
x,
value => {
if (called) return;
called = true;
resolvePromise(promise2, value, resolve, reject);
},
reason => {
if (called) return;
called = true;
reject(reason);
}
);
} catch (error) {
if (called) return;
reject(error);
}
} else {
// 如果 then 不是函数,直接 resolve
resolve(x);
}
} else {
// 如果 x 是普通值,直接 resolve
resolve(x);
}
}

三、实现 Promise 的静态方法 🛠️

3.1 Promise.resolve 和 Promise.reject

class MyPromise {
// 快速创建一个成功的 Promise
static resolve(value) {
// 如果 value 已经是 Promise,直接返回
if (value instanceof MyPromise) {
return value;
}

return new MyPromise(resolve => resolve(value));
}

// 快速创建一个失败的 Promise
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason));
}
}

3.2 Promise.all - 一个都不能少 🎯

Promise.all 接收一个 Promise 数组,只有全部成功才成功,有一个失败就失败:

static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let completedCount = 0;

// 处理空数组的情况
if (promises.length === 0) {
resolve(results);
return;
}

promises.forEach((promise, index) => {
// 将非 Promise 值转换为 Promise
MyPromise.resolve(promise).then(
value => {
results[index] = value;
completedCount++;

// 所有 Promise 都成功了
if (completedCount === promises.length) {
resolve(results);
}
},
reason => {
// 有一个失败就直接 reject
reject(reason);
}
);
});
});
}

3.3 Promise.race - 谁快用谁 🏃

static race(promises) {
return new MyPromise((resolve, reject) => {
// 空数组永远 pending
if (promises.length === 0) return;

promises.forEach(promise => {
// 谁先完成就用谁的结果
MyPromise.resolve(promise).then(resolve, reject);
});
});
}

3.4 Promise.allSettled - 等待所有结果 📊

不管成功还是失败,等所有 Promise 都有结果:

static allSettled(promises) {
return new MyPromise((resolve) => {
const results = [];
let settledCount = 0;

if (promises.length === 0) {
resolve(results);
return;
}

promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = { status: 'fulfilled', value };
settledCount++;
if (settledCount === promises.length) {
resolve(results);
}
},
reason => {
results[index] = { status: 'rejected', reason };
settledCount++;
if (settledCount === promises.length) {
resolve(results);
}
}
);
});
});
}

3.5 Promise.any - 一个成功就够了 ✅

static any(promises) {
return new MyPromise((resolve, reject) => {
const errors = [];
let rejectedCount = 0;

if (promises.length === 0) {
reject(new AggregateError([], 'All promises were rejected'));
return;
}

promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
// 有一个成功就 resolve
resolve(value);
},
reason => {
errors[index] = reason;
rejectedCount++;

// 全部失败才 reject
if (rejectedCount === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
}
);
});
});
}

四、实现 catch 和 finally 🎣

4.1 catch - 错误处理

catch(onRejected) {
// catch 就是 then 的语法糖
return this.then(null, onRejected);
}

4.2 finally - 无论如何都要执行

finally(callback) {
return this.then(
// 成功时执行 callback,但传递原来的值
value => MyPromise.resolve(callback()).then(() => value),
// 失败时执行 callback,但传递原来的错误
reason => MyPromise.resolve(callback()).then(() => { throw reason })
);
}

五、async/await 的实现原理 🔮

5.1 理解 async/await

async/await 本质上是 Generator + Promise 的语法糖。让我们看看它是如何工作的:

// async 函数
async function fetchData() {
const user = await getUser();
const posts = await getPosts(user.id);
return posts;
}

// 等价于
function fetchData() {
return spawn(function* () {
const user = yield getUser();
const posts = yield getPosts(user.id);
return posts;
});
}

5.2 实现自动执行器

function spawn(genFunc) {
return new Promise((resolve, reject) => {
const gen = genFunc();

function step(nextFunc) {
let next;
try {
next = nextFunc();
} catch (error) {
return reject(error);
}

// Generator 函数执行完毕
if (next.done) {
return resolve(next.value);
}

// 将 yield 的值包装成 Promise,然后递归执行
Promise.resolve(next.value).then(
value => step(() => gen.next(value)),
error => step(() => gen.throw(error))
);
}

// 开始执行
step(() => gen.next());
});
}

5.3 一个更简单的实现

function asyncToGenerator(generatorFunc) {
return function (...args) {
const gen = generatorFunc.apply(this, args);

return new Promise((resolve, reject) => {
function step(key, arg) {
let generatorResult;

try {
generatorResult = gen[key](arg);
} catch (error) {
return reject(error);
}

const { value, done } = generatorResult;

if (done) {
return resolve(value);
} else {
return Promise.resolve(value).then(
val => step('next', val),
err => step('throw', err)
);
}
}

step('next');
});
};
}

// 使用示例
const getData = asyncToGenerator(function* () {
const data1 = yield fetch('/api/1');
const data2 = yield fetch('/api/2');
return [data1, data2];
});

getData().then(result => console.log(result));

🎯 面试高频考点总结

  1. Promise 的状态机制:三种状态,只能单向改变,一旦改变不可逆
  2. then 的链式调用:then 返回新的 Promise,根据回调函数的返回值决定新 Promise 的状态
  3. 微任务队列:Promise 的回调在微任务队列中执行
  4. 错误处理:catch 能捕获前面所有 then 中的错误
  5. Promise.all vs Promise.race:一个要全部成功,一个要最快完成
  6. async/await 原理:Generator + 自动执行器 + Promise

模板渲染引擎

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模板渲染示例</title>
</head>
<body>
<div id="app"></div>

<script src="script.js"></script>
</body>
</html>
// 渲染引擎
function renderTemplate(template, data) {

// 处理条件判断
template = template.replace(/{{\s*#if\s*(\w+)\s*}}([\s\S]*?){{\s*\/if\s*}}/g, function (_, condition, content) {
return data[condition] ? content : '';
});

// 处理循环
template = template.replace(/{{\s*#each\s*(\w+)\s*}}([\s\S]*?){{\s*\/each\s*}}/g, function (_, arrayKey, content) {
let result = '';
let array = data[arrayKey];
if (Array.isArray(array)) {
array.forEach((item, index) => {
// 创建一个独立作用域,避免冲突
let temp = content;
// 替换模板内的 {{ this }} 为循环项
temp = temp.replace(/{{\s*this\s*}}/g, function () {
return item;
});
// 如果是对象,处理它的属性
temp = temp.replace(/{{\s*this\.(\w+)\s*}}/g, function (_, prop) {
return item[prop] || '';
});
// 替换循环索引(如果需要)
temp = temp.replace(/{{\s*index\s*}}/g, index);
// 将处理后的结果添加到最终结果中
result += temp;
});
}
return result;
});
// 替换变量,一定要放到最后!!
template = template.replace(/\{\{(.*?)\}\}/g, (match, key) => {
let keys = key.trim().split('.');
return keys.reduce((obj, k) => obj?.[k], data) || '';
})

return template;
}

// 定义数据
const data = {
name: "张三",
age: 25,
isEmployed: true,
occupation: "工程师",
hobbies: ["阅读", "编程", "旅游"],
friends: [
{ name: "李四", age: 26 },
{ name: "王五", age: 24 }
]
};

// 定义模板
const template = `
<h1>个人信息</h1>
<p>姓名: {{ name }}</p>
<p>年龄: {{ age }}</p>
<p>职业: {{ occupation }}</p>

{{#if isEmployed}}
<p>当前状态: 在职</p>
{{/if}}

<h2>兴趣爱好</h2>
<ul>
{{#each hobbies}}
<li>{{ this }}</li>
{{/each}}
</ul>

<h2>朋友列表</h2>
<ul>
{{#each friends}}
<li>{{ this.name }} - {{ this.age }} 岁</li>
{{/each}}
</ul>
`;

// 渲染模板并插入 HTML
document.getElementById('app').innerHTML = renderTemplate(template, data);

防抖截流

const debounce = (fn, ms, Immediate = false) => {
// Immediate选择是否立即执行
let timer = null;
return function (...thisArgs) {
if (timer) {
clearTimeout(timer)
}
if (Immediate) {
let flag = !timer
flag && fn.apply(this, thisArgs)
timer = setTimeout(() => {
timer = null
}, ms)
} else {
timer = setTimeout(() => {
fn.apply(this, thisArgs)
timer = null
}, ms)
}
}
}

const throttle = (fn, ms) => {
let timer = null;
return function (...thisArgs) {
if (!timer) {
fn.apply(this, thisArgs);
timer = setTimeout(() => {
timer = null;
}, ms);
}
}
}

技巧

数组去重

let arr = [1, 0, 2, 3, 4, 5, 2, 3, 4];
//indexOf去重
function removeRepeat(arr) {
let res = [];
for (let i of arr) {
if (res.indexOf(i) == -1) {
res.push(i);
}
}
return res;
}
// set 去重
function removeRepeat(arr) {
let res = new Set(arr);
return Array.from(res);
}
// for循环去重
function removeRepeat(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
// filter 去重
function removeRepeat(arr) {
return arr.filter((item, index) => {
return arr.indexOf(item) == index;
});
}
// includes 去重
function removeRepeat(arr) {
let res = [];
for (let i of arr) {
if (!res.includes(i)) {
res.push(i);
}
}
return res;
}
let res = removeRepeat(arr);
console.log(res);

快排和归并

Array.prototype.quicksort = function (l, r) {
if (l >= r) return this;
let key = this[l];
let i = l - 1, j = r + 1;
while (i < j) {
do i++; while (this[i] < key);
do j--; while (this[j] > key);
if (i < j) [this[i], this[j]] = [this[j], this[i]];
}
this.quicksort(l, j);
this.quicksort(j + 1, r);
return this;
}
let arr = [5,8,3,6,4,2];
console.log(arr.quicksort(0, arr.length-1));


Array.prototype.mergesort = function (temp, l, r) {
if (l >= r) return this;
let mid = l + r >> 1;
this.mergesort(temp, l, mid);
this.mergesort(temp, mid + 1, r);

let i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r) {
if (this[i] <= this[j]) temp[k++] = this[i++];
else temp[k++] = this[j++];
}
while (i <= mid) temp[k++] = this[i++];
while (j <= r) temp[k++] = this[j++];

for (let m = 0, n = l; n <= r; m++, n++) {
this[n] = temp[m];
}
return this;
}

let arr = [7, 1, 0, 3, 0, 5, 6, 4];
console.log(arr.mergesort(Array(arr.length).fill(0), 0, arr.length - 1));

下划线和驼峰相互转换

//方式一:操作字符串数组
function transformStr2Hump1(str) {
if(str == null) {
return "";
}
var strArr = str.split('-');
for(var i = 1; i < strArr.length; i++) {
strArr[i] = strArr[i].charAt(0).toUpperCase() + strArr[i].substring(1);
}
return strArr.join('');
}

//方式二:操作字符数组
function transformStr2Hump2(str) {
if(str == null) {
return "";
}
var strArr =str.split('');
for(var i = 0; i < strArr.length; i++) {
if(strArr[i] == "-"){
//删除-
strArr.splice(i, 1);
//将该处改为大写
if(i < strArr.length) {
strArr[i] = strArr[i].toUpperCase();
}
}
}
return strArr.join("");
}

//方式三:利用正则
function transformStr2Hump3(str) {
if(str == null) {
return "";
}
var reg = /-(\w)/g;//匹配字母或数字或下划线或汉字
return str.replace(reg, function($0, $1) {
return $1.toUpperCase();
})
}

懒加载

  • 首先,不要将图片地址放到src属性中,而是放到其它属性(data-original)中。
  • 页面加载完成后,根据scrollTop判断图片是否在用户的视野内,如果在,则将data-original属性中的值取出存放到src属性中。
  • 在滚动事件中重复判断图片是否进入视野,如果进入,则将data-original属性中的值取出存放到src属性中。

elementNode.getAttribute(name):方法通过名称获取属性的值。

elementNode.setAttribute(name, value):方法创建或改变某个新属性。

elementNode.removeAttribute(name):方法通过名称删除属性的值。

//懒加载代码实现
var viewHeight = document.documentElement.clientHeight;//可视化区域的高度

function lazyload () {
//获取所有要进行懒加载的图片
let eles = document.querySelectorAll('img[data-original][lazyload]');//获取属性名中有data-original的
Array.prototype.forEach.call(eles, function(item, index) {
let rect;
if(item.dataset.original === '') {
return;
}

rect = item.getBoundingClientRect();

//图片一进入可视区,动态加载
if(rect.bottom >= 0 && rect.top < viewHeight) {
!function () {
let img = new Image();
img.src = item.dataset.original;
img.onload = function () {
item.src = img.src;
}
item.removeAttribute('data-original');
item.removeAttribute('lazyload');
}();
}
})
}

lazyload();

document.addEventListener('scroll', lazyload);

数组扁平化

//传入参数 决定扁平化的阶数
Array.prototype._flat = function (n) {
let result = [];
let num = n;
for (let item of this) {
// 如果是数组
if (Array.isArray(item)) {
n--;
// 没有扁平化的空间 直接推入
if (n < 0) {
result.push(item);
}
// 继续扁平化 并将n传入 决定item这一个数组中的扁平化
else {
result.push(...item._flat(n));
}
}
// 不是数组直接推入
else {
result.push(item);
}
// 每次循环 重置n 为传入的参数 因为每一项都需要扁平化 需要进行判断
n = num;
}
return result;
};
let arr = [1, 2, [3, 4], [5, 6, [7, 8]]];
let res = arr._flat(1);
console.log(res); // [ 1, 2, 3, 4, 5, 6, [ 7, 8 ] ]

计算属性

请不要为所有函数添加缓存!

const computed = (func, content) => {
let cache = Object.create(null);
content = content || this;
return (...key) => {
console.log(cache)
if (!cache[key]) {
cache[key] = func.apply(content, key);
}
return cache[key];
}
}

有并发限制的Promise调度器

class Scheduler{
constructor(limit){
this.limit = limit;
this.running = 0;
this.queue = [];
}
createTask(callback, duration){
return ()=>{
return new Promise((resolve, reject)=>{
setTimeout(()=>{
callback();
resolve();
},duration);
});
};
}

add(callback, duration){
const task = this.createTask(callback, duration);
this.queue.push(task);
}

start(){
for(let i=0;i<this.limit;++i){
this.schedule();
}
}
schedule(){
if(this.queue.length === 0 || this.running >= this.limit)return;
this.running++;
const task = this.queue.shift();

task().then(()=>{
this.running--;
schedule();
});
}
}

网络请求和跨域解决方案

原生ajax

function sendajax() {
// 1、 初始化xhr对象
const xhr = new XMLHttpRequest();
// 2、 建立连接 设置请求方法和url
xhr.open("get", "./data.json");
// 3、发送请求
xhr.send();
// 4、状态改变时 进行回调
xhr.onreadystatechange = function () {
// readyState 有0-4 五个值
// 0 代表 未初始化 1 代表 初始化成功 2 代表发送请求
// 3 代表返回了部分数据 4 代表返回了全部数据
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 进行成功的操作
console.log(xhr.responseText);
}
}
};
}
sendajax();

JSONP跨域

首先在客户端注册一个callback,然后把callback的名字传给服务器。此时,服务器先生成json数据,然后以JavaScript的语法方式,生成function,function的名字就是传递上来带参数的jsonp,最后将json数据直接以入参的方式,放置在function中,这样子就生成JavaScript语法文档,返回给客户端。客户端浏览器,通过解析,并执行返回JavaScript文档,此时数据作为参数,传入到客户端预先定义好的callback函数中,简单地说,就是利用script标签没有跨域限制地漏洞来达到第三方通讯的目的(href、src 都不受同源策略的限制。)

优点:

  • 它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制,JSONP可以跨越同源策略;
  • 它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;

缺点:

  • 只支持GET请求而不支持POST等其它类型的HTTP请求
  • 它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
  • jsonp在调用失败的时候不会返回各种HTTP状态码。
  • 需要后端配合
function jsonp({url,params,cb}){
return new Promise((resolve, reject)=>{
window[cb] = function(data){
console.log(data)
resolve(data);
document.body.removeChild(script);
}//window对象上设置show方法
params= {...params,cb}
let arrs = [];
for (let key in params){
arrs.push(`${key}=${params[key]}`)
}
let script = document.createElement('script');
script.src = `${url}?${arrs.join('&')}`;
script.onerror = () => reject('加载失败')
document.body.appendChild(script);


})
}
jsonp({
url:"http://localhost:3000/users",
params:{name:"jin",age:12},
cb:'show'
}).then(data=>{
console.log(data)
})

后端:

let express = require('express');
let app = express();
app.get('/users', function(req, res, next) {
// 模拟的数据
let {name,age,cb} = req.query
let data = `"${name}现在${age}岁"`
res.send(`${cb}(${data})`);// show(data)
});

app.listen(3000)

cors跨域

同源策略是不允许接收响应而不是不允许发送请求,所以可以通过在响应头中设置某些字段来允许满足条件的请求跨域,比如设置 Access-Control-Allow-Origin 字段允许来自某个源的请求跨域,比如设置 Access-Control-Allow-Methods 字段允许’GET’或者’POST’方式的请求跨域

后端:

let express = require('express');
let app = express();
app.all('*', function (req, res, next) {
let origin = req.headers.origin
//设置哪个源可以访问我
res.header("Access-Control-Allow-Origin",origin);
// 允许携带哪个头访问我
res.header("Access-Control-Allow-Headers", "name");
// 允许哪个方法访问我
res.header("Access-Control-Allow-Methods", "POST");
// 允许携带cookie
res.set("Access-Control-Allow-Credentials", true);
// 预检的存活时间
res.header("Access-Control-Max-Age", 6);
// 允许前端获取哪个头
res.header("Access-Control-Expose-Headers", "name");
// 请求头的格式
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
app.post('/getData', function(req, res, next) {
console.log(req.headers)
res.send("你拿不到数据了!");
});

app.listen(4000)

img

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。 代码修改一下:

// 在请求头的设置中加上
if(req.method ==='OPTIONS'){
res.end();//OPTIONS请求不做任何处理
}

img

postMessage跨域

「window.postMessage()」 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 [Document.domain]设置为相同的值) 时,这两个脚本才能相互通信。「window.postMessage()」 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

用途

1.页面和其打开的新窗口的数据传递

2.多窗口之间消息传递

3.页面与嵌套的 iframe 消息传递

实现

a.html

<iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe>
<script>
function load(){
let frame = document.getElementById('frame');
frame.contentWindow.postMessage('你好','http://localhost:4000/');
//接收
window.onmessage= function(e){
console.log(e.data)
}
}
</script>

b.html

window.onmessage = function(e){
console.log(e.data);
//发送
e.source.postMessage('hello',e.origin)
}

本地代理跨域

proxy其实就是因为浏览器同源协议无法请求非同源的地址,但是服务器直接没有同源协议,利用将本地请求转到本地服务器进行代理转发,从而绕过了同源协议的限制,通过代理的实现可以解决跨域的问题

通过设置一个 node 后端作为中间层,前端发送的请求首先到达这个中间层,然后再由中间层将请求转发到目标服务器。响应过程也是如此,服务器先响应给中间层,中间层再将响应数据发送回前端。

这个中间层就起到了一个代理的作用。这样,浏览器看到的是同源请求,从而绕过了CORS限制。

假设前端现在要将请求发送给 http://192.168.1.63:3000 这个后端,就可以先由本机的 3001 端口作一个代理

<!-- 前端 -->
<body>
<script>
const xhr = new XMLHttpRequest()
xhr.open('GET', 'https://localhost:3001')
xhr.send()
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
</script>
</body>

本机后端

// 后端
const http = require('http')

// 监听本机3001端口,有新请求时调用回调函数
http.createServer((req, res) => {

// 设置响应头,以允许前端应用访问响应内容
res.writeHead(200, {
'Access-Control-Allow-Origin': '*'
})

// 转发请求到目标服务器,并处理响应
http.request({
host: '192.168.1.63',
port: 3000,
path: '/',
method: 'GET',
headers: {}
}, proxyRes => {
proxyRes.on('data', chunk => {
res.end(chunk.toString())
})
}).end()

}).listen(3001)

各脚手架的proxy配置:

Webpack (4.x)

webpack中可以配置proxy来快速获得接口代理的能力。

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
entry: {
index: "./index.js"
},
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist")
},
devServer: {
port: 8000,
proxy: {
"/api": {
target: "http://localhost:8080"
}
}
},
plugins: [
new HtmlWebpackPlugin({
filename: "index.html",
template: "webpack.html"
})
]
};

Vue-cli 2.x

// config/index.js

...
proxyTable: {
'/api': {
target: 'http://localhost:8080',
}
},
...

Vue-cli 3.x

// vue.config.js 如果没有就新建
module.exports = {
devServer: {
port: 8000,
proxy: {
"/api": {
target: "http://localhost:8080"
}
}
}
};

vite

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import styleImport, { VantResolve } from 'vite-plugin-style-import';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
styleImport({
resolves: [VantResolve()],
}),],
server: { //主要是加上这段代码
host: '127.0.0.1',
port: 3000,
proxy: {
'/api': {
target: 'http://127.0.0.1:8888', //实际请求地址
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
}
}
})

nginx反向代理跨域

反向代理与本地代理相反。本地代理是客户端搞定的,反向代理就是服务端搞定的

反向代理的是在服务端内部完成。

是服务端(数据应用)向服务端(网页应用)发送数据, 服务端向客户端发送数据 其本质是在服务端(网页应用)通过配置Access-Control-Allow-Origin * 来解决跨域问题。相当于对后端接口进行了统一的cors配置

Access-Control-Allow-Origin: * 值表明,该资源可以被任意外源访问。

    #通过配置nginx文件既可

events{}

http{
include mime.types;
default_type application/octet-stream;
server{
listen 80;
server_name 127.0.0.1;
root D:/nginx-1.26.1/dist;
index index.html;
location / {

location /api {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# 解决跨域问题
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, HEAD, PUT, DELETE";
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization, User-Agent, DNT";
add_header Access-Control-Max-Age 86400;


}
}
}
}

设计模式

单例模式

// 单例模式示例代码
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
}
return Singleton.instance;
}

createInstance() {
const object = { name: "example" };
return object;
}

getInstance() {
if (!Singleton.instance) {
Singleton.instance = this.createInstance();
}
return Singleton.instance;
}
}

// 使用示例
const instance1 = new Singleton();
const instance2 = new Singleton();

console.log(instance1 === instance2); // true

工厂模式

class Product {
constructor(name) {
this.name = name;
}

getName() {
return this.name;
}
}

class ProductFactory {
static createProduct(name) {
return new Product(name);
}
}

// 使用示例
const product = ProductFactory.createProduct("Example Product");
console.log(product.getName()); // "Example Product"

观察者模式

class Subject {
constructor() {
this.observers = [];
}

addObserver(observer) {
this.observers.push(observer);
}

removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}

notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}

class Observer {
update(data) {
console.log(`Received data: ${data}`);
}
}

// 使用示例
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.notify("Hello World!");

装饰器模式

interface Component {
operation(): void;
}

class ConcreteComponent implements Component {
public operation(): void {
console.log("ConcreteComponent: operation.");
}
}

class Decorator implements Component {
protected component: Component;

constructor(component: Component) {
this.component = component;
}

public operation(): void {
console.log("Decorator: operation.");
this.component.operation();
}
}

class ConcreteDecoratorA extends Decorator {
public operation(): void {
super.operation();
console.log("ConcreteDecoratorA: operation.");
}
}

class ConcreteDecoratorB extends Decorator {
public operation(): void {
super.operation();
console.log("ConcreteDecoratorB: operation.");
}
}

// 使用示例
const concreteComponent = new ConcreteComponent();
const concreteDecoratorA = new ConcreteDecoratorA(concreteComponent);
const concreteDecoratorB = new ConcreteDecoratorB(concreteDecoratorA);

concreteDecoratorB.operation();

代理模式

const target = {
method() {
console.log("Target method.");
}
};

const proxy = new Proxy(target, {
get(target, prop) {
console.log(`Called ${prop} method.`);
return target[prop];
}
});

// 使用示例
proxy.method(); // "Called method method. Target method."

适配器模式

class Adaptee {
specificRequest() {
return "适配者中的业务代码被调用";
}
}

class Target {
constructor() {
this.adaptee = new Adaptee();
}

request() {
let info = this.adaptee.specificRequest();
return `${info} - 转换器 - 适配器代码被调用`;
}
}

// 使用示例
let target = new Target();
target.request(); // "适配者中的业务代码被调用 - 转换器 - 适配器代码被调用"

MVC模式

class Model {
constructor() {
this.data = {
name: "example",
age: 18,
gender: "male"
};
}

setData(key, value) {
this.data[key] = value;
}

getData() {
return this.data;
}
}

class View {
constructor() {
this.container = document.createElement("div");
}

render(data) {
const { name, age, gender } = data;
this.container.innerHTML = `
<p>Name: ${name}</p>
<p>Age: ${age}</p>
<p>Gender: ${gender}</p>
`;
document.body.appendChild(this.container);
}
}

class Controller {
constructor(model, view) {
this.model = model;
this.view = view;
this.view.render(this.model.getData());
}

setData(key, value) {
this.model.setData(key, value);
this.view.render(this.model.getData());
}
}

// 使用示例
const model = new Model();
const view = new View();
const controller = new Controller(model, view);

controller.setData("age", 20);

策略模式

表单验证情景:

// 验证策略
const validationStrategies = {
required: {
validate: (value) => value !== '',
message: '该字段不能为空'
},

minLength: {
validate: (value, length) => value.length >= length,
message: (length) => `最少需要${length}个字符`
},

email: {
validate: (value) => /^\S+@\S+\.\S+$/.test(value),
message: '请输入有效的邮箱地址'
},

phone: {
validate: (value) => /^1[3-9]\d{9}$/.test(value),
message: '请输入有效的手机号'
},

custom: {
validate: (value, validator) => validator(value),
message: '自定义验证失败'
}
};

// 表单验证器
class FormValidator {
constructor() {
this.rules = [];
}

// 添加验证规则
addRule(field, strategy, ...params) {
this.rules.push({
field,
strategy,
params
});
}

// 执行验证
validate(formData) {
const errors = [];

for (const rule of this.rules) {
const { field, strategy, params } = rule;
const value = formData[field];
const validationStrategy = validationStrategies[strategy];

if (!validationStrategy.validate(value, ...params)) {
errors.push({
field,
message: typeof validationStrategy.message === 'function'
? validationStrategy.message(...params)
: validationStrategy.message
});
}
}

return {
isValid: errors.length === 0,
errors
};
}
}

// 使用示例
const validator = new FormValidator();
validator.addRule('username', 'required');
validator.addRule('username', 'minLength', 3);
validator.addRule('email', 'required');
validator.addRule('email', 'email');
validator.addRule('phone', 'phone');

const formData = {
username: 'ab',
email: 'invalid-email',
phone: '12345678901'
};

const result = validator.validate(formData);
console.log(result);
// {
// isValid: false,
// errors: [
// { field: 'username', message: '最少需要3个字符' },
// { field: 'email', message: '请输入有效的邮箱地址' },
// { field: 'phone', message: '请输入有效的手机号' }
// ]
// }
  • 标题: 【秋招准备】面试手撕大集合
  • 作者: Simon
  • 创建于 : 2025-07-22 15:20:00
  • 更新于 : 2025-08-05 16:12:28
  • 链接: https://www.simonicle.cn/2025/07/22/【秋招备战】面试手撕大集合/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
【秋招准备】面试手撕大集合