前端工程师面试题库
前端工程师面试题库
1, 说一说cookie sessionStorage localStorage 区别?
得分点
数据存储位置、生命周期、存储大小、写入方式、数据共享、发送请求时是否携带、应用场景
标准回答
Cookie、SessionStorage、 LocalStorage都是浏览器的本地存储。
它们的共同点:都是存储在浏览器本地的
它们的区别:
cookie是由服务器端写入的,
而SessionStorage、 LocalStorage都是由前端写入的,
cookie的生命周期是由服务器端在写入的时候就设置好的,
LocalStorage是写入就一直存在,除非手动清除,
SessionStorage是页面关闭的时候就会自动清除。
cookie的存储空间比较小大概4KB,SessionStorage、 LocalStorage存储空间比较大,大概5M。
Cookie、SessionStorage、 LocalStorage数据共享都遵循同源原则,SessionStorage还限制必须是同一个页面。在前端给后端发送请求的时候会自动携带Cookie中的数据,但是SessionStorage、 LocalStorage不会
加分回答
由于它们的以上区别,所以它们的应用场景也不同,Cookie一般用于存储登录验证信息SessionID或者token,LocalStorage常用于存储不易变动的数据,减轻服务器的压力,SessionStorage可以用来检测用户是否是刷新进入页面,如音乐播放器恢复播放进度条的功能。
2, 说一说JS数据类型有哪些,区别是什么?
得分点
Number、String、Boolean、BigInt、Symbol、Null、Undefined、Object、8种
标准回答
JS数据类型分为两类:一类是基本数据类型,也叫简单数据类型,包含7种类型,分别是Number 、String、Boolean、BigInt、Symbol、Null、Undefined。另一类是引用数据类型也叫复杂数据类型,通常用Object代表,普通对象,数组,正则,日期,Math数学函数都属于Object。
数据分成两大类的本质区别:基本数据类型和引用数据类型它们在内存中的存储方式不同。
基本数据类型是直接存储在栈中的简单数据段,占据空间小,属于被频繁使用的数据。
引用数据类型是存储在堆内存中,占据空间大。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。
加分回答
Symbol是ES6新出的一种数据类型,这种数据类型的特点就是没有重复的数据,可以作为object的key。
数据的创建方法Symbol(),因为它的构造函数不够完整,所以不能使用new Symbol()创建数据。由于Symbol()创建数据具有唯一性,所以 Symbol() !== Symbol(), 同时使用Symbol数据作为key不能使用for获取到这个key,需要使用Object.getOwnPropertySymbols(obj)获得这个obj对象中key类型是Symbol的key值。
let key = Symbol('key');
let obj = { [key]: 'symbol'};
let keyArray = Object.getOwnPropertySymbols(obj); // 返回一个数组[Symbol('key')]
obj[keyArray[0]] // 'symbol'
BigInt也是ES6新出的一种数据类型,这种数据类型的特点就是数据涵盖的范围大,能够解决超出普通数据类型范围报错的问题。
使用方法:
-整数末尾直接+n:647326483767797n
-调用BigInt()构造函数:BigInt("647326483767797")
注意:BigInt和Number之间不能进行混合操作
3, 说一说你对闭包的理解?
得分点
变量背包、作用域链、局部变量不销毁、函数体外访问函数的内部变量、内存泄漏、内存溢出、形成块级作用域、柯里化、构造函数中定义特权方法、Vue中数据响应式Observer
标准回答
闭包 一个函数和词法环境的引用捆绑在一起,这样的组合就是闭包(closure)。
一般就是一个函数A,return其内部的函数B,被return出去的B函数能够在外部访问A函数内部的变量,这时候就形成了一个B函数的变量背包,A函数执行结束后这个变量背包也不会被销毁,并且这个变量背包在A函数外部只能通过B函数访问。
闭包形成的原理:作用域链,当前作用域可以访问上级作用域中的变量 闭包解决的问题:能够让函数作用域中的变量在函数执行结束之后不被销毁,同时也能在函数外部可以访问函数内部的局部变量。
闭包带来的问题:由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。
加分回答
闭包的应用,能够模仿块级作用域,能够实现柯里化,在构造函数中定义特权方法、Vue中数据响应式Observer中使用闭包等。
4, 说一说promise是什么与使用方法?
得分点
pendding、rejected、resolved、微任务、then、catch、Promise.resolve()、Promise.reject()、Promise.all() Promise.any()、Promise.race()
标准回答
Promise的作用:Promise是异步微任务,解决了异步多层嵌套回调的问题,让代码的可读性更高,更容易维护
Promise使用:Promise是ES6提供的一个构造函数,可以使用Promise构造函数new一个实例,Promise构造函数接收一个函数作为参数,
这个函数有两个参数,分别是两个函数 `resolve`和`reject`,`resolve`将Promise的状态由等待变为成功,将异步操作的结果作为参数传递过去;`reject`则将状态由等待转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。
实例创建完成后,可以使用`then`方法分别指定成功或失败的回调函数,也可以使用catch捕获失败,then和catch最终返回的也是一个Promise,所以可以链式调用。
Promise的特点:
1. 对象的状态不受外界影响(Promise对象代表一个异步操作,有三种状态)。 - pending(执行中) - Resolved(成功,又称Fulfilled) - rejected(拒绝) 其中pending为初始状态,fulfilled和rejected为结束状态(结束状态表示promise的生命周期已结束)。
2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。 Promise对象的状态改变,只有两种可能(状态凝固了,就不会再变了,会一直保持这个结果): - 从Pending变为Resolved - 从Pending变为Rejected
3. resolve 方法的参数是then中回调函数的参数,reject 方法中的参数是catch中的参数
4. then 方法和 catch方法 只要不报错,返回的都是一个fullfilled状态的promise
加分回答
Promise的其他方法:
Promise.resolve() :返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。
Promise.reject():返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法。
Promise.all():返回一个新的promise对象,该promise对象在参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。
Promise.any():接收一个Promise对象的集合,当其中的一个 promise 成功,就返回那个成功的promise的值。
Promise.race():当参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。
5, 说一说跨域是什么?如何解决跨域问题?
得分点
同源限制、协议、域名、端口、CORS、node中间件、JSONP、postmessage
标准回答跨域:当前页面中的某个接口请求的地址和当前页面的地址如果协议、域名、端口其中有一项不同,就说该接口跨域了。
跨域限制的原因:浏览器为了保证网页的安全,出的同源协议策略。
跨域报错信息:
跨域解决方案
cors:目前最常用的一种解决办法,通过设置后端允许跨域实现。
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader("Access-Control-Allow-Methods", "GET, PUT, OPTIONS, POST");
node中间件、nginx反向代理:跨域限制的时候浏览器不能跨域访问服务器,node中间件和nginx反向代理,都是让请求发给代理服务器,静态页面面和代理服务器是同源的,然后代理服务器再向后端服务器发请求,服务器和服务器之间不存在同源限制。
JSONP:利用的原理是script标签可以跨域请求资源,将回调函数作为参数拼接在url中。后端收到请求,调用该回调函数,并将数据作为参数返回去,注意设置响应头返回文档类型,应该设置成javascript。
postmessage:H5新增API,通过发送和接收API实现跨域通信。
加分回答
跨域场景:前后端分离式开发、调用第三方接口
6, 说一说BFC
得分点
块级格式化上下文、独立的渲染区域、不会影响边界以外的元素、形成BFC条件、float、position、overflow、display
标准回答
BFC(Block Formatting Context)块级格式化上下文,是Web页面一块独立的渲染区域,内部元素的渲染不会影响边界以外的元素。
BFC布局规则 -内部盒子会在垂直方向,一个接一个地放置。 -Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠。 -每个盒子(块盒与行盒)的margin box的左边,与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。 -BFC的区域不会与float box重叠。 -BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。 -计算BFC的高度时,浮动元素也参与计算。
BFC形成的条件 -`float `设置成 `left `或 `right` -`position `是`absolute`或者`fixed` -`overflow `不是`visible`,为 `auto`、`scroll`、`hidden` -`display`是`flex`或者`inline-block` 等 BFC解决能的问题:清除浮动
加分回答
BFC的方式都能清除浮动,但是常使用的清除浮动的BFC方式只有`overflow:hidden`,原因是使用float或者position方式清除浮动,虽然父级盒子内部浮动被清除了,但是父级本身又脱离文档流了,会对父级后面的兄弟盒子的布局造成影响。如果设置父级为`display:flex`,内部的浮动就会失效。所以通常只是用`overflow: hidden`清除浮动。
IFC(Inline formatting contexts):内联格式上下文。IFC的高度由其包含行内元素中最高的实际高度计算而来(不受到竖直方向的padding/margin影响),IFC中的line box一般左右都贴紧整个IFC,但是会因为float元素而扰乱。
GFC(GrideLayout formatting contexts):网格布局格式化上下文。当为一个元素设置display值为grid的时候,此元素将会获得一个独立的渲染区域。
FFC(Flex formatting contexts):自适应格式上下文。display值为flex或者inline-flex的元素将会生成自适应容器。
7, 说一说Vuex是什么,每个属性是干嘛的,如何使用 ?
得分点
state、mutations、getters、actions、module、store.commit、store.dispatch
标准回答
Vuex是集中管理项目公共数据的。Vuex 有state、mutations 、getters、actions、module属性。
state 属性用来存储公共管理的数据。
mutations 属性定义改变state中数据的方法, 注意:不要在mutation中的方法中写异步方法ajax,那样数据就不可跟踪了 。
getters 属性可以认为是定义 store 的计算属性。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
action属性类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。
moudle属性是将store分割成模块。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块,从上至下进行同样方式的分割
使用方法: state :直接以对象方式添加属性 mutations :通过`store.commit`调用 action:通过 `store.dispatch` 方法触发 getters:直接通过store.getters.调用
加分回答
可以使用mapState、mapMutations、mapAction、mapGetters一次性获取每个属性下对应的多个方法。 VueX在大型项目中比较常用,非关系组件传递数据比较方便。
8, 说一说JavaScript有几种方法判断变量的类型?
得分点
typeof、instanceof、Object.prototype.toString.call()(对象原型链判断方法)、 constructor (用于引用数据类型)
标准回答
JavaScript有4种方法判断变量的类型,分别是typeof、instanceof、Object.prototype.toString.call()(对象原型链判断方法)、 constructor (用于引用数据类型)
typeof:常用于判断基本数据类型,对于引用数据类型除了function返回’function‘,其余全部返回’object'。
instanceof:主要用于区分引用数据类型,检测方法是检测的类型在当前实例的原型链上,用其检测出来的结果都是true,不太适合用于简单数据类型的检测,检测过程繁琐且对于简单数据类型中的undefined, null, symbol检测不出来。
constructor:用于检测引用数据类型,检测方法是获取实例的构造函数判断和某个类是否相同,如果相同就说明该数据是符合那个数据类型的,这种方法不会把原型链上的其他类也加入进来,避免了原型链的干扰。
Object.prototype.toString.call():适用于所有类型的判断检测,检测方法是Object.prototype.toString.call(数据) 返回的是该数据类型的字符串。
这四种判断数据类型的方法中,各种数据类型都能检测且检测精准的就是Object.prototype.toString.call()这种方法。
加分回答
instanceof的实现原理:验证当前类的原型prototype是否会出现在实例的原型链__proto__上,只要在它的原型链上,则结果都为true。因此,`instanceof` 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 `prototype`,找到返回true,未找到返回false。
Object.prototype.toString.call()原理:Object.prototype.toString 表示一个返回对象类型的字符串,call()方法可以改变this的指向,那么把Object.prototype.toString()方法指向不同的数据类型上面,返回不同的结果
9, 说一说样式优先级的规则是什么?
得分点
`!important`、行内样式、嵌入样式、外链样式、id选择器、类选择器、标签选择器、复合选择器、通配符、继承样式
标准回答
CSS样式的优先级应该分成四大类
第一类`!important`,无论引入方式是什么,选择器是什么,它的优先级都是最高的。
第二类引入方式,行内样式的优先级要高于嵌入和外链,嵌入和外链如果使用的选择器相同就看他们在页面中插入的顺序,在后面插入的会覆盖前面的。
第三类选择器,选择器优先级:id选择器>(类选择器 | 伪类选择器 | 属性选择器 )> (后代选择器 | 伪元素选择器 )> (子选择器 | 相邻选择器) > 通配符选择器 。
第四类继承样式,是所有样式中优先级比较低的。
第五类浏览器默认样式优先级最低。
加分回答
使用!important要谨慎
一定要优先考虑使用样式规则的优先级来解决问题而不是 `!important`
只有在需要覆盖全站或外部 CSS 的特定页面中使用 `!important`
永远不要在你的插件中使用 `!important`
永远不要在全站范围的 CSS 代码中使用 `!important` 优先级的比较指的是相同的样式属性,不同样式属性优先级比较失效,比如:在设置`max-width`时注意,已经给元素的`max-width`设置了`!important`但是还不生效,很有可能就是被width覆盖了 举例:`div`最终的宽度还是`200px` div { max-width: 400px !important; height: 200px;background-color: tomato; width: 200px; }
10, 说一说JS实现异步的方法?
得分点
回调函数、事件监听、setTimeout、Promise、生成器Generators/yield、async/await
标准回答
所有异步任务都是在同步任务执行结束之后,从任务队列中依次取出执行。 回调函数是异步操作最基本的方法,
比如AJAX回调,回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。此外它不能使用 try catch 捕获错误,不能直接 return Promise包装了一个异步调用并生成一个Promise实例,当异步调用返回的时候根据调用的结果分别调用实例化时传入的resolve 和 reject方法,then接收到对应的数据,做出相应的处理。
Promise不仅能够捕获错误,而且也很好地解决了回调地狱的问题,缺点是无法取消 Promise,错误需要通过回调函数捕获。
Generator 函数是 ES6 提供的一种异步编程解决方案,Generator 函数是一个状态机,封装了多个内部状态,可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。优点是异步语义清晰,缺点是手动迭代`Generator` 函数很麻烦,实现逻辑有点绕
async/await是基于Promise实现的,async/await使得异步代码看起来像同步代码,所以优点是,使用方法清晰明了,缺点是awt 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 awt 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。
加分回答
JS 异步编程进化史:callback -> promise -> generator/yield -> async/awt。 async/awt函数
对 Generator 函数的改进,
体现在以下三点:
内置执行器。 Generator 函数的执行必须靠执行器,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
更广的适用性。 yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 awt 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
更好的语义。 async 和 awt,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,awt 表示紧跟在后面的表达式需要等待结果。 目前使用很广泛的就是promise和async/await
11, 说一说Vue2.0 双向绑定的原理与缺陷?
得分点
Object.defineProperty、getter、setter
标准回答
Vue响应式指的是:组件的data发生变化,立刻触发视图的更新
原理: Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过Object.defineProperty来劫持数据的setter,getter,在数据变动时发布消息给订阅者,订阅者收到消息后进行相应的处理。 通过原生js提供的监听数据的API,当数据发生变化的时候,在回调函数中修改dom
核心API:Object.defineProperty
Object.defineProperty API的使用
作用: 用来定义对象属性 特点: 默认情况下定义的数据的属性不能修改 描述属性和存取属性不能同时使用,使用会报错
响应式原理: 获取属性值会触发getter方法 设置属性值会触发setter方法 在setter方法中调用修改dom的方法
加分回答
Object.defineProperty的缺点
1. 一次性递归到底开销很大,如果数据很大,大量的递归导致调用栈溢出
2. 不能监听对象的新增属性和删除属性
3. 无法正确的监听数组的方法,当监听的下标对应的数据发生改变时
12, 说一说数组去重都有哪些方法?
得分点
对象属性、new Set()、indexOf、hasOwnProperty、reduce+includes、filter
标准回答
第一种方法:利用对象属性key排除重复项:遍历数组,每次判断对象中是否存在该属性,不存在就存储在新数组中,并且把数组元素作为key,设置一个值,存储在对象中,最后返回新数组。这个方法的优点是效率较高,缺点是占用了较多空间,使用的额外空间有一个查询对象和一个新的数组
第二种方法:利用Set类型数据无重复项:new 一个 Set,参数为需要去重的数组,Set 会自动删除重复的元素,再将 Set 转为数组返回。这个方法的优点是效率更高,代码简单,思路清晰,缺点是可能会有兼容性问题
第三种方法:filter+indexof 去重:这个方法和第一种方法类似,利用 Array 自带的 filter 方法,返回 arr.indexOf(num) 等于 index 的num。原理就是 indexOf 会返回最先找到的数字的索引,假设数组是 [1, 1],在对第二个1使用 indexOf 方法时,返回的是第一个1的索引0。这个方法的优点是可以在去重的时候插入对元素的操作,可拓展性强。
第四种方法:这个方法比较巧妙,从头遍历数组,如果元素在前面出现过,则将当前元素挪到最后面,继续遍历,直到遍历完所有元素,之后将那些被挪到后面的元素抛弃。这个方法因为是直接操作数组,占用内存较少。
第五种方法:reduce +includes去重:这个方法就是利用reduce遍历和传入一个空数组作为去重后的新数组,然后内部判断新数组中是否存在当前遍历的元素,不存在就插入到新数组中。这种方法时间消耗多,内存空间也有额外占用。 方法还有很多,常用的、了解的这些就可以
加分回答
以上五个方法中,在数据低于10000条的时候没有明显的差别,高于10000条,第一种和第二种的时间消耗最少,后面三种时间消耗依次增加,由于第一种内存空间消耗比较多,且现在很多项目不再考虑低版本浏览器的兼容性问题,所以建议使用第二种去重方法,简洁方便。
13, 说一说null 和 undefined 的区别,如何让一个属性变为null
得分点
操作的变量没有被赋值、全局对象的一个属性、函数没有return返回值、值 `null` 特指对象的值未设置 undefined == null、undefined !== null
标准回答
undefind 是全局对象的一个属性,当一个变量没有被赋值或者一个函数没有返回值或者某个对象不存在某个属性却去访问或者函数定义了形参但没有传递实参,这时候都是undefined。
undefined通过typeof判断类型是'undefined'。undefined == undefined undefined === undefined 。
null代表对象的值未设置,相当于一个对象没有设置指针地址就是null。null通过typeof判断类型是'object'。null === null null == null null == undefined null !== undefined undefined 表示一个变量初始状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。在实际使用过程中,不需要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可。 让一个变量为null,直接给该变量赋值为null即可。
加分回答
null 其实属于自己的类型 Null,而不属于Object类型,typeof 之所以会判定为 Object 类型,是因为JavaScript 数据类型在底层都是以二进制的形式表示的,二进制的前三位为 0 会被 typeof 判断为对象类型,而 null 的二进制位恰好都是 0 ,因此,null 被误判断为 Object 类型。 对象被赋值了null 以后,对象对应的堆内存中的值就是游离状态了,GC 会择机回收该值并释放内存。因此,需要释放某个对象,就将变量设置为 null,即表示该对象已经被清空,目前无效状态。
14, 说一下浮动?
得分点
脱离文档流、盒子塌陷、 影响其他元素排版、伪元素 、`overflow:hidden` 、标签插入法
标准回答
浮动的作用,设置浮动的图片,可以实现文字环绕图片,设置了浮动的块级元素可以排列在同一行,设置了浮动的行内元素可以设置宽高,同时可以按照浮动设置的方向对齐排列盒子。
设置浮动元素的特点:
设置了浮动,该元素脱标。元素不占位置
浮动可以进行模式转换(行内块元素) 浮动造成的影响,使盒子脱离文档流,如果父级盒子没有设置高度,需要被子盒子撑开,那么这时候父级盒子的高度就塌陷了,同时也会造成父级盒子后面的兄弟盒子布局受到影响。如果浮动元素后面还有其他兄弟元素,其他兄弟元素的布局也会受到影响。
清除浮动的方法:
伪元素清除浮动:给浮动元素父级增加 .clearfix::after { content: ''; display: table; clear: both; } /*兼容IE低版本 */ .clearfix { *zoom: 1; } overflow:hidden`:给浮动元素父级增加`overflow:hidden`属性
额外标签法:给浮动元素父级增加标签
加分回答 三种清除浮动的特点和影响 -伪元素清除浮动:不会新增标签,不会有其他影响,是当下清除浮动最流行的方法 -`overflow:hidden`:不会新增标签,但是如果父级元素有定位元素超出父级,超出部分会隐藏,在不涉及父级元素有超出内容的情况,overflow:hidden比较常用,毕竟写法方便简洁 -标签插入法:清除浮动的语法加在新增标签上,由于新增标签会造成不必要的渲染,所以这种方法目前不建议使用
15, 说一说es6中箭头函数?
得分点
没有this、this是从外部获取、不能使用new、没有arguments、没有原型和super
标准回答
箭头函数相当于匿名函数,简化了函数定义。
箭头函数有两种写法,当函数体是单条语句的时候可以省略{}和return。另一种是包含多条语句,不可以省略{}和return。
箭头函数最大的特点就是没有this,所以this是从外部获取,就是继承外部的执行上下文中的this,由于没有this关键字所以箭头函数也不能作为构造函数, 同时通过 `call()` 或 `apply()` 方法调用一个函数时,只能传递参数(不能绑定this),第一个参数会被忽略。
箭头函数也没有原型和super。不能使用yield关键字,因此箭头函数不能用作 Generator 函数。不能返回直接对象字面量。
加分回答
箭头函数的不适用场景:
定义对象上的方法 当调用` dog.jumps` 时,`lives` 并没有递减。因为 `this` 没有绑定值,而继承父级作用域。 var dog = { lives: 20, jumps: () => { this.lives--; } }
不适合做事件处理程序 此时触发点击事件,this不是button,无法进行class切换 var button = document.querySelector('button'); button.addEventListener('click', () => { this.classList.toggle('on'); });
箭头函数函数适用场景:
简单的函数表达式,内部没有this引用,没有递归、事件绑定、解绑定,适用于map、filter等方法中,写法简洁 var arr = [1,2,3]; var newArr = arr.map((num)=>num*num)
内层函数表达式,需要调用this,且this应与外层函数一致时
let group = {
title: "Our Group",
students: ["John", "Pete", "Alice"],
showList() {
this.students.forEach( student => alert(this.title + ': ' + student) );
group.showList();
16, 说一说call apply bind的作用和区别?
得分点
bind改变this指向不直接调用、call和apply改变this指向直接调用、apply接收第二个参数为数组 、call用于对象的继承 、伪数组转换成真数组、apply用于找出数组中的最大值和最小值以及数组合并、bind用于vue或者react框架中改变函数的this指向
标准回答
call、apply、bind的作用都是改变函数运行时的this指向。
bind和call、apply在使用上有所不同,bind在改变this指向的时候,返回一个改变执行上下文的函数,不会立即执行函数,而是需要调用该函数的时候再调用即可,但是call和apply在改变this指向的同时执行了该函数。
bind只接收一个参数,就是this指向的执行上文。 call、apply接收多个参数,第一个参数都是this指向的执行上文,后面的参数都是作为改变this指向的函数的参数。但是call和apply参数的格式不同,call是一个参数对应一个原函数的参数,但是apply第二个参数是数组,数组中每个元素代表函数接收的参数,数组有几个元素函数就接收几个元素。
加分回答
call的应用场景: 对象的继承,在子构造函数这种调用父构造函数,但是改变this指向,就可以继承父的属性
function superClass() {
this.a = 1;
this.print = function () {
console.log(this.a);
function subClass() {
superClass.call(this); // 执行superClass,并将superClass方法中的this指向subClass this.print();
subClass();
借用Array原型链上的slice方法,把伪数组转换成真数组
let domNodes = Array.prototype.slice.call(document.getElementsByTagName("div"));
apply的应用场景: Math.max,获取数组中最大、最小的一项
let max = Math.max.apply(null, array);
let min = Math.min.apply(null, array);
//实现两个数组合并
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]
bind的应用场景 在vue或者react框架中,使用bind将定义的方法中的this指向当前类
17, 说一说HTML语义化?
得分点
语义化标签、利于页面内容结构化、利于无CSS页面可读、利于SEO、利于代码可读 标准回答 HTML语义化就是指在使用HTML标签构建页面时,避免大篇幅的使用无语义的标签
18, 说一说this指向(普通函数、箭头函数)?
得分点
全局执行上下文、函数执行上下文、this严格模式下undefined、非严格模式window、构造函数新对象本身、普通函数不继承this、箭头函数无this,可继承
标准回答
this关键字由来:在对象内部的方法中使用对象内部的属性是一个非常普遍的需求。但是 JavaScript 的作用域机制并不支持这一点,基于这个需求,JavaScript 又搞出来另外一套 this 机制。
this存在的场景有三种全局执行上下文和函数执行上下文和eval执行上下文,eval这种不讨论。
在全局执行环境中无论是否在严格模式下,(在任何函数体外部)`this` 都指向全局对象。
在函数执行上下文中访问this,函数的调用方式决定了 `this` 的值。
在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window,通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身。
普通函数this指向:当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身。
new 关键字构建好了一个新对象,并且构造函数中的 this 其实就是新对象本身。嵌套函数中的 this 不会继承外层函数的 this 值。
箭头函数this指向:箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数。
加分回答
箭头函数因为没有this,所以也不能作为构造函数,但是需要继承函数外部this的时候,使用箭头函数比较方便
var myObj = {
name : "闷倒驴",
showThis:function(){
console.log(this); // myObj
var bar = ()=>{
this.name = "王美丽";
console.log(this) // myObj
bar();
myObj.showThis();
console.log(myObj.name); // "王美丽"
console.log(window.name); // ''
19, 说一说CSS尺寸设置的单位
得分点
px、rem、em、vw、vh
标准回答
px:pixel像素的缩写,绝对长度单位,它的大小取决于屏幕的分辨率,是开发网页中常常使用的单位。
em:相对长度单位,在 `font-size` 中使用是相对于父元素的字体大小,在其他属性中使用是相对于自身的字体大小,如 width。如当前元素的字体尺寸未设置,由于字体大小可继承的原因,可逐级向上查找,最终找不到则相对于浏览器默认字体大小。
rem:相对长度单位,相对于根元素的字体大小,根元素字体大小未设置,使用浏览器默认字体大小。
vw:相对长度单位,相对于视窗宽度的1%。
vh:相对长度单位,相对于视窗高度的1%。
加分回答
rem应用:在移动端网页开发中,页面要做成响应式的,可使用rem配合媒体查询或者flexible.js实现。原理是通过媒体查询或者flexible.js,能够在屏幕尺寸发生改变时,重置html根元素的字体大小,页面中的元素都是使用rem为单位设置的尺寸,因此只要改变根元素字体大小,页面中的其他元素的尺寸就自动跟着修改
vw应用:由于vw被更多浏览器兼容之后,在做移动端响应式页面时,通常使用vw配合rem。原理是使用vw设置根元素html字体的大小,当窗口大小发生改变,vw代表的尺寸随着修改,无需加入媒体查询和flexible.js,页面中的其他元素仍使用rem为单位,就可实现响应式。
20, 说几个未知宽高元素水平垂直居中方法
`position` `transform` `flex` `justify-content` `align-items` `vertical-align` `text-align`
标准回答
未知宽高元素水平垂直都居中的实现方法:
1. 设置元素相对父级定位`left:50%;right:50%`,让自身平移自身高度50% `transform: translate(-50%,-50%);`,这种方式兼容性好,被广泛使用的一种方式
2. 设置元素的父级为弹性盒子`display:flex`,设置父级和盒子内部子元素水平垂直都居中`justify-content:center; align-items:center` ,这种方式代码简洁,但是兼容性ie 11以上支持,由于目前ie版本都已经很高,很多网站现在也使用这种方式实现水平垂直居中
3. 设置元素的父级为网格元素`display: grid`,设置父级和盒子内部子元素水平垂直都居中`justify-content:center; align-items:center` ,这种方式代码简介,但是兼容性ie 10以上支持
4. 设置元素的父级为表格元素`display: table-cell`,其内部元素水平垂直都居中`text-align: center;vertical-align: middle;` ,设置子元素为行内块`display: inline-block; `,这种方式兼容性较好
21, 说一说JS变量提升?
得分点
Var声明的变量声明提升、函数声明提升、let和const变量不提升
标准回答
变量提升是指JS的变量和函数声明会在代码编译期,提升到代码的最前面。 变量提升成立的前提是使用Var关键字进行声明的变量,并且变量提升的时候只有声明被提升,赋值并不会被提升,同时函数的声明提升会比变量的提升优先。 变量提升的结果,可以在变量初始化之前访问该变量,返回的是undefined。在函数声明前可以调用该函数。 加分回答 使用let和const声明的变量是创建提升,形成暂时性死区,在初始化之前访问let和const创建的变量会报错。
22, 说一说 HashRouter 和 HistoryRouter的区别和原理?
得分点
`#` `window.onhashchange` `history.pushState ` `window.onpopstate`
标准回答
HashRouter和 HistoryRouter的区别:
1. history和hash都是利用浏览器的两种特性实现前端路由,history是利用浏览历史记录栈的API实现,hash是监听location对象hash值变化事件来实现
2. history的url没有'#'号,hash反之
3. 相同的url,history会触发添加到浏览器历史记录栈中,hash不会触发,history需要后端配合,如果后端不配合刷新新页面会出现404,hash不需要。
HashRouter的原理:通过`window.onhashchange`方法获取新URL中hash值,再做进一步处理
HistoryRouter的原理:通过`history.pushState `使用它做页面跳转不会触发页面刷新,使用`window.onpopstate` 监听浏览器的前进和后退,再做其他处理 加分回答 hash模式下url会带有#,需要url更优雅时,可以使用history模式。 需要兼容低版本的浏览器时,建议使用hash模式。 需要添加任意类型数据到记录时,可以使用history模式
23, 说一说map 和 forEach 的区别?
得分点
map创建新数组、map返回处理后的值、forEach()不修改原数组、forEach()方法返回undefined
标准回答
map 和 forEach 的区别:map有返回值,可以开辟新空间,return出来一个length和原数组一致的数组,即便数组元素是undefined或者是null。
forEach默认无返回值,返回结果为undefined,可以通过在函数体内部使用索引修改数组元素。
加分回答
map的处理速度比forEach快,而且返回一个新的数组,方便链式调用其他数组新方法,比如filter、reduce
let arr = [1, 2, 3, 4, 5];
let arr2 = arr.map(value => value * value).filter(value => value > 10); // arr2 = [16, 25]