有兴趣的朋友可以查看博主自己的前端面试笔试学习知识笔记总结;
基础自省:
更多基础请直接前往前端学习专栏进行查看;
牛客网前端工程师面试题学习,不断学习更新,希望有大佬来指点提携一波,望共勉,一起进步~~
目录
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可以用来检测用户是否是属性进入页面,如音乐播放器恢复进度条的功能。
JS数据类型分为两类:
- 一类是基本数据类型,也叫简单数据类型,包含7种类型,分别是Number、String、Boolean、BigInt、Symbol、Null、Undefinrd。
- 另一类是引用数据类型,也叫复杂数据类型,通常用Object代表,普通对象,数组,正则,日期,Math数学函数都属于Object。
数据分为两大类的本质区别:基本数据类型和引用数据类型他们在内存的存储方式不同。
- 基本数据类型是直接存储在栈中的简单数据段,占据空间小,属于被频繁使用的数据;
- 引用数据类型时存储在堆内存中,占据空间打。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。
加分项:
- Symbol是ES6新出的一种数据类型,这种数据类型的特点就是没有重复的数据,可以作为object的key。
- 数据的创建方法Symbol(),因为它的构造函数不够完整,所以不能使用new Symbol()创建数据。
- 由于Symbol()创建数据具有唯一性,所以Symbol()!==Symbol(),同时使用Symbol数据作为key不能使用for获得到这个key,需要使用Object.getOwnPropertySymbols(obj)获得这个obj对象中key类型时Symbol的可以值。
let key = Symbol('key'); let obj = { [key]: 'symbol'}; let keyArray = Object.getOwnPropertySymbols(obj); // 返回一个数组[Symbol('key')] obj[keyArray[0]] // 'symbol'
- BigInt也是ES6新出的一种数据类型,这种数据类型的特点就是数据涵盖的范围大,能够解决超出普通数据类型范围报错的问题。
- 使用方法:整数末尾直接+n:1n;或者调用BigInt()构造函数:BigInt("1")
- 注意BigInt和Number之间不能进行混合操作;
闭包一个函数和词法环境的引用捆绑在一起,这样的组合就是闭包(closure)。一般就是一个函数A,如return其内部的函数B,被return出去的B函数能够在外部访问A函数内部的变量,这时候就形成了一个B函数的变量背包,A函数执行结束后这个变量背包也就不会被销毁,并且这个变量背包在A函数外部只能通过B函数访问。
- 闭包形成原理:作用域链,当前作用域可以访问上级作用域中的变量;
- 闭包解决的问题:能够让函数作用域中的变量在函数执行结束后不被销毁,同时也能在函数外部可以访问函数内部的局部变量。
- 闭包带来的问题:由于垃圾回收器不会将比保重变量销毁,于是就造成了北村泄露,内存泄露积累多了就容易导致内存溢出。
加分项:
- 闭包的应用:模块化的导出引入、防抖节流,能够模仿块级作用域,在构造函数中定义特权方法,Vue中数据响应式Observer中使用闭包等。
- Promise的概念:异步编程的一种解决方案,解决了地狱回调的问题;
- Promise的使用场景:异步请求、可读性高的场景、处理多并发 。
- Promise的使用方法:用来进行异步编程,通过.then进行链式调用,总共三个状态:pendding:等待响应:,resolved:成功的放回,rejected:失败的返回,书写错误时的返回。无论什么情况都会返回个状态要么成功要么失败。
- Promise的作用:Promise是异步微任务,解决了异步多层嵌套回调的问题,让代码的可读性更高,更容易维护;
- Promise的使用:Promise是ES6提供的一个构造函数,可以使用Promise构造函数new一个实例,Promise构造函数接受一个函数作为参数,这个函数有两个参数,分别是两个函数“resolve”和“reject”,“resolve”将Promise的状态有等待变为成功,将异步操作的结果作为参数传递过去。“reject”则将状态有等待转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。实例穿件完成后,可以使用“then”方法分别指定成功或失败的回调函数,也可以使用catch捕获失败,then和catch最终返回的也是一个Promise,所以可以链式使用;
- Promise的特点:
- 对象的状态不受外界影响(Promise对象代表一个异步操作,有三种状态):-pending(执行中)-Resolved(成功,又称Fulfilled)-rejected(拒绝),其中pending为初始状态,fildilled和rejected为结束状态(结束状态标识promise的生命周期已结束);
- 一旦状态改变,就不会在变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能(状态凝固了,就不会在变了,会一直保持这个结果):从Pending变为Resolved,从Pending变为Rejected;
- resolve方法的参数是then中回调函数的参数,reject方法中的参数是catch中的参数;
- then方法和catch方法只有不报错,返回的都是一个fullfilled状态的promise;
- 加分项:Promise的其他方法:
- Promise.resolve():返回的Promise对象状态为fullfilled,并且将改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对象;
- 跨域:当前页面中的某个接口请求的地址和当前页面的地址如果协议、域名、端口其中有一项不同,就说改接口跨域了;
- 跨域限制的原因:浏览器为了保证网页的安全,出的同源同源协议策略;
- 跨域报错信息:
- 跨域解决方案: 跨域一般的解决方法时Jsonp,script标签不受同源策略影响;前端还可以设置代理proxy;后端也可以设置CROS,Access-Control-Allow-Origin;此外还有nginx的反向代理。
- 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实现跨域通信;
BFC(Block Formatting Context)块级格式化上下文,是Web页面一块独立的渲染区域,内部元素的渲染不会影响便捷意外的元素。
BFC布局规则:
- 内部盒子会在垂直方向,一个接一个地放置;
- Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠;
- 每个盒子(块盒与行盒)的margin box的左边,与包含块border box的左边相接触(对于从左到右的格式化,否则相反)。即使存在浮动也是如此;
- BFC的区域不会与float box重叠;
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也是如此;
- 计算BFC的高度时,浮动元素也参与计算;
BFC形成条件:
- “float”设置成“left”或者“right”,不为none;
- “position”是“absolute”或者“fixed”;
- “overflow”不是“visible”,为“auto”,“scroll”,“hidden”;
- “display”是“flex”或者“inline-block”等;
BFC主要用途:
- 清除内部浮动,富元素设置为BFC可以清除子元素的浮动(最常用overflow:hidden,IE6需加上*zoom:1):计算BFC高度时会检测浮动子盒高度;
- 解决外边距合并问题;
- 右侧盒子自适应: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的元素将会生成自适应容器;
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在大型项目中比较常见,非关系组件传递数据比较方便;
总结:VueX是全局状态管理仓库,相当于window对象挂载了一个全局变量,state是数据源、actions处理异步事件、mutations处理同步事件、getters是过滤数据的、modules是模块。
JavaScript有4中方法判断变量的类型,分别是typeof、instanceof、Object.prototype.toString.call()(对象原型链判断方法)、constuctor(用于引用数据类型)。
- typrof:根据二进制判断,常用于判断基本数据类型,对于引用数据类型处理duncyion返回‘function’,其余全部返回‘object’,不能判断Object和null;
- instanceof:根据原型链判断,主要用于区分引用数据类型,检测方法是检测的类型在当前实例的原型链上,用其检测出来的结果都是true,不太适合用于简单数据类型的检测,检测过程繁琐且对于简单数据类型中的undefined,null,symbol检测不出来;
- construction:根据构造器判断,用于检测(监听)引用数据类型,检测方法是获取实例的构造函数判断和某个类是否相同,如果相同就说明该数据是符合那个数据类型的,这种方法不会把原型链上的其他类也加入进来,避免了原型类的干扰;
- Object.protptype.toString.call():用Object的toString方法判断,适用于所有类型的判断检测,检测方法是Object.prototype.toString.call(数据)返回的是该数据类型的字符串;
这四种判断数据类型的方法中,各种数据类型都能检测且检测精准的就是Object.prototypr.toString.call()这种方法。
加分项:
- instanceof的实现原理,验证当前类的原型prototype是否会出现在实例的原型链_proto_上,只要在它的原型链,则结果都为true。因此,`instanceof`在查找的过程中会遍历左边变量的原型链,直到找到右边变量的`prototype`,找到返回true,未找到返回false;
- Object.prototype.toString.call()原理:Object.prototype.toString表示一个返回对象类型的字符串,call()方法可以改变this的指向,那么把Object.prototype.toString()方法指向不同的数据类型上面,返回不同的结果;
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; backgroud-color:tomato; width:200px;}
所有的异步任务都是在同步任务执行结束之后,从任务列中一次取出执行。
- 回调函数式异步操作最基本方法,比如AJAX回调,回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。此外它不能使用try catch不火错误,不能直接return;
- Promise包装了一个异步调用并产生一个Promise实例,当异步调用返回的时候根据调用的结果分别调用实例化时传入的resolve和reject方法,then接收到对应的数据,做出相应的处理。Promise不仅能够捕获错误,而且也很好地解决了回调地狱的问题,缺点是无法取消Promise,错误需要通过回调函数捕获;
- Generate函数式ES6提供的一种异步编程解决方案,Generator函数是一个状态机,封装了多个内部状态,可暂停函数,yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。优点是异步语义清晰,缺点是手动迭代`Generator`很熟很麻烦,实现逻辑优点绕;
- async/awt是基于Promise实现的,async/awt使得异步代码看起来像同步代码,所以优点是:使用方法清晰明了,缺点是awt将异步代码改造成了同步代码,如果多个异步代码没有依赖性缺使用了awt会导致性能上的降低,代码没有依赖性的话,完全可以使用Promise.all的方式;
加分项:
- 异步编程进化史: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/awt;
- 异步微任务:回调函数,promise,async/await;
- 异步宏任务:setTimeout,setIntervial;
- Vue响应式指的是:组件的data发生变化,立刻触发试图的更新;
- 原理:Vue采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过Object.defineProperty来劫持数据的setter,getter,在数据变动时发布消息给订阅者,订阅者收到消息后进行相应的处理。通过原生js提供的监听数据的API,当数据变动时发布消息给订阅者,订阅者收到消息后进行相应的处理。通过原生js提供的监听数据的API,当数据发生变化的时候,在回调函数中修改dom;
- 核心API:Object.defineProperty,Object.defineProperty;
- API的使用的作用:用来定义对象属性;
- API的使用的特点:
- 默认情况下定义的数据的属性不能修改;
- 描述属性和存取属性不能同时使用,使用会报错;
- 响应式原理:
- 获取属性值会触发getter方法;
- 设置属性值会触发setter方法;
- 在setter方法中调用修改dom的方法;
- 加分项:Object.defineProperty的缺点:
- 一次性递归到底开销很大,如果数据很大,大量的递归导致调用栈溢出;
- 不能监听对象的新增属性和删除属性,需要通过Vue.set();
- 无法正确的监听数组的方法,不能监听通过下标改变数组对应数据;
- 总结:
- 原理:通过初始化最后一步observe遍历对象 ,对每个属性进行defineproperty劫持数据的setter和getter,结合发布-订阅模式,getter收集依赖(发布),setter派发更新(订阅)。
- 缺陷:无法对新添加或删除的属性进行监听、无法监听数组的变化。