一年前端面试打怪升级之路(四)

掘金传送门:

一年前端面试打怪升级之路(一)

一年前端面试打怪升级之路(二)

题外话:前面两篇文章掘金评论下展开了热烈的讨论,让我深切感受到掘金社区技术氛围的浓厚,同时也很感谢各位大佬对文章中的问题进行答疑解惑,感觉本菜鸟的前端经验值进度条又涨了不少~

之后面试了一家知名上市通信公司,一家A轮初创企业,一家跨境电商,其实有不少问题和前面的有重复,所以我会挑取其中有分享意义的部分,并且放在一个篇幅下。

其中跨境电商问的技术问题也不多,主要是想要了解我会使用哪些技术,然后针对做过的项目做了一些提问,两个面试官哥哥好像半小时不到就开始互相干瞪眼,没什么要问我了。。在我猜测我应该是GG了的时候,hr姐姐又进来了,拉着我聊了将近1个多小时。

通信公司

技术面+hr面,历时2个多小时

第一次遇到女性技术面试官,面试中全程板着脸,不太敢皮了,把我给紧张的...

项目问题

由于公司与我上家公司所在业务类似,所以对我所做的项目做了很多追问。这部分还是无压力的,每个人的项目都有自己不同的回答角度,答案并不是重点,重要的是有自己的思考过程

1. 项目用了什么技术?为什么要做这样的技术选型
2. (针对一个纯数据展示的地图平台)讲一下项目的数据流走向和传递
3. 期间遇到过什么难题,你是怎么解决的
4. 你觉得这个项目最复杂的地方在哪里
5. 你们部门的开发流程是怎样的

数组扩展问题

因为我所做的项目是处理大量的数据,会涉及到很多对数组的操作,面试官对数组进行了一些提问

1. 数组去重方法,至少两种
//第一种方法
function Fn(arr){
    if(Array.isArray(arr)){
        var newArr = [];
        newArr.push(arr[0]);
        for(var i = 0 ; i < arr.length ; i++)
         if( newArr.indexOf(arr[i]) < 0 ){
            newArr.push(arr[i]);
        }
        return newArr;
    }else{
        return arr + "不是数组"
    }
}

//第二种方法
[...new Set(arr)]
2. map和forEach有什么区别

返回值不同。两者都是对数组中的每一项进行操作。map返回新数组,forEach返回undefined

3. [1,2,3].push(4)返回什么

返回4,push返回的是新数组的长度

其他扩展问题

1. JQ中的eq(0)和first()方法哪个性能更好

我按我自己的理解,eq()应该是一个循环方法,而first相当于直接取第一个不需要遍历。我答first性能应该会更好一点。

后来去查了源码

first: function() {
    return this.eq( 0 );
}

eq: function( i ) {
    var len = this.length,
        j = +i + ( i < 0 ? len : 0 );
    return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
},

我???这不是一样吗,难道是eq效率更高?因为first多了一步引用?求解答...

2. 事件委托的应用场景
  1. 需要对一个页面中不存在的元素设置事件的时候,就需要把事件委托到父元素上。

  2. 还有就是当父元素下有多个标签的时候,也可以用事件委托,就不需要对每个标签设置事件

3. 闭包的应用场景

当需要一个变量常驻内存的时候。

看着面试官毫无波澜的表情真的很没底啊,我又补充了一句相当于是一个私有的概念,一个可以在全局访问的局部变量。

然后又说了工作中的使用场景,曾经使用闭包封装过一个工具

4. JQ和vue的使用场景,什么时候用jq什么时候用vue

没做过场景使用的归纳,知道个大概但是一时不知道怎么组织,偷换概念我答了JQ和vue的对比

JQ:数据和页面是耦合的,在原生的基础上提高了对dom元素的操作效率

vue:通过操作数据去渲染页面,不需要直接操作页面dom

后来去查了下也没什么特别出彩的,网上也尽是两者对比。按我理解的话,如果涉及到数据交互,vue更加便捷,但如果是展示性页面,还是JQ会更方便点

关于jQ和vue的讨论我其实听了很多观点。有些公司的人会认为JQ虽然现在依然是主流,但在MVVM大行其道的时代,它是一个迟早要被淘汰的框架(从2017年三大框架使用情况可以看出)。面试的时候会对我之前所在公司还在使用JQ表现出不解,甚至是不屑;而有些公司就不喜欢vue或者react比JQ更强的言论,认为需求至上,需要根据场景选择不同的框架。

天秤座的我就不站立场了,但是我很想听听来自各方不同的言论

5. vue的特色是什么

我答了组件通信和双向绑定,文档上也提到了这两点是vue的两个很强的优势所在。

不过面试官引导了一会儿,强调和JQ相比,vue的特色是什么,为什么虚拟DOM更好。我从性能角度上回答,她好像不太满意。难道是希望我回答跨平台?恩真的要回去撸vue底层了..

6. 优化性能的方法

其实原题她问的是,优化性能的步骤。我觉得步骤这个东西并没有一个标准,需求不同肯定侧重不同了,所以说了我的看法后,我回答的是优化性能的方法

  1. 尽可能减少对服务器资源请求的次数,制定合理的接口
  2. 尽可能减少资源的大小,比如优化图片、样式等资源
  3. 按需加载资源
  4. 尽可能减少页面dom的结构纵深,做到标签语义化
  5. 制定合理的标签Id和类名,避免使用复杂的css选择嵌套
  6. 合理使用预编译器
  7. 减少冗余代码,做好通用方法的封装
  8. 操作页面时尽量减少重排重绘的次数

前端性能优化其实是个很大的话题,当时脑子里没有一套体系,看上去回答了不少点,但是应该是比较乱。关于这个话题,我想我会再额外做一个总结。

下列是近期看的相关文章

2018前端性能优化清单(一)

2018前端性能优化清单(二)

2018前端性能优化清单(三)

2018前端性能优化清单(四)

7. 了解哪些设计规范

我实话实说,回答没有很系统地了解过,只知道一些基本的输入校验,兼容性处理,边界处理,条件封闭处理等等

这里有一篇我收藏的比较有意思的文章为什么你的工作经验不值钱,的确工作经验其实不能以年来衡量。在工作中总结,并且提高思维方式才是正确的打开方式

重复问题

1. 什么是重排重绘
2. 哪些操作会导致重排重绘

hr面

依旧是一个很严肃的大姐姐,问了一些常规问题,还让我讲了下成就感最高的项目,估计是想看看表达能力。期间有感受到她的施压,比如故意用严厉的语气指出你的缺点,然后问你觉得自己凭什么能胜任这份岗位。心理防线不坚挺的人估计就歇菜了。大公司的hr果然还是不好应付啊

电话面试

因为当时还处于在职的状态,请假成本有点高,所以这次我提出先进行电话面试,某天的中午面试官就打来了电话。历时约1个小时。

1. 如何区分JQ对象和js对象

instanceof可以区分,JQ对象的原型是jQuery

&elem instanceof jQuery
2. jQ的silce()方法实现原理

jQ的silice()是用于获取元素集合的指定子集

$elem.slice(0,2)  //获取选择对象的前两个元素

我第一反应是扩展数组slice方法。后来查了源码3.3.写法如下,应该也不算答错了吧?

slice: function() {
    return this.pushStack( slice.apply( this, arguments ) );
},
//pushStack应该是一个存储栈,用于存放选择的JQ对象
pushStack: function( elems ) {

        // Build a new jQuery matched element set
        var ret = jQuery.merge( this.constructor(), elems );

        // Add the old object onto the stack (as a reference)
        ret.prevObject = this;

        // Return the newly-formed element set
        return ret;
    },
3. 最近在看什么书,印象最深刻的一点是什么

《dom编程艺术》:渐进增强和平稳退化的概念,在处理兼容问题的两种开发思维

《js面向对象编程指南》:Js中的一切都是对象,从对象的角度重新认识了数据类型

4. vue的父子组件通信实现方式
  • 父——>子: 子组件中,使用prop用于接收父组件的data
props:{
    data1:{
        type:String,    //数据类型
        requried:true   //是否为必须字段
    }
}
  • 子——>父: 在子组件中利用事件监听,使用emit自定义指令,通过参数传递向父组件提交数据
methods:{
    event1(e){
        this.$emit('event1',e);
    }
}
5. ES6,for循环中let的作用域链是怎么样的

这个在阮老师的es6文档有详细的说明,运气也好这块有仔细读过

for循环的特殊情况在于,设置循环变量的那部分是一个父作用域,循环体是一个子作用域。

使用let声明循环变量,相当于每次循环都在创建一个新的变量,所以可以获取到每个步骤的变量

所以在我的这个系列第一篇一年前端面试打怪升级之路(一)中提到的输出1-10,其实最简单的方法就是使用let声明循环变量

for(let i = 1 ; i <= 10 ; i++){
    setTimeout(function(){
        console.log(i)
    });
}
6. 不使用插件,你会如何实现目录树

简历中有提到项目中使用了插件treeview

我说我自己写的话,应该会用遍历和递归吧

重复问题:

1. 垂直居中实现方式
2. vue双向绑定原理
3. 项目问题

之后我询问了他们的技术方向,人员构成等问题,这次面试就结束了。让我等待交叉面试通知。不过就没有然后了。

我觉得电话面试毕竟没有建立起你整个人的形象,除非回答的很出彩,否则很难给人留下很深的印象。多跑跑,没毛病

B轮互联网公司

主技术栈react,在这之前我完全没看过react,可以说是技术栈不符了。面试官在掘金上看到我的文章,还是让我过去聊了聊。

历时一个多小时,面完的感受DXY出来一致,互联网企业会问的非常细,看重底层和Js基本功

在自我介绍后,开始做现场笔试题,面试官就在旁边处理业务。随后的提问都是针对题目展开

1. promise的机制

下列处理有什么不对的地方,怎么处理(对promise不熟啊还有道题目没记下来)

var promise = new Promise promiseProject(){}
promise.then(function(){
    dosomething()
    
    dosomethingErrorExpect()
})

这里考察promise的成功和异常处理。之后展开的各种异步处理都不会,我说我只会ajax(捂脸)

map的参数,call

输出什么,说明理由

var arr = [" abc","c d "];

function F(arr){
    return arr.map(
    Function.prototype.call,
    String.prototype.trim
    )
}
F(arr)

结果是string的trim方法生效,输出["abc","c d"];

题目给了个提示,map的第2个参数代表回调的this。

首先,根据提示题目中的String,prototype.trim就是Function .prototype.callthis,这里的call作用就是改变this的指向,让回调Functionthis指向String,prototype.trim

map回调的第一个参数是遍历数组的元素elem,这里就是分别传入" abc""c d"

最后相当于是对每个elem执行了trim两边去空格的方法

输出["abc","cd"]

bind和call的区别是什么

变量提升

写出执行结果

var a = 1;
function F(){
    var a = 2;
    c = 4;
    return function g(){
        console.log(a++);
        console.log(b++);
        console.log(c++);
    }
}

console.log(a);
console.log(b);
console.log(c);

var b = 3;
var g = F();
g();

console.log(a);
console.log(b);
console.log(c);
分析:
  • step1. 这题涉及4个变量,a,b,c,g。其中a,b,g都使用关键字var声明,c没有使用关键字声明,相当于是一个全局window的属性,可在全局访问
  • step2. 使用var声明的变量,会把声明提前,所以全局中一开始就存在a,b,g,在对它们赋值前都是undefined
  • step3. 在第一次console.log的时候,a已被赋值,b已存在,但未赋值,c不存在(F()还未被调用)。这是程序执行到console.log(c);会报错,并停止执行
假设把报错语句console.log(c);注释掉,接下去会如何执行?
  • step4. 执行到var g = F()的时候,相当于调用F()函数,新增了c = 4,但是还没执行return中的内容
  • step5. 执行g(),也就是执行F()return中的内容。a++是先使用再赋值,这里先输出a的值,再进行自加操作。b和c同理。所以这一步的打印结果:2 3 4a b c实际值分别为3,4,5
  • step6. 也就是上一步的最后分析结果,打印结果为3 4 5
c和var a的区别

一个是创建全局属性,一个是声明变量

主要区别在于是不是有可删除属性

创建全局变量可以使用delete删除

声明变量不可以删除

translate内部机制。不会涉及重排重绘

//两段代码的区别,哪个更好
#elem{
    position:absolute;
    top:30px;
    left:100px;
    animation: myAnimate easing 4s
}

@keyframe myAnimate{
    50%{
        transfrom: translate(100px 100px)
    }
}
<!--  ——————  --!>
#elem{
    position:absolute;
    top:30px;
    left:100px;
    animation: myAnimate easing 4s
}

@keyframe myAnimate{
    50%{
        top:130px;
        left:200px
    }
}

Linux读写权限?

sudo chmod -R 777 /var/emmweb/
  • -R 文件夹以及文件夹下面所有的子文件夹

  • 777 读写执行

  • /var/emmweb/ : 操作的文件夹

权限码描述

sudo chmod 600 ××× (只有所有者有读和写的权限)
sudo chmod 644 ××× (所有者有读和写的权限,组用户只有读的权限)
sudo chmod 700 ××× (只有所有者有读和写以及执行的权限)
sudo chmod 666 ××× (每个人都有读和写的权限)
sudo chmod 777 ××× (每个人都有读和写以及执行的权限)

-R表示包含设置所有子目录

冒泡排序

遍历数组,每次进行相邻两个数比较,大的放在左边,小的放右边,遍历n次,保证所有区域处于有序状态

//大概写了一个
function sort(arr){
    if(Array.isArray(arr)){
        for(var i = 0 ; i < arr.length ; i++){     
            arr.forEach(function(ele,index){
                if(arr[index + 1] > ele){
                    var flag = arr[index];
                    arr[index] = arr[index + 1];
                    arr[index + 1] = flag;
                }
            });
        }
        return arr;
    }
}

优化下列代码

function F(){
    var count = 0;
    function g(){
        var elem = document.querySelector("#elem");
        elem.textContent = count++;
        setTimeout(g, 1000);
    }
    g();
}
F()

优化点:

  1. 闭包内定义变量,循环时重复创建,消耗大量内存(闭包内的变量常驻内存)。应该把变量提升到闭包外部
  2. 选择器querySelector可以换成getElementById,效率更高

推荐阅读更多精彩内容