前言
发表一下自己对面试的看法。首先面试跟实际水平是有偏差的,与工作经验是有关系的,但也不是正比关系。
手写代码跟手敲代码区别挺大的,手敲代码能进行调试,但是手写代码,会犯很多的低级错误,因为脱离了IDE带来的提示。不过手写代码是能够看出面试者的思路的,大部分的面试官基本只是看看思路以及逻辑严谨性。
另外,其实面试就是公司让你去造飞机,其实工作顶多拧拧螺丝钉。不过每个人知识的深度和广度,以及这个人的潜力,有经验的面试官是能够看出来的。这也是面试的时候offer到手的筹码,别以为背背面试题就可以了。平时多严格要求自己,多多得研究和总结。
进入正题
公司名字我都不写了基本都是大厂,有些公司的面试题我没有贴出来,都是烂大街的前端面试题,大家看看这篇文章就够了。
题目我也只是挑一些自己觉得比较经典的,分享给大家!
1、写一个函数fn(10),获取0-100以内的随机数传入一个数组,数组内不能重复出现想同的数字,数组长度为10(下面写的代码进行了一部分改造原题);
function getRandomNum(num,[min,max]){
let arr = [];
if(typeof num === "number" && typeof min === "number" && typeof max === "number"){
// console.log(num,min,max);
while(true){
let isExit = false;
let randomNum = parseInt(min + (max-min) * Math.random());
//方法一:
// for(let i = 0;i<arr.length;i++){
// if(arr[i]===randomNum){
// console.log(arr[i]);
// isExit = true;
// break;
// }
// }
//方法二:
let set = new Set(arr)
if(!set.has(randomNum)){
arr.push(randomNum);
}
if(arr.length === num) break;
}
}else{
throw Error("请传入Number类型!")
}
return arr;
}
let randomArr = getRandomNum(10,[10,100]);
console.log(randomArr);
2、给定这么一个数据:
let data = [{
id:1,
name:"北京"
},{
id:1,
name:"天津",
children:[{
id:1,
name:"西青"
},{
id:1,
name:"和平",
children:[{
id:1,
name:"和平一"
},{
id:1,
name:"和平二"
}]
}]
}]
写一个函数将数据内的key值name
和children
相应的替换成lable
和city
:
function dealData(arr){
if(arr instanceof Array){
let dealedArr = JSON.parse(JSON.stringify(arr));
let new_arr = changeKey(dealedArr);
return new_arr;
}else{
throw Error("请传入数组!")
}
}
function changeKey(arr){
let new_arr = arr.map((item,index)=>{
if(item.hasOwnProperty("name")){
item["lable"]=item["name"];
delete item.name;
}
if(item.children && item.children.length > 0){
item['city'] = changeKey(item.children);
delete item.children;
}
return item;
})
return new_arr;
}
let arr = dealData(data);
console.log(arr);
3、给一个数组如下
const tree = [ '1', ['2', '3'], [4, ["5",6]]];
需要改造成
let new_tree = ["1", "2", "3", 4, "5", 6];
这道题面试的时候,一眼看过去真的会懵逼,反正我是懵逼了。不过只要懂一些iterator的知识,其实并不难;
const tree = [ '1', ['2', '3'], [4, ["5",6]]];
function* iteratorTree(tree){
if(typeof tree === "string" || typeof tree === "number"){
yield tree;
}else if(tree instanceof Array){
for (const i of tree) {
yield* iteratorTree(i)
}
}
}
// for(const i of iteratorTree(tree)){
// console.log(i);
// }
console.log([...iteratorTree(tree)]);
4、说一说promise
的实现原理
so,我也是比较懵逼的,只是会用而已,恶补了下这篇文章;
5、js类型检测的各种方法及其对比
转移现场,看看这篇文章
6、vue双向绑定的基本原理
这个题我估计只要考到vue,基本都会考到吧!
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体步骤:第一步:需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter
这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
这种题一般考到之后,先问你了解发布者订阅者模式(观察者模式)
的实现,再问你是否了解其他的js的设计模式;
其实,只要我们曾经在DOM节点上绑定过事件函数,那我们曾经就使用过发布-订阅模式
;比如click
事件,给某个dom节点添加click
事件,其实就是订阅了这个dom
节点的click
事件,当这个dom
被点击的时候,就会像订阅者click
事件发布这个消息。
发布订阅者模式
我用得也很少,用得比较多的也就是单例模式
和代理模式
,遇到这种情况的了解哪一种说哪一种吧,别瞎比比就行。毕竟面试要求你什么都会,而实际工作需要用什么。js设计模式建议大家还是多看看,毕竟前人总结出来的好东西,不用一用,你还好意思说自己是站在巨人的肩膀上么?
7、vue生命周期钩子的理解(vue2.0+)
-
beforeCreate
:在实例初始化之后,数据观测(data observer)
和event/watcher
事件配置之前被调用。 -
created
:实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer)
,属性和方法的运算,watch/event
事件回调。然而,挂载阶段还没开始,$el
属性目前不可见。 -
beforeMount
:在挂载开始之前被调用:相关的 render 函数首次被调用,也就是说,render函数此时会生成虚拟的dom
节点,即vm.$el
; -
mounted
:el
被新创建的vm.$el
替换,并挂载到实例上去之后调用该钩子。如果root
实例挂载了一个文档内元素,当mounted
被调用时vm.$el
也在文档内。 -
beforeUpdate
:数据更新时调用,发生在虚拟DOM
重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。 -
updated
:由于数据更改导致的虚拟DOM
重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件DOM
已经更新,所以你现在可以执行依赖于DOM
的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。 -
beforeDestroy
:实例销毁之前调用。在这一步,实例仍然完全可用。 -
destroyed
:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。该钩子在服务器端渲染期间不被调用。
8、http状态码有哪些?
大概说说就行,这种状态码,其实工作中遇到了直接就给后台说一声就行,反正我从来不记,面试的时候,大致看看记了一下
100-199 用于指定客户端应相应的某些动作。
200-299 用于表示请求成功。
300-399 用于已经移动的文件并且常被包含在定位头信息中指定新的地址信息。
400-499 用于指出客户端的错误。400 1、语义有误,当前请求无法被服务器理解。
401 当前请求需要用户验证 403 服务器已经理解请求,但是拒绝执行它。
500-599 用于支持服务器错误。 503 – 服务不可用
9、谈一谈let
- 与var的不同在于,用let声明的变量只在 { } 内有效。这使得我们可以很方便的实现块级作用域,不再使用立即实行函数。
{
let a=1;
var b=2;
}
console.log(a); //undefined
console.log(b); //2
- let不会变量提升。也就是说,如果你使用var ,可以先使用变量再定义变量(注意:变量提升只提升声明不提升赋值操作),但是如果你使用let定义变量则必须先定义后使用,否则会报错。
console.log(a);//报错:Uncaught ReferenceError: a is not defined
console.log(b); //undefined
let a=1;
var b=2;
- 暂时性死区(Temporal Dead Zone),只要块级作用域内有let,let 声明的变量不受全局同名变量的影响,如果想要在块级作用域内使用let 声明的变量,只能为其赋值。
var a=1;
if(true){
a=2;
let a; //Uncaught ReferenceError: a is not defined
}
- 不允许在一个块级作用域内重复声明一个变量
不管是var与let重复声明,还是let与let重复声明,都会报错。
为了方便大家看,把原文复制了一遍给大家看,懒得去自己敲栗子了!
10、promise
的一个题
console.log('1');
setTimeout(function() {
console.log('2');
}, 0);
Promise.resolve().then(function() {
console.log('3');
}).then(function() {
console.log('4');
});
console.log('5');
顺序是:15342
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
new Promise(resolve => {
console.log(3)
resolve()
}).then(() => {
console.log(4)
})
console.log(5)
顺序是:13542
这个题也比较简单,注意一个是有new
关键字的,另一个是直接在then
方法中输出的,小心这个陷阱就OK了!
11、简单写一下数组中求差集的方法
考过这道题之后,自己回来总结了一下数组中的求交集、差集、并集的方法,包括es5
和es6
两种方式,大家可以去看看。
暂时先写到这儿吧!最近手头事儿比较多,还有面试,有经典的题再跟大家分享!
后续
持续更新
如果有更好的方法,请及时交流。
可加微信 YOYO_ZCC
。
最近在面试,多多交流;
[...面试题]
;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。