建站教程

建站教程

Products

当前位置:首页 > 建站教程 >

前端JS高阶面试题(JavaScript实现手写 forEach算法)

GG网络技术分享 2025-03-18 16:14 1


前端JS高阶面试题

JS深入

如何在 JS 中“深冻结”对象

1. 如果咱们想要确保对象被深冻结,就必须创建一个递归函数来冻结对象类型的每个属性:

2. 没有深冻结

let person = {

name: "Leonardo",

profession: {

name: "developer"

}

};

Object.freeze(person);

person.profession.name = "doctor";

console.log(person); //output { name: 'Leonardo', profession: { name: 'doctor' } }

3. 深冻结

function deepFreeze(object) {

let propNames = Object.getOwnPropertyNames(object);

for (let name of propNames) {

let value = object[name];

object[name] = value && typeof value === "object" ? deepFreeze(value) : value;

}

return Object.freeze(object);

}

let person = {

name: "Leonardo",

profession: {

name: "developer"

}

};

deepFreeze(person);

person.profession.name = "doctor"; // TypeError: Cannot assign to read only property

手写call()

/*

自定义函数对象的call方法

*/

export function call (fn, obj, ...args) {

// 如果传入的是null/undefined, this指定为window

if (obj===null || obj===undefined) {

obj = obj || window

}

// 给obj添加一个方法: 属性名任意, 属性值必须当前调用call的函数对象

obj.tempFn = fn

// 通过obj调用这个方法

const result = obj.tempFn(...args)

// 删除新添加的方法

delete obj.tempFn

// 返回函数调用的结果

return result

}

5.3. 手写apply()

/*

自定义函数对象的apply方法

*/

export function apply (fn, obj, args) {

// 如果传入的是null/undefined, this指定为window

if (obj===null || obj===undefined) {

obj = obj || window

}

// 给obj添加一个方法: 属性名任意, 属性值必须当前调用call的函数对象

obj.tempFn = fn

// 通过obj调用这个方法

const result = obj.tempFn(...args)

// 删除新添加的方法

delete obj.tempFn

// 返回函数调用的结果

return result

}

手写bind()

import {call} from './call'

/*

自定义函数对象的bind方法

重要技术:

高阶函数

闭包

call()

三点运算符

*/

export function bind (fn, obj, ...args) {

if (obj===null || obj===undefined) {

obj = obj || window

}

return function (...args2) {

call(fn, obj, ...args, ...args2)

}

}

5.5. 手写一个防抖函数

/*

实现函数防抖的函数

*/

export function debounce(callback, delay) {

return function () {

// console.log('debounce 事件...')

// 保存this和arguments

const that = this

const args = arguments

// 清除待执行的定时器任务

if (callback.timeoutId) {

clearTimeout(callback.timeoutId)

}

// 每隔delay的时间, 启动一个新的延迟定时器, 去准备调用callback

callback.timeoutId = setTimeout(function () {

callback.apply(that, args)

// 如果定时器回调执行了, 删除标记

delete callback.timeoutId

}, delay)

}

}

手写一个节流函数

/*

实现函数节流的函数

*/

export function throttle(callback, delay) {

let start = 0 // 必须保存第一次点击立即调用

return function () {

// 它的this是谁就得让callback()中的this是谁, 它接收的所有实参都直接交给callback()

console.log('throttle 事件')

const current = Date.now()

if (current - start > delay) { // 从第2次点击开始, 需要间隔时间超过delay

callback.apply(this, arguments)

start = current

}

}

}

手写一个深拷贝函数

/*

1). 大众乞丐版

问题1: 函数属性会丢失

问题2: 循环引用会出错

*/

export function deepClone1(target) {

return JSON.parse(JSON.stringify(target))

}

/*

获取数据的类型字符串名

*/

function getType(data) {

return Object.prototype.toString.call(data).slice(8, -1)

}

/*

2). 面试基础版本

解决问题1: 函数属性还没丢失

*/

export function deepClone2(target) {

const type = getType(target)

if (type==='Object' || type==='Array') {

const cloneTarget = type === 'Array' ? [] : {}

for (const key in target) {

if (target.hasOwnProperty(key)) {

cloneTarget[key] = deepClone2(target[key])

}

}

return cloneTarget

} else {

return target

}

}

/*

3). 面试加强版本

解决问题2: 循环引用正常

*/

export function deepClone3(target, map = new Map()) {

const type = getType(target)

if (type==='Object' || type==='Array') {

let cloneTarget = map.get(target)

if (cloneTarget) {

return cloneTarget

}

cloneTarget = type==='Array' ? [] : {}

map.set(target, cloneTarget)

for (const key in target) {

if (target.hasOwnProperty(key)) {

cloneTarget[key] = deepClone3(target[key], map)

}

}

return cloneTarget

} else {

return target

}

}

/*

4). 面试加强版本2(优化遍历性能)

数组: while | for | forEach() 优于 for-in | keys()&forEach()

对象: for-in 与 keys()&forEach() 差不多

*/

export function deepClone4(target, map = new Map()) {

const type = getType(target)

if (type==='Object' || type==='Array') {

let cloneTarget = map.get(target)

if (cloneTarget) {

return cloneTarget

}

if (type==='Array') {

cloneTarget = []

map.set(target, cloneTarget)

target.forEach((item, index) => {

cloneTarget[index] = deepClone4(item, map)

})

} else {

cloneTarget = {}

map.set(target, cloneTarget)

Object.keys(target).forEach(key => {

cloneTarget[key] = deepClone4(target[key], map)

})

}

return cloneTarget

} else {

return target

}

}

自定义instanceof工具函数

/*

自定义instanceof工具函数:

语法: myInstanceOf(obj, Type)

功能: 判断obj是否是Type类型的实例

实现: Type的原型对象是否是obj的原型链上的某个对象, 如果是返回true, 否则返回false

*/

export function myInstanceOf(obj, Type) {

// 得到原型对象

let protoObj = obj.__proto__

// 只要原型对象存在

while(protoObj) {

// 如果原型对象是Type的原型对象, 返回true

if (protoObj === Type.prototype) {

return true

}

// 指定原型对象的原型对象

protoObj = protoObj.__proto__

}

return false

}

自定义new工具函数

/*

自定义new工具函数

语法: newInstance(Fn, ...args)

功能: 创建Fn构造函数的实例对象

实现: 创建空对象obj, 调用Fn指定this为obj, 返回obj

*/

export function newInstance(Fn, ...args) {

// 创建一个新的对象

const obj = {}

// 执行构造函数

const result = Fn.apply(obj, args) // 相当于: obj.Fn()

// 如果构造函数执行的结果是对象, 返回这个对象

if (result instanceof Object) {

return result

}

// 如果不是, 返回新创建的对象

obj.__proto__.constructor = Fn // 让原型对象的构造器属性指向Fn

return obj

}

手写axios函数

/*

1. 函数的返回值为promise, 成功的结果为response, 失败的结果为error

2. 能处理多种类型的请求: GET/POST/PUT/DELETE

3. 函数的参数为一个配置对象

{

url: '', // 请求地址

method: '', // 请求方式GET/POST/PUT/DELETE

params: {}, // GET/DELETE请求的query参数

data: {}, // POST或DELETE请求的请求体参数

}

4. 响应json数据自动解析为js的对象/数组

*/

/* 发送任意类型请求的函数 */

function axios({

url,

method='GET',

params={},

data={}

}) {

// 返回一个promise对象

return new Promise((resolve, reject) => {

// 处理method(转大写)

method = method.toUpperCase()

// 处理query参数(拼接到url上) id=1&xxx=abc

/*

{

id: 1,

xxx: 'abc'

}

*/

let queryString = ''

Object.keys(params).forEach(key => {

queryString += `${key}=${params[key]}&`

})

if (queryString) { // id=1&xxx=abc&

// 去除最后的&

queryString = queryString.substring(0, queryString.length-1)

// 接到url

url += '?' + queryString

}

// 1. 执行异步ajax请求

// 创建xhr对象

const request = new XMLHttpRequest()

// 打开连接(初始化请求, 没有请求)

request.open(method, url, true)

// 发送请求

if (method==='GET') {

request.send()

} else if (method==='POST' || method==='PUT' || method==='DELETE'){

request.setRequestHeader('Content-Type', 'application/json;charset=utf-8') // 告诉服务器请求体的格式是json

request.send(JSON.stringify(data)) // 发送json格式请求体参数

}

// 绑定状态改变的监听

request.onreadystatechange = function () {

// 如果请求没有完成, 直接结束

if (request.readyState!==4) {

return

}

// 如果响应状态码在[200, 300)之间代表成功, 否则失败

const {status, statusText} = request

// 2.1. 如果请求成功了, 调用resolve()

if (status>=200 && status<=299) {

// 准备结果数据对象response

const response = {

data: JSON.parse(request.response),

status,

statusText

}

resolve(response)

} else { // 2.2. 如果请求失败了, 调用reject()

reject(new Error('request error status is ' + status))

}

}

})

}

/* 发送特定请求的静态方法 */

axios.get = function (url, options) {

return axios(Object.assign(options, {url, method: 'GET'}))

}

axios.delete = function (url, options) {

return axios(Object.assign(options, {url, method: 'DELETE'}))

}

axios.post = function (url, data, options) {

return axios(Object.assign(options, {url, data, method: 'POST'}))

}

axios.put = function (url, data, options) {

return axios(Object.assign(options, {url, data, method: 'PUT'}))

}

export default axios

自定义事件总线

/*

* 自定义事件总线

*/

const eventBus = {}

/*

{

add: [callback1, callback2]

delete: [callback3]

}

*/

let callbacksObj = {}

/*

绑定事件监听

*/

eventBus.on = function (eventName, callback) {

const callbacks = callbacksObj[eventName]

if (callbacks) {

callbacks.push(callback)

} else {

callbacksObj[eventName] = [callback]

}

}

/*

分发事件

*/

eventBus.emit = function (eventName, data) {

const callbacks = callbacksObj[eventName]

if (callbacks && callbacks.length > 0) {

callbacks.forEach(callback => {

callback(data)

})

}

}

/*

移除事件监听

*/

eventBus.off = function (eventName) {

if (eventName) {

delete callbacksObj[eventName]

} else {

callbacksObj = {}

}

}

export default eventBus

自定义消息订阅与发布

/*

自定义消息订阅与发布

*/

const PubSub = {}

/*

{

add: {

token1: callback1,

token2: callback2

},

update: {

token3: callback3

}

}

*/

let callbacksObj = {} // 保存所有回调的容器

let id = 0 // 用于生成token的标记

// 1. 订阅消息

PubSub.subscribe = function (msgName, callback) {

// 确定token

const token = 'token_' + ++id

// 取出当前消息对应的callbacks

const callbacks = callbacksObj[msgName]

if (!callbacks) {

callbacksObj[msgName] = {

[token]: callback

}

} else {

callbacks[token] = callback

}

// 返回token

return token

}

// 2. 发布异步的消息

PubSub.publish = function (msgName, data) {

// 取出当前消息对应的callbacks

let callbacks = callbacksObj[msgName]

// 如果有值

if (callbacks) {

// callbacks = Object.assign({}, callbacks)

// 启动定时器, 异步执行所有的回调函数

setTimeout(() => {

Object.values(callbacks).forEach(callback => {

callback(data)

})

}, 0)

}

}

// 3. 发布同步的消息

PubSub.publishSync = function (msgName, data) {

// 取出当前消息对应的callbacks

const callbacks = callbacksObj[msgName]

// 如果有值

if (callbacks) {

// 立即同步执行所有的回调函数

Object.values(callbacks).forEach(callback => {

callback(data)

})

}

}

/*

4. 取消消息订阅

1). 没有传值, flag为undefined

2). 传入token字符串

3). msgName字符串

*/

PubSub.unsubscribe = function (flag) {

// 如果flag没有指定或者为null, 取消所有

if (flag === undefined) {

callbacksObj = {}

} else if (typeof flag === 'string') {

if (flag.indexOf('token_') === 0) { // flag是token

// 找到flag对应的callbacks

const callbacks = Object.values(callbacksObj).find(callbacks => callbacks.hasOwnProperty(flag))

// 如果存在, 删除对应的属性

if (callbacks) {

delete callbacks[flag]

}

} else { // flag是msgName

delete callbacksObj[flag]

}

} else {

throw new Error('如果传入参数, 必须是字符串类型')

}

}

export default PubSub

自定义数组声明式系列方法

/*

实现数组声明式处理系列工具函数

*/

/*

实现map()

*/

export function map (array, callback) {

const arr = []

for (let index = 0; index < array.length; index++) {

arr.push(callback(array[index], index))

}

return arr

}

/*

实现reduce()

*/

export function reduce (array, callback, initValue) {

let result = initValue

for (let index = 0; index < array.length; index++) {

// 调用回调函数将返回的结果赋值给result

result = callback(result, array[index], index)

}

return result

}

/*

实现filter()

*/

export function filter(array, callback) {

const arr = []

for (let index = 0; index < array.length; index++) {

if (callback(array[index], index)) {

arr.push(array[index])

}

}

return arr

}

/*

实现find()

*/

export function find (array, callback) {

for (let index = 0; index < array.length; index++) {

if (callback(array[index], index)) {

return array[index]

}

}

return undefined

}

/*

实现findIndex()

*/

export function findIndex (array, callback) {

for (let index = 0; index < array.length; index++) {

if (callback(array[index], index)) {

return index

}

}

return -1

}

/*

实现every()

*/

export function every (array, callback) {

for (let index = 0; index < array.length; index++) {

if (!callback(array[index], index)) { // 只有一个结果为false, 直接返回false

return false

}

}

return true

}

/*

实现some()

*/

export function some (array, callback) {

for (let index = 0; index < array.length; index++) {

if (callback(array[index], index)) { // 只有一个结果为true, 直接返回true

return true

}

}

return false

}

export function test() {

console.log('test()222')

}

手写Promise

const PENDING = 'pending' // 初始未确定的状态

const RESOLVED = 'resolved' // 成功的状态

const REJECTED = 'rejected' // 失败的状态

/*

Promise构造函数

*/

function Promise(excutor) {

const self = this // Promise的实例对象

self.status = PENDING // 状态属性, 初始值为pending, 代表初始未确定的状态

self.data = undefined // 用来存储结果数据的属性, 初始值为undefined

self.callbacks = [] // {onResolved(){}, onRejected(){}}

/*

将promise的状态改为成功, 指定成功的value

*/

function resolve(value) {

// 如果当前不是pending, 直接结束

if (self.status !== PENDING) return

self.status = RESOLVED // 将状态改为成功

self.data = value // 保存成功的value

// 异步调用所有缓存的待执行成功的回调函数

if (self.callbacks.length > 0) {

// 启动一个延迟时间为0的定时器, 在定时器的回调中执行所有成功的回调

setTimeout(() => {

self.callbacks.forEach(cbsObj => {

cbsObj.onResolved(value)

})

})

}

}

/*

将promise的状态改为失败, 指定失败的reason

*/

function reject(reason) {

// 如果当前不是pending, 直接结束

if (self.status !== PENDING) return

self.status = REJECTED // 将状态改为失败

self.data = reason // 保存reason数据

// 异步调用所有缓存的待执行失败的回调函数

if (self.callbacks.length > 0) {

// 启动一个延迟时间为0的定时器, 在定时器的回调中执行所有失败的回调

setTimeout(() => {

self.callbacks.forEach(cbsObj => {

cbsObj.onRejected(reason)

})

})

}

}

// 调用excutor来启动异步任务

try {

excutor(resolve, reject)

} catch (error) { // 执行器执行出错, 当前promise变为失败

console.log('-----')

reject(error)

}

}

/*

用来指定成功/失败回调函数的方法

1). 如果当前promise是resolved, 异步执行成功的回调函数onResolved

2). 如果当前promise是rejected, 异步执行成功的回调函数onRejected

3). 如果当前promise是pending, 保存回调函数

返回一个新的promise对象

它的结果状态由onResolved或者onRejected执行的结果决定

2.1). 抛出error ==> 变为rejected, 结果值为error

2.2). 返回值不是promise ==> 变为resolved, 结果值为返回值

2.3). 返回值是promise ===> 由这个promise的决定新的promise的结果(成功/失败)

*/

Promise.prototype.then = function (onResolved, onRejected) {

const self = this

onResolved = typeof onResolved === 'function' ? onResolved : value => value // 将value向下传递

onRejected = typeof onRejected === 'function' ? onRejected : reason => {

throw reason

} // 将reason向下传递

return new Promise((resolve, reject) => { // 什么时候改变它的状态

/*

1. 调用指定的回调函数

2. 根据回调执行结果来更新返回promise的状态

*/

function handle(callback) {

try {

const result = callback(self.data)

if (!(result instanceof Promise)) { // 2.2). 返回值不是promise ==> 变为resolved, 结果值为返回值

resolve(result)

} else { // 2.3). 返回值是promise ===> 由这个promise的决定新的promise的结果(成功/失败)

result.then(

value => resolve(value),

reason => reject(reason)

)

// result.then(resolve, reject)

}

} catch (error) { // 2.1). 抛出error ==> 变为rejected, 结果值为error

reject(error)

}

}

if (self.status === RESOLVED) {

setTimeout(() => {

handle(onResolved)

})

} else if (self.status === REJECTED) {

setTimeout(() => {

handle(onRejected)

})

} else { // PENDING

self.callbacks.push({

onResolved(value) {

handle(onResolved)

},

onRejected(reason) {

handle(onRejected)

}

})

}

})

}

/*

用来指定失败回调函数的方法

catch是then的语法糖

*/

Promise.prototype.catch = function (onRejected) {

return this.then(undefined, onRejected)

}

/*

用来返回一个指定vlaue的成功的promise

value可能是一个一般的值, 也可能是promise对象

*/

Promise.resolve = function (value) {

return new Promise((resolve, reject) => {

// 如果value是一个promise, 最终返回的promise的结果由value决定

if (value instanceof Promise) {

value.then(resolve, reject)

} else { // value不是promise, 返回的是成功的promise, 成功的值就是value

resolve(value)

}

})

}

/*

用来返回一个指定reason的失败的promise

*/

Promise.reject = function (reason) {

return new Promise((resolve, reject) => {

reject(reason)

})

}

/*

返回一个promise, 只有当数组中所有promise都成功才成功, 否则失败

*/

Promise.all = function (promises) {

return new Promise((resolve, reject) => {

let resolvedCount = 0 // 已经成功的数量

const values = new Array(promises.length) // 用来保存成功promise的value值

// 遍历所有promise, 取其对应的结果

promises.forEach((p, index) => {

p.then(

value => {

resolvedCount++

values[index] = value

if (resolvedCount === promises.length) { // 都成功了

resolve(values)

}

},

reason => reject(reason)

)

})

})

}

/*

返回一个promise, 由第一个完成promise决定

*/

Promise.race = function (promises) {

return new Promise((resolve, reject) => {

// 遍历所有promise, 取其对应的结果

promises.forEach(p => {

// 返回的promise由第一个完成p来决定其结果

p.then(resolve, reject)

})

})

}

/*

返回一个延迟指定时间才成功(也可能失败)的promise

*/

Promise.resolveDelay = function (value, time) {

return new Promise((resolve, reject) => {

setTimeout(() => {

// 如果value是一个promise, 最终返回的promise的结果由value决定

if (value instanceof Promise) {

value.then(resolve, reject)

} else { // value不是promise, 返回的是成功的promise, 成功的值就是value

resolve(value)

}

}, time)

})

}

/*

返回一个延迟指定时间才失败的promise

*/

Promise.rejectDelay = function (reason, time) {

return new Promise((resolve, reject) => {

setTimeout(() => {

reject(reason)

}, time)

})

}

export default Promise

自定义数组扁平化

/*

数组扁平化: 取出嵌套数组(多维)中的所有元素放到一个新数组(一维)中

如: [1, [3, [2, 4]]] ==> [1, 3, 2, 4]

*/

/*

方法一: 递归 + reduce() + concat()

*/

export function flatten1 (array) {

return array.reduce((pre, item) => {

if (Array.isArray(item)) {

return pre.concat(flatten1(item))

} else {

return pre.concat(item)

}

}, [])

}

/*

方法二: ... + some() + concat()

*/

export function flatten2 (array) {

let arr = [].concat(...array)

while (arr.some(item => Array.isArray(item))) {

arr = [].concat(...arr)

}

return arr

}

JavaScript实现手写 forEach算法

本文实例讲述了JS实现手写 forEach算法。分享给大家供大家参考,具体如下:

手写 forEach

forEach()方法对数组的每个元素执行一次提供的函数

arr.forEach(callback(currentValue [, index [, array]])[, thisArg]);

callback

currentValue

数组中正在处理的当前元素。

index 可选

数组中正在处理的当前元素的索引。

array 可选

forEach() 方法正在操作的数组。

thisArg 可选

可选参数。当执行回调函数 callback 时,用作 this 的值。

没有返回值

如果提供了一个thisArg参数给forEach函数,则参数将会作为回调函数中的this值。否则this值为undefined。回调函数中this的绑定是根据函数被调用时通用的this绑定规则来决定的。

let arr = [1, 2, 3, 4];

arr.forEach((...item) => console.log(item));

// [1, 0, Array(4)] 当前值

function Counter() {

this.sum = 0;

this.count = 0;

}

// 因为 thisArg 参数(this)传给了 forEach(),每次调用时,它都被传给 callback 函数,作为它的 this 值。

Counter.prototype.add = function(array) {

array.forEach(function(entry) {

this.sum += entry;

++this.count;

}, this);

// ^---- Note

};

const obj = new Counter();

obj.add([2, 5, 9]);

obj.count;

// 3 === (1 + 1 + 1)

obj.sum;

// 16 === (2 + 5 + 9)

每个数组都有这个方法

回调参数为:每一项、索引、原数组

Array.prototype.forEach = function(fn, thisArg) {

var _this;

if (typeof fn !== \"function\") {

throw \"参数必须为函数\";

}

if (arguments.length > 1) {

_this = thisArg;

}

if (!Array.isArray(arr)) {

throw \"只能对数组使用forEach方法\";

}

for (let index = 0; index < arr.length; index++) {

fn.call(_this, arr[index], index, arr);

}

};

JavaScript实现手写 forEach算法 (https://www.wpmee.com/) javascript教程 第1张

标签:

提交需求或反馈

Demand feedback