美团实习前端一二面面经
一面
1、介绍一下webpack和Node.js
Webpack是一个现代的JavaScript应用程序的静态模块打包器(module bundler)。
四个核心概念:entry、output、loader、plugins
Node.js:Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎。
2、v-if和v-show的区别
两者都是在视图层进行条件判断视图展示。
v-if是根据判断条件来动态的渲染标签,增删DOM元素。
v-show是根据判断条件来动态的显示和隐藏元素。
频繁的DOM操作会影响页面加载速度和性能,因此v-show的性能比v-if好
3、简单的理解Vue diff算法和Virtual Dom的原理
DOM操作非常昂贵,为了减少对DOM操作,Vue.js将DOM抽象成一个以JavaScript对象为节点的虚拟DOM树,以VNode节点模拟真实DOM节点,可以对虚拟DOM节点进行创建、删除以及修改等操作,这个过程不需要操作真实DOM。当数据发生改变时,Vue.js会使用diff算法比对OldVNode和VNode,得出最小修改单位,对真实DOM打补丁。
当数据进行更改时,会触发update函数,该函数会接收一个VNode对象,利用__patch__
方法比对VNode和OldVNode,并对真实DOM打补丁,__patch__
的核心就是diff算法。diff算法是通过同层的树节点进行比较而非对树进行逐层搜索,时间复杂度只有log(n)。
diff算法:
1、使用sameVnode判断是否为相同节点(tag、key、isComment是否相同)
2、不是相同节点则在真实DOM上创建新的DOM,移除旧的DOM
3、是相同节点则调用patchVnode函数:
- 找到Vnode和OldVnode对应的真实DOM,即el
- 判断是Vnode和oldVnode是否指向同一节点,是则返回
- 两者都有文本节点切不相等,将el的文本节点设置为Vnode的文本节点
- oldVnode有子节点,Vnode没有,则删除el的子节点
- oldVnode没有子节点,Vnode有,将Vnode子节点真实化后添加到el
- 都存在子节点,调用updataChildren函数比较子节点
4、对子节点调用updataChildren函数:
- 将Vnode的子节点Vch和oldVnode的子节点oldCh提取出来
- Vch和oldCh都有StartIdx和EndIdx变量,2个变量相互比较。
- 若4种比较都未匹配,如果设置了key,利用key进行比较
- 比较过程中变量会向中间移,出现StartIdx>EndIdx则表示至少存在一个遍历完成,结束比较
4、手写:ajax请求,解释状态码含义
var xhr = new XMLHttpRequest(); xhr.open('get',url); xhr.setRequestHeader(header); xhr.send(data); xhr.onreadystatechange = function(){ if(xhr.status === 200 && xhr.readyState === 4){ console.log(xhr.responseTest) } }
ajax的5种状态(readyState):
- 0 : 未初始化,还未调用open方法
- 1 :载入中,已调用sned方法发送请求
- 2 :载入已完成,send方法执行完成,已收到全部响应信息
- 3 :交互,正在解析响应内容
- 4 :完成,响应内容解析完成
5、项目有用过Promise吗?说说Promise
项目中用了axios,是基于promise封装的ajax。
promise主要用于异步状态,可以将异步状态操作队列化,按照希望的顺序执行,解决回调地狱
promise有有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)
.then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise
对象的状态变为resolved
时调用,第二个回调函数是Promise
对象的状态变为rejected
时调用。其中,第二个函数是可选的,不一定要提供。
.catch
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
6、为什么使用Vuex,作用是什么
项目中的登录是使用vuex实现的,vuex存储的后台返回的token以及权限控制所需要的用户组信息
Vuex是一个专门为Vue.js应用程序开发的状态管理模式,解决了不同组件之间的数据共享和数据持久化。
Vuex的五个核心概念:
- State:保存着store中的变量
- Getter:对store的数据派生出状态,可以理解为store中数据的计算属性
- Mutation:提供更改store中数据的方法,是同步函数
- Action:异步方法,提交Mutation,通过Mutation更改状态
- Module:状态树过大时将store分割成module,每个module都有自己的state、mutation、action、getter
7、display的常用属性
none,block,inline,table,inherit,flex,grid
display:none和visibility:hidden的区别
visibility:hidden可以隐藏元素,但隐藏的元素仍需要占用与未隐藏之前一样的空间
display:none可以隐藏元素,隐藏的元素不会占用任何空间
8、手写:实现垂直居中的方法
<div class="a"> <div class="b"> </div> </div>
1、父元素相对定位,子元素绝对定位,利用margin:auto
.a{ width:100px; height:100px; background-color:green; position:relative; } .b{ width:50px; height:50px; background-color:gray; position:absolute; top:0; left:0; right:0; bottom:0; margin:auto; }
2、父元素设置flex布局,设置flex-direction和justify-content属性,子元素设置align-self属性
.a{ width:100px; height:100px; background-color:green; display:flex; flex-direction:column; justify-content:center; } .b{ width:50px; height:50px; background-color:gray; align-self:center; }
3、父元素设置相对定位,子元素绝对定位使用transform和translate
.a{ width:100px; height:100px; background-color:green; position:relative; } .b{ width:50px; height:50px; background-color:gray; position:absolute; top:50%; left:50%; transform: translate(-50%,-50%) }
4、父元素设置table属性,子元素设置table-cell然后设置vertical-align属性
.a{ width:300px; padding:100px; background-color:green; display:table; } .b{ height:100px; background-color:gray; display:table-cell; vertical-align:center; }
9、手写:CSS 实现一个三角形
利用边框均分原理
div{ width:0; height:0; border:100px solid; border-color:red transparent transparent transparent; }
10、JS的基本数据类型
值放置在栈中,有Number、String、Undefined、Null、Symbol、Boolean
引用数据类型:值放置在堆内存中,栈中保存指向该数据的指针 Object,Array,Function
11、判断变量类型
判断是不是number:使用typeof和Object.prototype.toString.call()
判断是不是Array:使用instanceof和Object.prototype.toString.call()
12、let、const和var的区别
变量提升和块级作用域,let会绑定当前作用域,const常量不可更改
13、git常用命令
git config //配置详细 git init //初始化仓库 git remote //管理远程仓库 添加、删除和修改远程分支的关联 git status //显示文件状态,红色表示已修改未提交暂存区,绿色表示已提交暂存区 git add //将文件提交到暂存区 git commit //将暂存区修改提交到本地仓库 git push //将本地更新推送远程主机 git pull //获取远程主机某个分支的更新 git branch //进行分支管理,查看、创建和删除分支 git checkout //切换分支 git merge //分支合并 git diff //比较修改 git reset //将本地仓库的修改撤回暂存区
14、linux常用命令
ls、cd、pwd、mkdir、rm、rmdir、mv、cp、cat、more、grep、tail、chmod、kill、free、ps ps aux|grep xxx
反问:问了一下登录权限控制是怎么实现的
让我去了解一下jwt、token和session,权限控制一般都是后台实现的
后面回顾录音感觉没几个问题顺利答完都结结巴巴的,ajax请求码完全说错了,可能我答的比较自信,面试官也没追究,diff面试完才知道有这个东西,对虚拟dom的理解在看完vue diff这块源码后发现其实是错误的,查漏补缺吧_(:з」∠)_,我居然能过一面也是挺意外的
二面
二面一上来自我介绍,然后面试官就开始出题,做了一个小时的题(:з」∠)
除了第一题我后面都写错了或者概念糊涂,面试官给我讲了40分钟的题,好温柔啊
我同学说他问题没答上来向问问答案,腾讯面试官非常冷漠的让他自己去查
EventLoop
for(var i = 0; i < 3; i++) { setTimeout(function() { //问:console输出结果 console.log(i); }, 1000); } // 输出3 3 3
for(var i = 0; i < 3; i++) { setTimeout(function() { //问:console输出结果 console.log(i); }, 0); } //输出3 3 3
for(let i = 0; i < 3; i++) { setTimeout(function() { //问:console输出结果 console.log(i); }, 0); } //输出0 1 2
我答了前两题答了222和222,面试官居然没发现我的错误
然后解释了一下宏任务和微任务,js的事件循环机制
宏任务:script(整体代码)、setTimeOut、setInterval、postMessage、I/O、UI交互等
微任务:promise.then、process.nextTick等
运行机制:
- 主线程在执行栈中执行同步代码(宏任务)
- 主线程中之外,事件触发线程管理着任务队列(包括宏任务队列和微任务队列)
- 异步执行有了运行结果,就在任务队列中放置一个事件,宏任务事件结果放到宏任务队列,微任务事件结果放到微任务队列
- 执行栈中的同步代码执行完毕后,主线程就会读取任务队列将可运行的任务添加到可执行栈,开始执行
- 执行完一个宏任务后,会先添加微任务队列中所有微任务到执行栈中,全部执行完之后,在添加宏任务队列中的宏任务
第一题和第二题setTimeOut是个异步宏任务,因此js会将for循环执行完成后再执行conso.log(i),此时的i已经完成了三次++操作,所以输出3 3 3
第三题i使用let声明,let命令会绑定作用域,因此输出的是0 1 2
this指向
var obj = { xo: 'obj', method: function() { var xo = 'method'; //问1:console输出结果 console.log(this.xo); function test() { var xo = 'test'; //问2:console输出结果 console.log(this.xo); } test(); } }; function call1(method) { method(); } call1(obj.method); // undefined // undefined obj.method(); // obj // undefined
this指向:
- 一般函数this指向全局,严格模式下是undefined
- 箭头函数的this继承自定义该箭头函数的宿主对象
- 函数内的this指向调用该函数的对象
- 构造函数里的this指向创建出来的实例
- 使用bind、call、apply绑定的this指向绑定对象
上述call1函数中method是独自调用,因此指向全局,全局中无xo变量,所以打印出undefined
obj.method() 调用method方法的是obj对象,所以this指向obj对象,this.xo的值为 'obj' ;test()是独自运行的,因此this指向全局,输出undefined
变量提升&this&原型链
function Foo() { getName = function () { console.log(1); }; return this; } Foo.getName = function () { console.log(2); }; Foo.prototype.getName = function () { console.log(3); }; var getName = function () { console.log(4); }; function getName() { console.log(5); } //问:出以下输出结果 Foo.getName(); // 2 getName(); // 4 Foo().getName(); // 1 getName(); // 1
这道题被我蒙对了3个=- =
Foo.getName();
运行的是function () { console.log(2); };
,会先查找实例本身属性,若未找到再原型链上查找,因此输出2
getName();
var getName = function () { console.log(4); }; function getName() { console.log(5); } // 存在函数提升和变量提升,函数提升在变量提升之上 // 上述两句代码真正的执行顺序如下 function getName() { console.log(5); } var getName; getName = function () { console.log(4); };
所以最终getName被function () { console.log(4); };
覆盖了,输出4
Foo().getName();
首先Foo()执行的返回值是this,所以这个可以看做是this.getName(),此时this指向全局
全局范围内的this.getName就是getName = function () { console.log(4); };
但是Foo()中的getName = function () { console.log(1); };
没有对getName使用let声明,所以此时全局的getName被Foo中的function () { console.log(1); };
覆盖了,所以输出1
getName();
这个getName和this.getName是同一个变量,因此输出和Foo().getName();
一样,都为1
手写代码
浏览器打开一个页面,在控制台执行脚本,获取页面TagName种类
刚刚开始写了
var list = document.querySelectorAll('*') var res = new Set() list.forEach(node=>res.add(node)) console.log(res)
发现打印出来的是所有node,而不是tagName,虽然用了set但是由于每个node都不唯一,没有起到过滤的作用。后来查了查怎么获取node的tagName属性
var list = document.querySelectorAll('*') var res = new Set() list.forEach(node=>res.add(node.tagName)) console.log(res) // 面试官让我简化代码,弄成一句话代码,= =,不知道咋简化了 var r = new Set() console.log(document.querySelectorAll('*').forEach(n=>r.add(n.tagName)))
后来问了我对工作公司、工作地点、实习时间,然后介绍了美团点评上海的研发中心,面试官在的到店部门。到店的一些主要部门什么的和技术栈。
反问了要是不通过还有机会面试美团吗?面试关口头答复说这面是通过了,还有一轮面试,那轮面试不会问很细,他是倾向于我能过是最好的,要是不通过会推荐给其他部门。
#前端##美团##前端工程师##实习##面经#