前端面试题(亲身面试经验)

最近面试了一些公司,趁着疫情期间,总结一波。大家可以看看  会有用的。

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的问题

更多的会慢慢完善,我过滤掉了很多比如根据自己理解去解答的,代码题也过滤了不少,因为最近电话面试居多,程序题几乎没有。过滤了很多基础的,基本用过的人都会的。只取其中最经典,最常问,最有误区的,可能会由一些疏漏我之后想起来或者遇到再补上

相关推荐