请自我介绍一下
面试官您好,我是xxx,今年xx岁,毕业于xxx院校,毕业已经x年了,主要从事的工作方向是前端开发工程师。先后任职两份工作岗位,第一家公司叫做xxx公司,主要的业务是xxx,使用到的技术框架是xxx。第二家公司叫做xxx公司,主要的业务是xxx,使用到的技术框架是xxx。本人目前处于离职状态,在寻找一份新的工作岗位,这就是我的一个基本情况。
请问离职的原因是什么
避免吐槽前公司,可以往自己的发展规划方向来描述。
接下来问一些前端的基本知识,描述一下CSS的盒模型
CSS的盒模型主要分为两种,标准盒模型和怪异(IE)盒模型。可以通过CSS的box-sizing属性来定义。标准盒模型:content-box,怪异盒模型:border-box,默认为content-box。
标准盒模型的宽度计算主要是根据定义的width属性,也就是内容区域content,不计算padding和border。
怪异盒模型的计算等于 content+border+padding,需要计算padding和border。
说说平常开发的时候,响应式布局可以通过什么方式实现
目前最常用的方法是flex布局,其他的方法还可以通过媒体查询、单位使用百分比、使用rem作为单位、grid、珊格布局等来实现。
你说到使用flex布局,说说对它的理解,平常使用的flex:1是什么意思?
当使用flex布局时,首先会想到两根轴线,主轴和交叉轴。
主轴通过row-direction
来定义,主要有4个属性,分别是row/row-reserve/column/column-reserve
。而交叉轴是垂直于主轴的。
定义了display: flex
的元素,其子元素可以定义flex
属性,该属性主要由3个属性组成,分别是flex-grow,flex-shrink,flex-basis
。
flex-grow: 放大比例,用于定义当容器中存在剩余空间时,该子元素的放大比例,默认值为0。
flex-shrink: 缩小比例,用于定义容器空间不能够容纳所有子元素时,该元素的缩小比例,默认值为1
flex-basis: 基本长度,用于定义该子元素项目占据的主轴空间,默认值为auto。
当定义flex:1 时,实际是将flex-grow定义为1,即将放大比例设置为1。flex: 0 1 auto
为属性的默认值。
使用百分比作为单位,会有什么问题?
外边距属性margin和内边距属性padding取百分比值的时候,无论是left、right或top、bottom,都是以其父元素的宽值(width)作为参照物(即分母top/width),这是W3C的规范。
所以在使用以上属性时,需要注意以上问题,避免达到不符合预期的UI效果。
rem和em的区别是什么?
em是基于当前元素的父元素的fontSize属性的比例来取值的,如果父元素没有定义该属性,则往上继续查找,直至根节点。
rem是基于body标签的fontSize属性比例来取值的。
好的,接下来我问些JS相关的知识,说说数组的reduce方法,作用是什么,参数有什么。
reduce方法常用与计算数组的求和。
reduce方法有两个参数,第一个参数是callback,第二个参数是默认值initialValue。
callback函数也接收2个参数,第一个参数是上一次循环返回的结果,第二个参数是当前遍历的item值。
当没有传入initialValue时,遍历会从第二项开始,一共遍历arr.length - 1
次。遍历的第一次,callback的参数数值分别是数组的第一项和数组的第二项。
当传入了initialValue时,遍历会从第一项开始,一共遍历arr.length
次。遍历的第一次,callback的参数数值分别是传入的默认值和数组的第一项。
// 复制到控制台输出即可一目了然
let arr = [1,2,3,4,5,6];
arr.reduce((x, y) => {
console.log('y', y, 'x', x);
return y - x;
})
arr.reduce((x, y) => {
console.log('y', y, 'x', x);
return y - x;
}, 5)
遍历数组的方法有哪些,各自的区别是什么
forEach/map/filter/some/find/findIndex
- forEach 接收三个参数,item: 当前遍历项,index: 当前遍历项的下标值, array: 当前遍历的数组。没有返回值。
- map 接收三个参数,item: 当前遍历项,index: 当前遍历项的下标值, array: 当前遍历的数组。返回每一次遍历返回的值组成的数组。
- filter 接收三个参数,item: 当前遍历项,index: 当前遍历项的下标值, array: 当前遍历的数组。返回每一次遍历返回值为true的项组成的数组。
- some 接收三个参数,item: 当前遍历项,index: 当前遍历项的下标值, array: 当前遍历的数组。返回一个boolean值,在所有的遍历中有任意一项返回true,则为true,否则为false。
- find 接收三个参数,item: 当前遍历项,index: 当前遍历项的下标值, array: 当前遍历的数组。返回item,当遍历返回的值为true时,则返回当前项的值,当有多项均返回true时,则返回第一符合项的值。
- findIndex 接收三个参数,item: 当前遍历项,index: 当前遍历项的下标值, array: 当前遍历的数组。返回index下标值,当遍历返回的值为true时,则返回当前项的下标值,当有多项均返回true时,则返回第一符合项的下标值。
简单说一下对事件循环的理解
事件循环机制的出现,主要是为了处理异步任务。
什么是异步任务,异步任务通常来描述一些无法立即得到结果的任务,例如定时器,请求等。
JS是单线程的,在浏览器的渲染线程中执行,假设所有的任务都是同步的任务,当遇到耗时较久的任务时,浏览器就会产生阻塞的现象,导致卡顿,非常影响用户的体验。所以有了异步任务的概念。
在浏览器的事件机制中,有一个执行栈,还有一个任务队列。在解析js时,会生成一个任务,放到执行栈中执行,在执行的过程中,同步任务会正常执行,当遇到异步任务时,会将异步任务放入到任务队列中。当执行栈中的任务执行完了以后,会去任务队列中查找已经执行完的异步任务,将其取出创建一个任务放入到执行栈中,再回到执行栈中执行,如此反复,则为事件循环。
说一说对闭包的理解,举例应用场景
理解闭包,从以下几个点:
第一点是理解作用域。作用域分为两种,全局作用域和局部作用域。创建作用域的方法有很多,例如常见的if/函数/for等等,在浏览器环境下,一般认为window是全局作用域。当我们在全局作用域下创建一个局部作用域,例如定义了一个函数A,函数A的内部,则称为局部作用域。一般情况下,局部作用域内可以访问当前作用域内的属性,也可以访问全局作用域下的属性。但是全局作用域是不能访问局部作用域内的属性。
第二点是怎么实现从全局作用域访问局部作用域的属性,这就需要通过闭包来实现了。如上例子,当我在函数A内部再定义一个函数B,则又产生了一个局部作用域,该作用域可以访问3层作用域的属性(当前作用域,函数A作用域,全局作用域),再将函数B返回出去。在全局环境下,定义一个变量C,变量C的值为执行函数A后得到的返回值,即函数B。通过这样的方式得到的变量,就可以对3层作用域的属性进行访问,从而实现了从全局作用域访问局部作用域属性的方法。
第三点,理解闭包。函数B和它所在的作用域结合在一起的组合,就称为闭包。本质上,闭包是用于全局作用域和局部作用域沟通的桥梁。使用闭包可以实现对某些特定变量的保护和保存。
场景:防抖节流的原理,JS设计模式单例模式的设计原理,bind方法的原理等。
说一说对原型和原型链的理解
个人认为,原型就是一个对象,用于存放公用的变量和方法的对象。所有的引用数据类型的变量,都会有一个__proto__
属性,该属性指向其对应的原型。
原型通常随着构造函数一起定义,使用构造函数创造出来的实例,都会有__proto__
属性,指向构造函数的原型。所以在原型中定义好公用的方法和变量,创建的实例都会继承,通过原型链的方式可以进行访问。
什么是原型链?当一个对象变量在访问一个属性时,会先在自身查找是否存在对应属性,如果存在则直接引用,如果不存在,则会通过原型链的方式,找到其对应的原型,在原型对象上再次查找对应的属性。
前面说到,所有的引用数据类型的变量都会有自己的原型,原型本身也是一个对象,它也有自己的__proto__
属性,当在原型上也没有找到对应的属性时,则会继续通过原型链向上查找,直到原型指向null。(Object对象的原型的__proto__属性指向null)
主要技术框架是vue,请你说说vue2双向绑定的原理是什么
vue2双向绑定是通过数据劫持+发布订阅模式(观察者模式)来实现的。
第一点,数据劫持,基于Object.defineProperty,遍历vue实例中的data属性,对data中的每一项数据进行数据劫持。
第二点,编译模版,该阶段会对产生副作用的函数进行订阅,记录依赖关系。通过解析template,遍历所有的html节点,过程中能够判断出对应的节点是否绑定data中的变量。
第三点,发布订阅模式,当对应的元素绑定了data中的变量时,需要将data中的变量进行读取,然后在元素中对应的位置进行替换,我们将替换操作定义为watcher,在读取变量的时候会触发数据劫持的get方法,在get方法中将watcher的操作订阅起来,放入到dep的数组中。当data中的变量发生改变时,会触发数据劫持的set方法,在set方法中循环遍历执行dep数组中的方法,从而实现对视图的更新。
vue3和vue2的区别是什么
主要的区别有以下几点:响应式原理的升级/组合式API/ts的类型支持/生命周期更新/服务器渲染
响应式原理从Object.defineProperty变成了用es6 proxy的方式来实现。不再需要遍历整个对象进行逐个的劫持绑定,改为直接对整个对象进行劫持。
组合式API的开发模式,让组件化的开发模式更为显著,有以下几个优点,响应式API ref/reactive,每一个组件有自己的生命周期钩子,代码结构更加清晰灵活,更好的逻辑复用。
在以往的的选项式API模式下,复用的逻辑代码通常会用mixins的方式来实现抽离,当然也带来了很多问题,比如变量命名重复的问题,多个mixins一起使用时变量的来源问题等等,逻辑看起来更加的复杂,而组合式API就很好的解决了这些问题。
ts的类型支持更加的友好,我认为最大的原因还是归功于组合式API,用一种类似于函数式编程的方式,主要利用基本的变量和函数,它们本身就是类型友好的。因此也能够更加的适配ts的类型支持。
选项时API和组合式API都有一套属于自己的生命周期钩子,在vue3中新增了例如错误的捕获等钩子,在组合式API的生命周期钩子中使用 setup 代替了 beforeCreate 和 created,同时将 beforeDestory/destoryed 改为 beforeUnmounted和unmounted。
当然vue3中对vue2都进行了兼容处理。
前端缓存的方式有哪些?
localStorage、sessionStorage、cookie、indexDB、http缓存
http缓存分为强缓存和协商缓存,协商缓存一定会与服务器进行通信,而强缓存会判断是否命中缓存时间,如果命中则直接取出浏览器中的缓存数据进行使用。
做过什么性能上的优化
性能优化的方式有很多中,从在浏览器中输入url到页面渲染完成的整个过程中,每一个步骤基本都有能够优化的事情。先简单说下优化的方式有哪些。
减少http的请求,通过合并css文件合并图片,通常将项目中固定的icon合并成一张图片,在前端中也被称为雪碧图。
减少请求资源的体积,压缩文件,压缩图片。
处理数据,通过分页、异步、缓存的方式。
渲染相关,通过懒加载、减少dom操作。
代码层面,防抖节流、监听事件清楚消除、事件委托。