前端面试题

文章目录


前言

本文主要记录在面试过程中,所遇到的题目。


一、lodash.get方法?

问题:

object (Object): 要检索的对象。

path (string): 要获取属性的路径。

[defaultValue] (*): 如果解析值不存在,会返回 default。
用例:

javascript 复制代码
const object = { 'a': [{ 'b': { 'c': 3 } }] };

console.log(_get(object, 'a[0].b.c'));
// => 3

console.log(_get(object, ['a', '0', 'b', 'c']));
// => 3

console.log(_get(object, 'a.b.c', 'default'));
// => 'default'

分析:

  • lodash.get是用来解决一些链路调用问题,使其能够在各种情况下都返回正确的值,而不是报错。
  • 官方API中path不止是string还可以是Array,也就是使用连续字符串也可以使用。
  • 在这里先将path进行处理,处理成数组['a', '0', 'b', 'c']形式,然后通过迭代去取值
javascript 复制代码
function _get(object, path, defaultVal='default') {
    // 在这里实现
    let newPath = [] //存放预处理的path
    if (Array.isArray(path)){// 如果传入路径为数组形式直接赋值不用处理
        newPath = path
    }else {// 处理path为数组,利用replace替换'[]'为'.',利用split将字符串分割成字符数组
        newPath  = path.replace(/\[/g,'.').replace(/\]/g,'').split('.')
    }
    return newPath.reduce((o,k)=>{//通过reduce迭代newPath找路径没找到则返回defaultVal
        // { a: [ { b: [Object] } ] } a
        // [ { b: { c: 3 } } ] 0
        // { b: { c: 3 } } b
        // { c: 3 } c
        return (o ||{})[k]
    },object) || defaultVal
}

二、实现一个EventEmitter.js

问题:

实现一个 EventEmitter。
用例:

javascript 复制代码
const eventEmitter = new EventEmitter()

function callback() {
    console.log('hit!')
}

// 监听事件, 其中有一个 once 单次监听
eventEmitter.on('custom-event', callback)
eventEmitter.once('custom-event', callback)

// 连续触发两次
eventEmitter.emit('custom-event')
eventEmitter.emit('custom-event')
// 预期输出 3 次 "hit!"

// 删除并再次=触发
eventEmitter.removeListener('custom-event');
eventEmitter.emit('custom-event')
// 预期没有输出

这里查了一下EventEmitter属于node服务端events模块对外提供的一个EventEmitter对象,用于对Node.js中对事件进行统一管理,表示没学Node,根本不知道啊,只知道浏览器事件EventTarget,不过二者都差不多,都是用来对事件进行处理的,不过浏览器事件会存在冒泡,因为在Node中不存在层级关系,浏览器DOM是存在层级关系的,且浏览器事件是基于观察者模式的,而EventEmitter的事件是基于发布订阅模式的。

分析:

需要编写一个类,实现内部方法on、once、emit、removeListener

  • on:注册事件监听器,接受两个参数,第一个参数是事件名称,第二个参数是事件监听器。
    先判断是否存在该事件,不存在旧创建空数组并将事件处理函数添加到数组中
  • once:注册事件监听器,只会触发一次,触发后会自动移除。
    本质还是调用on方法,只不过事件处理函数会被额外包裹一层,其中事件处理函数最后会调用off方法,
    off方法,会根据传入事件处理函数名称来去除不是 callback 的函数,这样旧形成了只调用一次
  • emit:按照注册的顺序同步调用为名为传入名称的事件注册的每个侦听器
    循环遍历事件集合,执行事件处理函数
  • removeListener:移除事件监听器,接受两个参数,第一个参数是事件名称,第二个参数是事件监听器。
    直接移除事件对象对应的属性
javascript 复制代码
class EventEmitter {
    constructor() {
        this.events = {}// 存储事件
    }
    // 在这里实现
    on(event,callback) {// 监听
        if (!this.events[event]){// 是否存在该事件
            this.events[event] = []// 不存在创建一个空数组
        }
        this.events[event].push(callback)// 将事件处理函数添加到数组集合中
    }
    once(event,callback){// 单次监听
        const wrapper = () => {// 在外包裹一层,使调用时同时清除该次事件处理函数
            callback();
            this.off(event);
        };
        this.on(event, wrapper);
    }
    off(event){
        if (!this.events[event]) {
            return;
        }
        this.events[event] = this.events[event].filter((cb) => cb!== callback);
    }
    emit(event){// 触发事件
        if (!this.events[event]) {
            return;
        }
        this.events[event].forEach((callback) => callback());// 循环执行事件
    }
    removeListener(event){
        if (!this.events[event]) {
            return;
        }
        delete this.events[event]// 删除事件
    }
}

三、渲染VNode.js

问题:

写个函数用来渲染这个结构

就是将一个虚拟DOM渲染成真实DOM的过程
用例:

javascript 复制代码
const renderJSON = {
    type: 'div',
    props: {
        className: '',
    },
    childrens:[
        {
            type: 'p',
            props: {
                text:'xxxxx'
            },
            childrens:['xxxx']
        }
    ]
}

分析:

  • type:标签名
  • props:属性名,是个集合可能包含许多属性,需要遍历挂载,需要处理行内样式和值的绑定
  • childrens:子元素,子元素分为两种:一种为标签元素,另外一种为文本元素
    查看结构每个对象中都会包含childrens用来储存该DOM下的层级关系,通过递归的形式进行渲染
javascript 复制代码
const render = (renderJSON) =>{
    const {type, props, childrens} = renderJSON //将三个参数结构出来
    let el = document.createElement(type)// 创建标签元素
    for (let key in props){// 挂载属性
        el.setAttribute(key,props[key])//设置属性上的值,这里没有考虑行内样式以及绑定值的处理
    }
    //创建子节点
    childrens.forEach(child =>{
        if (child instanceof Object){//如果为标签元素
            el.appendChild(render(child))//将子元素添加到父元素内部末尾处,递归创建子元素
        }else {//如果为文本
            let textNode = document.createTextNode(child)//创建一个文本节点,将文本塞入
            el.appendChild(textNode)//添加文本结点到父元素内部
        }
    })
    return el
}

document.body.appendChild(render(renderJSON))// 挂载到body下

四、设计一个Cache.js

问题:

设计一个 Cache

支持下列两个基本操作:

  • set(id, object), 根据id设置对象;
  • get(id): 根据id得到一个对象;
    同时它有下面几个性质:
  1. x秒自动过期, 如果cache内的对象, x秒内没有被get或者set过, 则会自动过期;
  2. 对象数限制, 该cache可以设置一个n, 表示cache最多能存储的对象数;
  3. LRU置换, 当进行set操作时, 如果此时cache内对象数已经到达了n个, 则cache自动将最久未被使用过的那个对象剔除, 腾出空间放置新对象;
    用例:
javascript 复制代码
const cache = new Cache(2,3)
cache.set(1,{name:'smz1'})
cache.set(2,{name:'smz2'})
cache.set(1,{name:'smz3'})
setTimeout(()=>{
    console.log(cache.get(1))// 已过期
},4000)

分析:

  • set方法,在设置缓存对象时,我们首先将其封装成一个对象 { obj, timestamp },其中 timestamp 表示缓存对象的时间戳,用于判断对象是否过期。
    然后,我们将该对象存储在缓存中,当缓存中不存在该缓存时将其唯一标识添加到 LRU 链表的末尾。检查缓存的大小,如果超过了最大大小,则自动删除最早添加的缓存对象;如果存在,则更新 LRU 链表位置,以及缓存时间戳信息。
  • get 方法用于获取缓存对象,它接受一个参数 id,表示要获取的缓存对象的唯一标识。
    在获取缓存对象时,我们首先检查该对象是否存在,如果不存在,则返回 不存在。
    如果存在,则检查该对象是否过期,如果过期,则从缓存中删除该对象,并返回 '已过期'。
    否则,我们将该对象移动到 LRU 链表的首部,并返回缓存对象。
  • delete 方法用于删除缓存对象,它接受一个参数 id,表示要删除的缓存对象的唯一标识。
    在删除缓存对象时,我们首先从缓存中删除该对象,并从 LRU 链表中删除该对象的唯一标识。
  • delete方法,用于删除缓存及标识
  • _checkSize方法,用于删除最久未使用的
  • _moveToFront方法,用于更新LRU链位置
javascript 复制代码
class Cache {
    constructor(maxSize = 10,maxAge = 60) {
        this.maxSize = maxSize // 最大缓存数
        this.maxAge = maxAge// 最长过期时间
        this.cache = {}// 缓存列表
        this.lruList = [] // 缓存唯一标识
    }
    set(id,obj){
        const item = this.cache[id];// 在缓存中查找是否存在
        const timestamp = Date.now() // 存储建立的时间
        this.cache[id] = {obj, timestamp} // 封装成对象存储在缓存中
        if (!item){// 不存在
            this.lruList.push(id)// 将唯一标识添加到链表末尾
            this._checkSize()// 检查缓存大小
        }else {// 存在
            this._moveToFront(id);//将该id标识移动到最后面
        }
    }
    get(id){
        const item = this.cache[id];// 在缓存中查找是否存在
        if (!item) {// 不存在返回提示
            return '不存在';
        }
        if (Date.now() - item.timestamp > this.maxAge * 1000) {// 判断是否过期,过期删除并返回提示
            this.delete(id);
            return '已过期';
        }
        this._moveToFront(id);//将该id标识移动到最后面
        return item.obj;
    }
    delete(id){// 过期删除缓存及标识
        delete this.cache[id];// 删除缓存
        this.lruList = this.lruList.filter((item) => item!== id);// 移除标识
    }
    _checkSize() {// 缓存满删除缓存
        if (this.lruList.length > this.maxSize) {// 大于了最大储存数时
            const id = this.lruList.shift();// 返回第一个元素
            delete this.cache[id];// 在缓存中删除
        }
    }

    _moveToFront(id) {// 更新id标识位置
        const index = this.lruList.indexOf(id);// 指定元素下标
        if (index!== -1) {// 存在
            this.lruList.splice(index, 1);//移除id标识旧位置
            this.lruList.push(id);// 将id标识添加到链表最后面
        }
    }

    getList(){// 返回存储集合
        return this.cache
    }
}

相关推荐
As977_几秒前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu10830189112 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾4 分钟前
前端基础-html-注册界面
前端·算法·html
Dragon Wu6 分钟前
前端 Canvas 绘画 总结
前端
CodeToGym11 分钟前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫12 分钟前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫16 分钟前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
Cwhat17 分钟前
前端性能优化2
前端
熊的猫1 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
瑶琴AI前端1 小时前
uniapp组件实现省市区三级联动选择
java·前端·uni-app