美团实习前端一二面面经
一面
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)))
后来问了我对工作公司、工作地点、实习时间,然后介绍了美团点评上海的研发中心,面试官在的到店部门。到店的一些主要部门什么的和技术栈。
反问了要是不通过还有机会面试美团吗?面试关口头答复说这面是通过了,还有一轮面试,那轮面试不会问很细,他是倾向于我能过是最好的,要是不通过会推荐给其他部门。
#前端##美团##前端工程师##实习##面经#