web前端JS高阶面试题

问题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)
})
}
}
/

  1. 取消消息订阅

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)

文末

技术是没有终点的,也是学不完的,最重要的是活着、不秃。

零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。

最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。

自学最怕的就是缺乏自驱力,一定要自律,杜绝"三天打鱼两天晒网",到最后白忙活一场。

高度自律的同时,要保持耐心,不抛弃不放弃,切勿自怨自艾,每天给自己一点点鼓励,学习的劲头就会很足,不容易犯困。

技术学到手后,找工作的时候一定要好好准备一份简历,不要无头苍蝇一样去海投简历,容易"竹篮打水一场空"。好好的准备一下简历,毕竟是找工作的敲门砖。

拿到面试邀请后,在面试的过程中一定要大大方方,尽力把自己学到的知识舒适地表达出来,不要因为是自学就不够自信,给面试官一个好的印象,面试成功的几率就会大很多,加油吧,骚年!

相关推荐
Мартин.6 分钟前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
不知所云,1 小时前
qt cmake自定义资源目录,手动加载资源(图片, qss文件)
开发语言·qt
昨天;明天。今天。2 小时前
案例-表白墙简单实现
前端·javascript·css
数云界2 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd2 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常2 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
阑梦清川2 小时前
Java继承、final/protected说明、super/this辨析
java·开发语言
ChinaDragonDreamer2 小时前
Vite:为什么选 Vite
前端
小御姐@stella2 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing2 小时前
【React】增量传输与渲染
前端·javascript·面试