前端面试题

文章目录


前言

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


一、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
    }
}

相关推荐
DogDaoDao3 小时前
leetcode 面试经典 150 题:有效的括号
c++·算法·leetcode·面试··stack·有效的括号
桂月二二4 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062065 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb5 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角5 小时前
CSS 颜色
前端·css
浪浪山小白兔6 小时前
HTML5 新表单属性详解
前端·html·html5
lee5767 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579657 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me7 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者7 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架