前端面试题(亲身面试经验)
最近面试了一些公司,趁着疫情期间,总结一波。大家可以看看 会有用的。
webpack
1、webpack中entry和output的作用
webpack中的entry标记入口文件,它可以是一个字符串(单入口)或者一个数组(多入口),output用来标记输出,主要有两个属性 path和 filename。其次就是publicPath 和chunkFileName
2、webpack中loader和plugin的作用
loader 用于加载某些资源文件。 因为webpack 本身只能打包commonjs规范的js文件,对于其他资源例如 css,图片,或者其他的语法集,比如 jsx, coffee,是没有办法加载的。 这就需要对应的loader将资源转化,加载进来。loader是用于加载的,它作用于一个个文件上。 plugin 用于扩展webpack的功能。它直接作用于 webpack,扩展了它的功能。当然loader也是变相的扩展了 webpack ,但是它只专注于转化文件。而plugin的功能更加的丰富,而不仅局限于资源的加载。
3、webpack中可以有哪些优化
1、优化Loader的文件搜索范围,指定include、exclude 2、把Babel编译过的文件缓存起来 loader: ‘babel-loader?cacheDirectory=ture‘ 3、懒加载、按需加载、分包、压缩 4、提取公共代码与第三方代码 5、使用HappyPack插件开启多进程Loader转换 6、区分dev、product环境,减少不必要 7、减少不必要的编译,提升开发时构建速度(devServer.watchOptions.aggregateTimeout)
4、你是依据什么来确定你要优化哪个模块
我会使用一个webpack-bundle-analyzer的插件,这个插件(plugin常规使用,需要在package.json 中写npm_config_report=true)可以查看打包后文件包的大小,进而可以找出比较大的包,对他进行优化
5、webpack构建流程
1、初始化参数,从配置文件和shell语句中读到的参数合并,得到最后的参数 2、开始编译:用合并得到的参数初始化complier对象,加载是所有配置的插件,执行run方法开始编译 3、确定入口,通过entry找到入口文件 4、编译模块,从入口文件出发,调用所有配置的loader对模块进行解析翻译,在找到该模块依赖的模块进行处理 5、 完成模块编译,得到每个模块被翻译之后的最终的内容和依赖关系 6、输出资源,根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,在把每个chunk转换成一个单独的文件加载到输出列表、 7、输出完成,确定输出的路径和文件名,把内容写到文件系统中 简单版 初始化参数 -> 编译开始 -> 找到入口 -> 编译入口及其依赖 -> 完成编译确定关系 -> 输出资源 -> 输出完成
6、webpack热更新原理
首先可以这么理解 webpack是一个基于node的小服务器,这么就好理解了,简单来说: 1、watch 编译过程、devServer 推送更新消息到浏览器 2、浏览器接收到服务端消息做出响应 3、对模块进行热更新或刷新页面
7、webpack-dev-server和http服务器如nginx有什么区别?
webpack-dev-server使用内存来存储webpack开发环境下的打包文件,并且可以使用模块热更新,他比传统的http服务对开发更加简单高效。
8、happypack使用方法
module:{ rules:[{ test:/\.js$/, use:[‘happypack/loader?id=babel‘] exclude:path.resolve(__dirname, ‘node_modules‘) },{ test:/\.css/, use:[‘happypack/loader?id=css‘] }], plugins:[ new HappyPack({ id:‘babel‘, loaders:[‘babel-loader?cacheDirectory‘] }), new HappyPack({ id:‘css‘, loaders:[‘css-loader‘] }) ] }
js
1、array map和foreach的区别
map有返回值而且必须return返回一个数组才行,而forEach没有返回值可直接打印结果。
2、array some和filter的区别
some方法返回的是boolean值,可用于检察数组中是否有某对象 filter方法返回的是一个新数组,可用于过滤数组中的对象
3、new一个对象过程发生了什么
1、创建一个新对象,如:var person = {}; 2、新对象的_proto_属性指向构造函数的原型对象。 3、将构造函数的作用域赋值给新对象。(也所以this对象指向新对象) 4、执行构造函数内部的代码,将属性添加给person中的this对象。 5、返回新对象person。
4、数组排序的方式
主要有像快速排序、插入排序、冒泡排序
插入排序
方法:将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。 代码: var insertSort = function(arr) { var len = arr.length; var preIndex, current; for(var i = 1; i < len; i++){ preIndex = i - 1; current = arr[i]; while(preIndex >= 0 && arr[preIndex] > current){ arr[preIndex + 1] = arr[preIndex]; preIndex--; } arr[preIndex + 1] = current; } return arr; }
快速排序
方法 :基本原理是将数组内的数分成三组,取数组中间的数为基准,将较小数放在左边,较大数放在右边,分别将三类数存放在一个数组内,最后递归进行排序。 代码: function quickSort(arr) { if(arr.length<=1) { return arr; } let leftArr = []; let rightArr = []; let q = arr[0]; for(let i = 1,l=arr.length; i<l; i++) { if(arr[i]>q) { rightArr.push(arr[i]); }else{ leftArr.push(arr[i]); } } return [].concat(quickSort(leftArr),[q],quickSort(rightArr)); }
冒泡排序
方法:比较两个相邻的元素,如果后一个比前一个大,则交换位置,然后类推,每次比较完成后可以少比较最后一个 代码: function bubbleSort(arr) { for(let i = 0,l=arr.length;i<l-1;i++) { for(let j = i+1;j<l;j++) { if(arr[i]>arr[j]) { let tem = arr[i]; arr[i] = arr[j]; arr[j] = tem; } } } return arr; }
5、数组去重的几种方式
1、利用ES6 Set去重 代码: Array.from(new Set(arr))或者[...new Set(arr)]
2、健值去重法 原理: 利用对象的属性不能相同的特点进行去重 function unique(arr) { var arrry= []; var obj = {}; for (var i = 0; i < arr.length; i++) { if (!obj[arr[i]]) { arrry.push(arr[i]) obj[arr[i]] = 1 } else { obj[arr[i]]++ } } return arrry; }
3、includes function unique(arr) { var array =[]; for(var i = 0; i < arr.length; i++) { if( !array.includes( arr[i]) ) { array.push(arr[i]); } } return array } 高大上写法:(reduce+includes) function unique(arr){ return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[]); }
4、排序后相邻去除法 原理:sort之后会把所有元素排序,只需要比较当前和下一个元素是否完全一致,不完全一致则增加 function uniq(array){ array.sort(); var temp=[array[0]]; for(var i = 1; i < array.length; i++){ if( array[i] !== temp[temp.length-1]){ temp.push(array[i]); } } return temp; }
5、indexof 原理:如果当前数组的第i项在当前数组中第一次出现的位置不是i,那么表示第i项是重复的,忽略掉。否则存入结果数组。 function uniq(array){ var temp = []; for(var i = 0; i < array.length; i++) { //如果当前数组的第i项在当前数组中第一次出现的位置是i,才存入数组;否则代表是重复的 if(array.indexOf(array[i]) == i){ temp.push(array[i]) } } return temp; }
6、字符串出现最多的次数
1、对象法 原理:利用js健值对唯一的特性,如果包含key则value++ function countStr(str){ var obj = {}; for(var i = 0, l = str.length,k; i < l ;i++){ k = str.charAt(i); if(obj[k]){ obj[k]++; }else{ obj[k] = 1; } } var m = 0,i=null; for(var k in obj){ if(obj[k] > m){ m = obj[k]; i = k; } } return i + ‘:‘ + m; }
7、reduce两个参数分别是什么?
第一个参数是一个函数,函数包含4个参数分别是:total(本次总和、初始值)、currentValue(当前元素)、currentIndex(当前index)、arr(原数组) 第二个参数是一个初始值
8、怎么可以将一个包含id的数组变成一个用id做key的json
arr.reduce((total,currentItem) => { total[currentItem.id] = currentItem; return total },{})
9、下面这个代码段输出什么
console.log(1); setTimeout(function(){ console.log(2) }) new Promise(function(res,rej){ console.log(3) res() }) .then(function(){ console.log(4) }) .catch(function(){ console.log(5) }) async function a(){ console.log(6); await console.log(7); console.log(8) } a(); console.log(9) 1,3,6,7,9,4,8,2 本题花式比较多,遵循 同步>异步 微任务>宏任务
10、promise有几种状态?
三种,分别是pending、fulfilled、rejected(未决定,履行,拒绝),resolve时候会由padding->fulfilled,reject会由padding->rejected
11、聊聊原型链
这个。。。不知道怎么描述,也不给个题目,大致说下我是怎么说的吧 js是一个万物皆对象的语言,每一个对象都会包含一个原型对象,对象以原型为模版,从他的原型继承方法和属性。当然对象也是可以由原型的,一层一层类似于一个链条,我们叫做原型链。比如举个例子,man->person->object
12、判断是不是array
1、instanceof (检测构造函数的 prototype 属性是否出现在某个实例对象的原型链) a instanceof Array
2、原型prototype + toString + call() Object.prototype.toString.call(a) === "[object Array]" Object.getPrototypeOf(a) === "[object Array]"
3、原型prototype + isPrototypeOf() Array.prototype.isPrototypeOf(variable)
4、 Array.isArray
13、判断是不是对象
1、原型prototype + toString + call() Object.prototype.toString.call(obj) === ‘[object Object]‘ Object.getPrototypeOf(b).toString() === "[object Object]"
2、instanceof(需要处理array) a instanceof Object && !(a instanceof Array)
14、闭包是什么?怎么写?
主要是为了从函数外部访问函数内部的变量,且这个变量永远不会被内存回收(会回收闭包内函数的变量不会回收闭包的变量),写法为函数内reuturn一个function
15、null和undefined的区别?
1、null是一个表示”无”的对象,转为数值时为0;undefined是一个表示”无”的原始值,转为数值时为NaN。 2、undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义。 3、null表示”没有对象”,即该处不应该有值。
16、前端几种数据存储方式
localstorage、sessionstorage、cookie、websql等
17、localstorage和sessionStorage以及cookie的区别
首先是cookie他的存储大小对比local和session会小很多,local他会一直存在于浏览器内存,即使窗口关闭或者是浏览器关闭哪怕是新开一个页面local都是共享的。而session他是属于会话级的,我们哪怕打开相同的页面,session也不会共享过来。
18、解决跨域的方式
1、通过jsonp跨域(callback) 2、CORS (服务端配置Access-Control-Allow-Origin) 3、domain+iframe(做法:页面写一个iframe,将sec设置为要跨域的域名,通过document.getElementById(‘iframe‘).contentWindow.$,来取到对应的$,再通过$.ajax调用即可,不过仅限于主域名相同)4、nginx5、iframe
19、eventloop介绍
js是一个单线程的东西(为什么单线程自己百度), 在JavaScript中,存在一个执行栈,当我们调用一个函数时,JavaScript引擎会将函数push到执行栈(Stack)中, 执行完毕之后pop出去(先进先出),当遇到异步任务时,就将任务放进任务队列中。异步任务有两种,一种叫做宏任务一种叫做微任务, 当主线程执行完毕,也就是执行栈为空时,会先确认微任务队列是否为空,不为空就取出微任务队列中所有微任务然后顺序执行,微任务执行完毕后 会在宏任务队列中取出下一个任务添加到执行栈,如果这时候再遇到微任务继续加到微任务队列,如此循环。
20、对象深拷贝方式
1、最简单的方式 JSON.parse(JSON.stringify(Obj)) 这种方法使用较为简单,可以满足基本的深拷贝需求,而且能够处理JSON格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深拷贝(而且会直接丢失相应的值)。
2、jQuery深拷贝 var copiedObject = $.extend(true, {}, originalObject)
3、手动写递归方式 function deepClone (obj) { var newobj = obj.constructor === Array ? [] : {}; if(typeof obj !== ‘object‘){ return; } for(var i in obj){ newobj[i] = typeof obj[i] === ‘object‘ ? deepClone(obj[i]) : obj[i]; } return newobj }
21、object.assign 是深拷贝还是浅拷贝
Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。 也就是说,如果对象的属性值为简单类型(如string, number),通过Object.assign({},srcObj); 得到的新对象为深拷贝;如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的。
22、聊聊平时你都会做哪些优化
这个比较多了,我之前总结过。可以移步https://www.cnblogs.com/jinzhenzong/p/11777065.html,可以整理一下剪短的话语。 我们可以分为几块去说,首先是缓存方面,我们可以借助如:localstorage、sessionStorage、cookie、浏览器自带缓存、h5 minifest等对数据、ajax接口、页面等进行缓存, 在请求时,我们应该尽量减少重定向,做一些dns预解析、提高浏览器并发数等。之后就是页面解析和渲染时候,可以做关键路径的渲染,避免一下复杂的布局,用flex替换原始的布局模型 还有一些像长列表的尾优化啊、图片懒加载啊,尽量的减少js的占用时间,比如使用jq时候我们经常会遇到一些操作样式或者设置内容等,这个时候我们可以对dom进行缓存和批量处理,来减少操作dom和回流重绘等。比如我们 需要监听用户输入时候都要做一下事件的防抖节流,还有一些如减少闭包(为了变量能够被内存回收机制回收)、使用性能好的api如requestanimationframe代替settimeout、setinterval等。还有一块比较大的就是 资源的处理,更多还是借助webpack,比如像分包、代码合并、文件的缓存、小图片base64等,还有像综合考虑是否使用框架、图片选质量比较低的,视频的预加载等。 ps:强烈建议去看我原文章至少吧xmind下下来注释都看一编,不然深入问一下你就废了
23、前端打开页面的过程
同样之前总结过https://www.cnblogs.com/jinzhenzong/p/11753559.html 大概过程如下: 1、输入网址 2、浏览器解析ip 3、通过ip发起链接 4、服务器接受请求 5、处理请求并返回 6、页面渲染 -> 解析HTML文件,创建DOM树(解析执行JS脚本时,会停止解析后续HTML) 解析CSS,形成CSS对象模型 将CSS与DOM合并,构建渲染树 布局渲染树 绘制渲染树(可能触发回流和重绘) 7、渲染完毕 经过几个公司问题总结出,一般多会问第六步。前面就是听听而已。
24、promise,async函数区别与理解
promise 将原来的用 回调函数 的 异步编程 方法转成用relsove和reject触发事件, 用 then 和 catch 捕获成功或者失败的状态 async函数就相当于自执行的Generator函数,async函数就相当于自执行的Generator函数,较于Promise,async 的优越性就是把每次异步返回的结果从 then 中拿到最外层的方法中,不需要链式调用,只要用同步的写法就可以了。 比 promise 直观,但是 async 必须以一个 Promise 对象开始 ,所以 async 通常是和Promise 结合使用的。
js的比较多,我只是选了一部分比较常问的,其他还有像设计模式、mvc、mvvm、jq组件、还有各种数组对象的方法的作用参数等就不总结了
vue篇
1、watch和计算属性区别
watch适合处理的场景是,侦听一个数的变化,当该数据变化,来处理其他与之相关数据的变化(该数据影响别的多个数据) computed适合处理的场景是,获得一个值或者结果,该结果受其他的依赖的影响。(一个数据受多个数据影响)
2、watch也可以实现监听为什么还要计算属性这个?
1、首先vue官方推荐的是如果可以用计算属性就不要用watch,为什么呢?因为计算属性是有缓存的,对性能来说是更好的。 2、其次两种功能是不一样的,computed针对于一个值依赖于多个,watch虽然也可以,但是代码会很臃肿
3、data为什么是一个函数?
组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。
4、双向数据绑定原理?
利用了 Object.defineProperty() ,让数据变得可观测,结合订阅者发布者模式,当数据变化时候会由发布者来通知订阅者更新数据和view vue3利用proxy来让数据可观测。
5、组件怎么实现双向数据绑定
组件 内部props写上value,watch value进行数据的初始化,data中声明一个newvalue,监听这个newvalue,如果页面发生变化使用emit/on向上抛出input事件
6、生命周期(8+3)官网去看
7、extend和混合
extend用于创建vue实例 mixins可以混入多个mixin,extends只能继承一个 mixins类似于面向切面的编程(AOP),extends类似于面向对象的编程
8、什么是单向数据流
数据流向是单向的,即父能给子,子不能修改父,写组件时我都会声明一个类似box的东西,当我子组件发生变化我都会通知到box再由box进行分发,这么做可以保证出现bug时候的调试
9、事件通信
父子:props 子父:emit/on 同级:新声明一个vue作为中间,来转发。或者原生写一个eventbus 跨层级:provide 和 inject(不建议日常开发使用,主要在开发高阶插件/组件库时使用。)、同级的在这里也可以使用
10、vue中 key 值的作用
1、不加key的话,更新机制的进行diff的时候是会全部比较的,对性能不好。 2、不加可能会出现数据变化而没有渲染视图 3、根据diff算法,同一层级的一组节点,他们需要通过唯一的id进行区分。
11、vue中如果数据更新了视图没有渲染怎么办
一般这种都会出现在json\jsonArray中,需要使用$set(目标,要修改的key,值),如果是层级特别多可能set还是不会生效,需要this.$forceUpdate();
12、如果我需要在视图更新后进行一些操作可以怎么做
$nextTick
13、vue v-show/v-if
v-show控制display,适用于切换频繁的场景 v-if如果为false,页面都不会渲染这个dom,适用于不经常切换的场景。
14、vue-router有哪几种导航钩子
1、全局的 beforeEach、afterEach 2、单个路由独享的beforeEnter 3、页面级的 beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
15、hash和history路由
hash 值变化不会导致浏览器向服务器发出请求 hash改变会触发 hashchange 事件(hashchange只能改变 # 后面的url片段) hash发生变化的url都会被浏览器记录下来,从而你会发现浏览器的前进后退都可以用了 history 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。但是history路由需要后端配置 如果路由找不到,就始终返回一个html,不然会由刷新404的问题
更多的会慢慢完善,我过滤掉了很多比如根据自己理解去解答的,代码题也过滤了不少,因为最近电话面试居多,程序题几乎没有。过滤了很多基础的,基本用过的人都会的。只取其中最经典,最常问,最有误区的,可能会由一些疏漏我之后想起来或者遇到再补上