• 前端面试题



    前言

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


    一、lodash.get方法?

    问题:
    object (Object): 要检索的对象。
    path (string): 要获取属性的路径。
    [defaultValue] (*): 如果解析值不存在,会返回 default。

    用例:

    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'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    分析:

    • lodash.get是用来解决一些链路调用问题,使其能够在各种情况下都返回正确的值,而不是报错。
    • 官方API中path不止是string还可以是Array,也就是使用连续字符串也可以使用。
    • 在这里先将path进行处理,处理成数组[‘a’, ‘0’, ‘b’, ‘c’]形式,然后通过迭代去取值
    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    二、实现一个EventEmitter.js

    问题:
    实现一个 EventEmitter。

    用例:

    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')
    // 预期没有输出
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

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

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

    • on:注册事件监听器,接受两个参数,第一个参数是事件名称,第二个参数是事件监听器。
      先判断是否存在该事件,不存在旧创建空数组并将事件处理函数添加到数组中
    • once:注册事件监听器,只会触发一次,触发后会自动移除。
      本质还是调用on方法,只不过事件处理函数会被额外包裹一层,其中事件处理函数最后会调用off方法,
      off方法,会根据传入事件处理函数名称来去除不是 callback 的函数,这样旧形成了只调用一次
    • emit:按照注册的顺序同步调用为名为传入名称的事件注册的每个侦听器
      循环遍历事件集合,执行事件处理函数
    • removeListener:移除事件监听器,接受两个参数,第一个参数是事件名称,第二个参数是事件监听器。
      直接移除事件对象对应的属性
    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]// 删除事件
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    三、渲染VNode.js

    问题:
    写个函数用来渲染这个结构
    就是将一个虚拟DOM渲染成真实DOM的过程

    用例:

    const renderJSON = {
        type: 'div',
        props: {
            className: '',
        },
        childrens:[
            {
                type: 'p',
                props: {
                    text:'xxxxx'
                },
                childrens:['xxxx']
            }
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    分析:

    • type:标签名
    • props:属性名,是个集合可能包含许多属性,需要遍历挂载,需要处理行内样式和值的绑定
    • childrens:子元素,子元素分为两种:一种为标签元素,另外一种为文本元素
      查看结构每个对象中都会包含childrens用来储存该DOM下的层级关系,通过递归的形式进行渲染
    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下
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    四、设计一个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自动将最久未被使用过的那个对象剔除, 腾出空间放置新对象;

    用例:

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    分析:

    • set方法,在设置缓存对象时,我们首先将其封装成一个对象 { obj, timestamp },其中 timestamp 表示缓存对象的时间戳,用于判断对象是否过期。
      然后,我们将该对象存储在缓存中,当缓存中不存在该缓存时将其唯一标识添加到 LRU 链表的末尾。检查缓存的大小,如果超过了最大大小,则自动删除最早添加的缓存对象;如果存在,则更新 LRU 链表位置,以及缓存时间戳信息。
    • get 方法用于获取缓存对象,它接受一个参数 id,表示要获取的缓存对象的唯一标识。
      在获取缓存对象时,我们首先检查该对象是否存在,如果不存在,则返回 不存在。
      如果存在,则检查该对象是否过期,如果过期,则从缓存中删除该对象,并返回 ‘已过期’。
      否则,我们将该对象移动到 LRU 链表的首部,并返回缓存对象。
    • delete 方法用于删除缓存对象,它接受一个参数 id,表示要删除的缓存对象的唯一标识。
      在删除缓存对象时,我们首先从缓存中删除该对象,并从 LRU 链表中删除该对象的唯一标识。
    • delete方法,用于删除缓存及标识
    • _checkSize方法,用于删除最久未使用的
    • _moveToFront方法,用于更新LRU链位置
    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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

  • 相关阅读:
    动态内存管理
    Python学习基础笔记十一——编码
    tensorflow-卷积神经网络-图像分类入门demo
    JavaScript定义函数,创建函数实例时的内部原理
    ESP8266-Arduino编程实例-TMP175数字温度传感器驱动
    COMP1721 Object-Oriented Programming
    qmake source code 解读
    气象数据库分析
    小项目-词法分析器
    南开大学计算机考研资料汇总
  • 原文地址:https://blog.csdn.net/smznbhh/article/details/132675031