最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 三月面试复盘及思考

    正文概述 掘金(qiugu)   2021-03-28   640

    写之前

    想说说自己为何写这么一篇。笔者学历渣渣,虽然三年经验,不过都是小厂,虽然一心向往,不过此前基本没什么机会,也是赶着正好三年这个节骨眼上,试着去冲下大厂,相信很多小伙伴应该是跟我一样的想法。然后就接到了某大厂面试机会,嗯,最后失败了,不过觉得收获还是挺多的,也算是走出了迈向大厂的第一步吧。具体流程就不说了,就聊聊面试题,感受一下和之前面试的区别。

    面试的一些题

    HTML5的新标签section和article的语义是什么

    这里回答的并不好,就说了section表示某块功能区域,article表示具体的内容,就是从单词字面意思解释了一下,后来翻了一下whatwg文档,上面是这么描述section和article的。

    简单翻译一下就是section元素表示文档的通用节,也就是按照主题进行划分的一块区域,通常会带上一个标题。而article则表示文档、页面、应用程序或站点中的完整或自包含的组合,是可独立分发或可重用的。举例来说article可以表示论坛帖子、杂志或报纸文章、博客条目、用户提交的评论、交互式小部件或小工具,或者任何其他独立的内容,内部嵌套的article也需要和外部的article内容上是相关联的,但是作者相关的信息则不适合使用article,而可以使用address元素。

    CSS选择符的优先级以及如果元素带有一个id属性和10个class属性,那么声明CSS的时候,哪个优先级更高

    CSS选择符的优先级我们都知道,id选择符 > class选择符、属性选择符、伪类选择符 > 标签选择符、伪元素选择符 > 通用选择符。那么如上描述一个id和10个class的选择符,哪个优先级更高呢?

    当时就有点懵了,因为我回答的是id代表100优先级,class代表10优先级,那10个class理论上是不是应该能代替id呢。觉得这么一算好像又不太合理。后来查阅了《CSS权威指南》,上面所谓100、10的优先级称为特指度,特指度的比较是从左往右的,也就是说一个id选择符的特指度是100,写成0,1,0,0,而10个class选择器的特指度是100,但是写成0,0,100,0,从左往右比较的话,0,1,0,0永远是大于0,0,100,0的,这里class选择符叠加的再多,也不会有进位,因此1个id选择符的优先级大于任意数量的class叠加的优先级。

    使用bind返回的函数作为构造函数执行,this是指向了什么

    这里就是考察队bind实现的一个理解,所以先看下bind方法是如何实现的:

    Function.prototype.myBind = function(context, args) {
      const self = this;
      const params = Array.prototype.slice.call(arguments, 1);
      function FNO() {}
      function Fn() {
        const bindParams = Array.prototype.slice.call(arguments);
        // 如果当前this是函数的实例,则此函数作为构造函数使用,因此其上下文指向了实例,否则的话其上下文就是指定的context
        return self.call(this instanceof Fn ? this : context, params.concat(bindParams));
      }
    
      FNO.prototype = this.prototype;
      Fn.prototype = new FNO();
    
      return Fn;
    }
    

    通过这个实现问题就很明显了,如果bind返回的函数作为构造函数来调用的话,那么this并不会指向设置的那个this,而是指向了构造函数的实例。我们可以用一个例子来证明一下:

    var obj = {
        name: 'qiugu'
    };
    
    function Person(age) {
        this.age = age;
    }
    
    // 注意这里new后面要加括号,不然会报错,因为Person.bind不能作为构造函数调用
    const p = new (Person.bind(obj, 20));
    console.log(p);
    
    

    三月面试复盘及思考

    好了,清晰明了,根据bind的实现,也可以看到作为构造函数调用时,也就是this instanceof Fn这里,this就指向了构造函数的实例了。

    new调用的构造函数如果有返回值的话,返回什么,如果返回值是一个对象或者是基础类型的话又返回什么

    这里也是和上面一样,关键还是要知道new的一个实现,来复习下new的实现:

    function objFactory(fn, ...args) {
      // 生成一个空对象
      const obj = new Object();
      // 将对象的原型链接到构造函数的原型上,这么做就能使对象访问到构造函数原型上的属性方法
      obj.__proto__ = fn.prototype;
      // 执行构造函数,利用call将上下文指向刚刚创建的对象,这样就能访问this上的属性方法
      const res = fn.call(obj, ...args);
      // 如果构造函数有返回值的话,需要判断返回值的类型是否是对象,如果是对象就返回这个对象
      // 如果是基础类型,则还是返回创建的对象
      return typeof res === 'object' ? res : obj;
    }
    

    那么答案也很明显了,所以有的时候,面试官可能并不会直接去问你new怎么实现,你也许背过,也许正好看到过,换个其他的方式更能证明是否真正理解了其中的原理。

    数组去重的方法

    这个也比较简单,但是如果想要面试官更加满意的话,那么以下方法都是要理解的(小声bb:我当时就记得三种了)。

    1. 双重for循环
    function unique(arr) {
      if (!Array.isArray(arr)) return;
      let res = arr[0];
      for(let i = 1; i < arr.length; i++) {
        let flag = true;
        for(let j = 0; j < res.length; j++) {
          flag = true;
          if (arr[i] === res[j]) break;
        }
        if (flag) res.push(arr[i]);
      }
      return res;
    }
    
    1. indexOf
    function unique(arr) {
      if (!Array.isArray(arr)) return;
      let res = [];
      for(let i = 0; i < arr.length; i++) {
        if (res.indexOf(arr[i]) !== -1) {
          res.push(arr[i]);
        }
      }
      return res;
    }
    
    1. filter
    function unique(arr) {
      if (!Array.isArray(arr)) return;
      return arr.filter((item, index) => arr.indexOf(item) === index);
    }
    
    1. sort
    function unique(arr) {
      if (!Array.isArray(arr)) return;
      arr.sort();
      let res = [];
      for(let i = 0; i < arr.length; i++) {
        if (arr[i] !== arr[i-1]) res.push(arr[i]);
      }
      return res;
    }
    
    1. reduce
    function unique(arr) {
      if (!Array.isArray(arr)) return;
      return arr.reduce((prev, cur) => {
        return prev.includes(cur) : prev : [...prev, cur];
      }, []);
    }
    
    1. Set
    function unique(arr) {
      if (!Array.isArray(arr)) return;
      return [...new Set(arr)];
    }
    
    // 也可以使用Array.from来把Set转成数组
    function unique(arr) {
      if (!Array.isArray(arr)) return;
      return Array.from(new Set(arr));
    }
    
    
    1. 使用对象或者Map去重
    function unique(arr) {
      if (!Array.isArray(arr)) return;
      let obj = {}, res = [];
      for(let i = 0; i < arr.length; i++) {
        if (!obj[arr[i]]) {
          res.push(arr[i]);
          obj[arr[i]] = 1;
        } else {
          obj[arr[i]]++;
        }
      }
      return res;
    }
    

    参考来源

    TypeScript中的any和unknown的区别

    我用过ts,但是没用过unknown,所以没回答上来,回去翻了资料。

    简单说下,any可是是任何类型的父类或者是子类,是类型不安全的,什么是类型不安全?很好理解,就是平常我们懒得写定义,直接any,让编译器忽略检查any类型的值,这么做就会产生一些意想不到的情况,导致代码出错,而且很难去排查。而unknown则是类型安全的,unknown也是可以赋值任何值,但是当我们进行使用它进行一些操作的时候,比如把unknown类型的值作为一种方法来调用,编译器就会报错,因为你不确定这个变量是不是个方法,因此是不能调用的,需要在调用前确保它可以被调用。

    let fn:unknown = () => {};
    if(typeof fn === 'function') {
        fn();
    }
    

    这个时候再去调用,编译器就不会报错了,所以是类型安全的。

    Hooks的实现,以及为什么采用这种方式实现,useState的状态怎么存储的

    关于React中的源码实现,自己只是看过一些分析的文章,实际还是缺少深入的了解。

    这里自己只回答了Hooks是用链表去实现的,所以如果把Hooks放入判断条件中,会破坏链表的结构。采用链表结构,也是从链表本身出发,链表对空间要求低,不需要连续的空间,链表添加删除操作效率高。关于useState如何存储状态,我们知道React中使用Fiber作为一种结构来存储组件的状态,所以useState中的状态也存储在节点对应的Fiber上。

    Hooks的极简实现

    手写EventEmit的实现

    这道题也没有做出来,虽然有了解过,但是并没有注意其实现,后面就认真的看了其实现,然后自己再写了一遍,去理解其实现原理。

    function EventEmit() {
      this.listeners = {};
    }
    
    EventEmit.prototype.on = function(eventName, cb) {
      // 因为事件是可以重复注册的,所以需要用一个数组来存储事件回调的队列
      if (!this.listeners[eventName]) {
        this.listeners[eventName] = [cb];
      } else {
        this.listeners[eventName].push(cb);
      }
    }
    
    EventEmit.prototype.once = function(eventName, cb) {
      if (!this.listeners[eventName]) {
        this.listeners[eventName] = [cb];
      } else {
        this.listeners[eventName].push(cb);
      }
      // 使用一个标记来标明这是一个一次性的事件回调
      this.listeners[eventName].once = true;
    }
    
    EventEmit.prototype.off = function(eventName) {
      if (this.listeners[eventName]) {
        this.listeners[eventName] = null;
      }
    }
    
    EventEmit.prototype.emit = function(eventName, args) {
      if (this.listeners[eventName]) {
        this.listeners[eventName].forEach(fn => fn.apply(this, args));
        // 如果这个是一次性的事件的话,执行完成后销毁该事件
        if (this.listeners[eventName].once) this.off(eventName);
      }
    }
    
    

    算法题

    /**
     * 把一日划分为48段,每段是半个小时,用一个48位的位图表示一日中的多个工作时间段
     * 按如下输入输出设计一个方案,计算一日中的有效工作时间段
     * 输入'110011100000000000000000111100000000000000000000'
     * 输出['00:00-01:00', '02:00-03:30', '12:00-02:00']
     */
    function solution(bitmap) {
        let p = 0, res = [], ans = [];
        for(let i = 0; i < bitmap.length; i++) {
            if (bitmap[i] === '0') p++;
            else if (bitmap[i] === '1' && (bitmap[i+1] === '0' || !bitmap[i+1])) {
                res.push([p, i]);
                p = i+1;
            }
        }
        const format = (left, right) => {
            const timeZone = new Date().getTimezoneOffset() * 60 * 1000;
            const leftTime = new Date(left / 2 * 60 * 60 * 1000 + timeZone);
            const leftHours = leftTime.getHours() < 10 ? '0' + leftTime.getHours() : leftTime.getHours() + '';
            const leftMinus = leftTime.getMinutes() < 10 ? '0' + leftTime.getMinutes(): leftTime.getMinutes() + '';
            const leftStr = `${leftHours}:${leftMinus}`;
            const rightTime = new Date((right / 2 + 0.5) * 60 * 60 * 1000 + timeZone);
            const rightHours = rightTime.getHours() < 10 ? '0' + rightTime.getHours() : rightTime.getHours() + '';
            const rightMinus = rightTime.getMinutes() < 10 ? '0' + rightTime.getMinutes(): rightTime.getMinutes() + '';
            const rightStr = `${rightHours}:${rightMinus}`;
            return [leftStr, rightStr];
        }
        for(let i = 0; i < res.length; i++) {
            const item = format(res[i][0], res[i][1]);
            ans.push(item);
        }
        return ans;
    }
    console.log(solution('110011100000000000000000111100000000000000000011'));
    
    

    这题是后面自己才做出来的,实现思路就是将上面48位的位图,转换成这样一种结构

    // 每个子数组存储对应的1的索引区间
    [[0, 1],[4,6],[24,27]]
    

    所以如何去转成这样一种结构才是这题的关键,虽然自己力扣也写了有200题,但是还是忽略了思考的过程,这也是此次感受最深的地方。

    写到最后

    自己将一些API实现代码整理好放在了这里

    上面这些只是面试的一部分,并且是自己回答的不够好或者是没有回答出来的,总的感受就是:并不是你会就可以了,还需要真正理解,所谓真正理解就是无论题目怎么变,原理还是那些,只有真正搞明白原理,才不会被问倒。

    关于算法,也是参考了很多社区大佬的学习方法,分类刷题什么的,但是还是忽略了一些东西,比如思考的过程,如何推导,是否还有其他方法等等,而不是看答案对不对。所以其实刷题并不是让我们能有足够的题目数量的经验,而是培养解决问题的思考过程,如何思考,如果思路不正确的话,能否换个思路去解决,这才是该去关心的。

    最后希望此篇能给各位正在准备面试的小伙伴一些经验和思考。


    下载网 » 三月面试复盘及思考

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元