美文网首页编程让前端飞
web前端JS高阶面试题

web前端JS高阶面试题

作者: 程序员阿野 | 来源:发表于2021-03-16 17:13 被阅读0次

    如何在 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
    }
    

    相关文章

      网友评论

        本文标题:web前端JS高阶面试题

        本文链接:https://www.haomeiwen.com/subject/oegrcltx.html