说一说cookie sessionStorage localStorage 区别?
它们的共同点:都是存储在浏览器本地的 它们的区别:cookie是由服务器端写入的,而SessionStorage、 LocalStorage都是由前端写入的,cookie的生命周期是由服务器端在写入的时候就设置好的,LocalStorage是写入就一直存在,除非手动清除,SessionStorage是页面关闭的时候就会自动清除。cookie的存储空间比较小大概4KB,SessionStorage、 LocalStorage存储空间比较大,大概5M。Cookie、SessionStorage、 LocalStorage数据共享都遵循同源原则,SessionStorage还限制必须是同一个页面。在前端给后端发送请求的时候会自动携带Cookie中的数据,但是SessionStorage、 LocalStorage不会
说一说JS数据类型有哪些,区别是什么?
JS数据类型分为两类:一类是基本数据类型,也叫简单数据类型,包含7种类型,分别是Number 、String、Boolean、BigInt、Symbol、Null、Undefined。另一类是引用数据类型也叫复杂数据类型,通常用Object代表,普通对象,数组,正则,日期,Math数学函数都属于Object。
数据分成两大类的本质区别:基本数据类型和引用数据类型它们在内存中的存储方式不同。
基本数据类型是直接存储在栈中的简单数据段,占据空间小,属于被频繁使用的数据。
引用数据类型是存储在堆内存中,占据空间大。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。
说一说你对闭包的理解?
闭包 一个函数和词法环境的引用捆绑在一起,这样的组合就是闭包(closure)。一般就是一个函数A,return其内部的函数B,被return出去的B函数能够在外部访问A函数内部的变量,这时候就形成了一个B函数的变量背包,A函数执行结束后这个变量背包也不会被销毁,并且这个变量背包在A函数外部只能通过B函数访问。 闭包形成的原理:作用域链,当前作用域可以访问上级作用域中的变量 闭包解决的问题:能够让函数作用域中的变量在函数执行结束之后不被销毁,同时也能在函数外部可以访问函数内部的局部变量。 闭包带来的问题:由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。
说一说promise是什么与使用方法?
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
说一说跨域是什么?如何解决跨域问题?
跨域:当前页面中的某个接口请求的地址和当前页面的地址如果协议、域名、端口其中有一项不同,就说该接口跨域了。
跨域限制的原因:浏览器为了保证网页的安全,出的同源协议策略。
跨域报错信息:
跨域解决方案
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
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解决能的问题:清除浮动
说一说Vuex是什么,每个属性是干嘛的,如何使用 ?
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.调用
说一说JavaScript有几种方法判断变量的类型?
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()这种方法
说一说样式优先级的规则是什么?
CSS样式的优先级应该分成四大类 -第一类!important
,无论引入方式是什么,选择器是什么,它的优先级都是最高的。 -第二类引入方式,行内样式的优先级要高于嵌入和外链,嵌入和外链如果使用的选择器相同就看他们在页面中插入的顺序,在后面插入的会覆盖前面的。 -第三类选择器,选择器优先级:id选择器>(类选择器 | 伪类选择器 | 属性选择器 )> (后代选择器 | 伪元素选择器 )> (子选择器 | 相邻选择器) > 通配符选择器 。 -第四类继承样式,是所有样式中优先级比较低的。 -第五类浏览器默认样式优先级最低。
说一说JS实现异步的方法?
所有异步任务都是在同步任务执行结束之后,从任务队列中依次取出执行。 回调函数是异步操作最基本的方法,比如AJAX回调,回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。此外它不能使用 try catch 捕获错误,不能直接 return Promise包装了一个异步调用并生成一个Promise实例,当异步调用返回的时候根据调用的结果分别调用实例化时传入的resolve 和 reject方法,then接收到对应的数据,做出相应的处理。Promise不仅能够捕获错误,而且也很好地解决了回调地狱的问题,缺点是无法取消 Promise,错误需要通过回调函数捕获。 Generator 函数是 ES6 提供的一种异步编程解决方案,Generator 函数是一个状态机,封装了多个内部状态,可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。优点是异步语义清晰,缺点是手动迭代Generator
函数很麻烦,实现逻辑有点绕 async/awt是基于Promise实现的,async/awt使得异步代码看起来像同步代码,所以优点是,使用方法清晰明了,缺点是awt 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 awt 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。
说一说Vue2.0 双向绑定的原理与缺陷?
Vue响应式指的是:组件的data发生变化,立刻触发试图的更新 原理: Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过Object.defineProperty来劫持数据的setter,getter,在数据变动时发布消息给订阅者,订阅者收到消息后进行相应的处理。 通过原生js提供的监听数据的API,当数据发生变化的时候,在回调函数中修改dom 核心API:Object.defineProperty Object.defineProperty API的使用 作用: 用来定义对象属性 特点: 默认情况下定义的数据的属性不能修改 描述属性和存取属性不能同时使用,使用会报错 响应式原理: 获取属性值会触发getter方法 设置属性值会触发setter方法 在setter方法中调用修改dom的方法
说一说数组去重都有哪些方法?
第一种方法:利用对象属性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遍历和传入一个空数组作为去重后的新数组,然后内部判断新数组中是否存在当前遍历的元素,不存在就插入到新数组中。这种方法时间消耗多,内存空间也有额外占用。 方法还有很多,常用的、了解的这些就可以
说一说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即可。
说一下浮动?
浮动的作用,设置浮动的图片,可以实现文字环绕图片,设置了浮动的块级元素可以排列在同一行,设置了浮动的行内元素可以设置宽高,同时可以按照浮动设置的方向对齐排列盒子。 设置浮动元素的特点: -设置了浮动,该元素脱标。元素不占位置 -浮动可以进行模式转换(行内块元素) 浮动造成的影响,使盒子脱离文档流,如果父级盒子没有设置高度,需要被子盒子撑开,那么这时候父级盒子的高度就塌陷了,同时也会造成父级盒子后面的兄弟盒子布局受到影响。如果浮动元素后面还有其他兄弟元素,其他兄弟元素的布局也会受到影响。 清除浮动的方法: -伪元素清除浮动:给浮动元素父级增加 .clearfix::after { content: ‘’; display: table; clear: both; } /*兼容IE低版本 */ .clearfix { *zoom: 1; } overflow:hidden:给浮动元素父级增加
overflow:hidden`属性 额外标签法:给浮动元素父级增加标签
说一说es6中箭头函数?
箭头函数相当于匿名函数,简化了函数定义。箭头函数有两种写法,当函数体是单条语句的时候可以省略{}和return。另一种是包含多条语句,不可以省略{}和return。 箭头函数最大的特点就是没有this,所以this是从外部获取,就是继承外部的执行上下文中的this,由于没有this关键字所以箭头函数也不能作为构造函数, 同时通过 call()
或 apply()
方法调用一个函数时,只能传递参数(不能绑定this),第一个参数会被忽略。箭头函数也没有原型和super。不能使用yield关键字,因此箭头函数不能用作 Generator 函数。不能返回直接对象字面量。
说一说call apply bind的作用和区别?
call、apply、bind的作用都是改变函数运行时的this指向。 bind和call、apply在使用上有所不同,bind在改变this指向的时候,返回一个改变执行上下文的函数,不会立即执行函数,而是需要调用该函数的时候再调用即可,但是call和apply在改变this指向的同时执行了该函数。 bind只接收一个参数,就是this指向的执行上文。 call、apply接收多个参数,第一个参数都是this指向的执行上文,后面的参数都是作为改变this指向的函数的参数。但是call和apply参数的格式不同,call是一个参数对应一个原函数的参数,但是apply第二个参数是数组,数组中每个元素代表函数接收的参数,数组有几个元素函数就接收几个元素。
说一说HTML语义化?
HTML语义化就是指在使用HTML标签构建页面时,避免大篇幅的使用无语义的标签,要求尽可能的使用具有语义的标签,也可以使用span+CSS实现标题的样式, 使用HTML语义化标签的作用: - 易于用户阅读,样式文件未加载时,页面结构清晰。 - 有利于SEO,搜索引擎根据标签来确定上下文和各个关键字的权重。 - 方便屏幕阅读器解析,如盲人阅读器根据语义渲染网页。 - 有利于开发和维护,代码更具可读性,代码更好维护
说一说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 取决于它的外部函数。
说一说CSS尺寸设置的单位
px:pixel像素的缩写,绝对长度单位,它的大小取决于屏幕的分辨率,是开发网页中常常使用的单位。 em:相对长度单位,在 font-size
中使用是相对于父元素的字体大小,在其他属性中使用是相对于自身的字体大小,如 width。如当前元素的字体尺寸未设置,由于字体大小可继承的原因,可逐级向上查找,最终找不到则相对于浏览器默认字体大小。 rem:相对长度单位,相对于根元素的字体大小,根元素字体大小未设置,使用浏览器默认字体大小。 vw:相对长度单位,相对于视窗宽度的1%。 vh:相对长度单位,相对于视窗高度的1%。
说几个未知宽高元素水平垂直居中方法
未知宽高元素水平垂直都居中的实现方法:
- 设置元素相对父级定位
position:absolute;left:50%;right:50%
,让自身平移自身高度50%transform: translate(-50%,-50%);
,这种方式兼容性好,被广泛使用的一种方式 - 设置元素的父级为弹性盒子
display:flex
,设置父级和盒子内部子元素水平垂直都居中justify-content:center; align-items:center
,这种方式代码简洁,但是兼容性ie 11以上支持,由于目前ie版本都已经很高,很多网站现在也使用这种方式实现水平垂直居中 - 设置元素的父级为网格元素
display: grid
,设置父级和盒子内部子元素水平垂直都居中justify-content:center; align-items:center
,这种方式代码简介,但是兼容性ie 10以上支持 - 设置元素的父级为表格元素
display: table-cell
,其内部元素水平垂直都居中text-align: center;vertical-align: middle;
,设置子元素为行内块display: inline-block;
,这种方式兼容性较好
说一说JS变量提升?
变量提升是指JS的变量和函数声明会在代码编译期,提升到代码的最前面。 变量提升成立的前提是使用Var关键字进行声明的变量,并且变量提升的时候只有声明被提升,赋值并不会被提升,同时函数的声明提升会比变量的提升优先。 变量提升的结果,可以在变量初始化之前访问该变量,返回的是undefined。在函数声明前可以调用该函数。
说一说 HashRouter 和 HistoryRouter的区别和原理?
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
监听浏览器的前进和后退,再做其他处理
说一说map 和 forEach 的区别?
map 和 forEach 的区别:map有返回值,可以开辟新空间,return出来一个length和原数组一致的数组,即便数组元素是undefined或者是null。forEach默认无返回值,返回结果为undefined,可以通过在函数体内部使用索引修改数组元素。
说一说事件循环Event loop,宏任务与微任务?
浏览器的事件循环:执行js代码的时候,遇见同步任务,直接推入调用栈中执行,遇到异步任务,将该任务挂起,等到异步任务有返回之后推入到任务队列中,当调用栈中的所有同步任务全部执行完成,将任务队列中的任务按顺序一个一个的推入并执行,重复执行这一系列的行为。 异步任务又分为宏任务和微任务。 宏任务:任务队列中的任务称为宏任务,每个宏任务中都包含了一个微任务队列。 微任务:等宏任务中的主要功能都完成后,渲染引擎不急着去执行下一个宏任务,而是执行当前宏任务中的微任务 宏任务包含:执行script标签内部代码、setTimeout/setInterval、ajax请、postMessageMessageChannel、setImmediate,I/O(Node.js) 微任务包含:Promise、MutonObserver、Object.observe、process.nextTick(Node.js)
说一说Vue3.0 实现数据双向绑定的方法 ?
Vue3.0 是通过Proxy实现的数据双向绑定,Proxy是ES6中新增的一个特性,实现的过程是在目标对象之前设置了一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。 用法: ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。 var proxy = new Proxy(target, handler) target: 是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 handler: 是一个对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。
说一下Diff算法?
Diff算法比较过程 第一步:patch函数中对新老节点进行比较 如果新节点不存在就销毁老节点 如果老节点不存在,直接创建新的节点 当两个节点是相同节点的时候,进入 patctVnode 的过程,比较两个节点的内部 第二步:patchVnode函数比较两个虚拟节点内部 如果两个虚拟节点完全相同,返回 当前vnode 的children 不是textNode,再分成三种情况 - 有新children,没有旧children,创建新的 - 没有新children,有旧children,删除旧的 - 新children、旧children都有,执行updateChildren
比较children的差异,这里就是diff算法的核心 当前vnode 的children 是textNode,直接更新text 第三步:updateChildren函数子节点进行比较 - 第一步 头头比较。若相似,旧头新头指针后移(即 oldStartIdx++
&& newStartIdx++
),真实dom不变,进入下一次循环;不相似,进入第二步。 - 第二步 尾尾比较。若相似,旧尾新尾指针前移(即 oldEndIdx--
&& newEndIdx--
),真实dom不变,进入下一次循环;不相似,进入第三步。 - 第三步 头尾比较。若相似,旧头指针后移,新尾指针前移(即 oldStartIdx++
&& newEndIdx--
),未确认dom序列中的头移到尾,进入下一次循环;不相似,进入第四步。 - 第四步 尾头比较。若相似,旧尾指针前移,新头指针后移(即 oldEndIdx--
&& newStartIdx++
),未确认dom序列中的尾移到头,进入下一次循环;不相似,进入第五步。 - 第五步 若节点有key且在旧子节点数组中找到sameVnode(tag和key都一致),则将其dom移动到当前真实dom序列的头部,新头指针后移(即 newStartIdx++
);否则,vnode对应的dom(vnode[newStartIdx].elm
)插入当前真实dom序列的头部,新头指针后移(即 newStartIdx++
)。 - 但结束循环后,有两种情况需要考虑: - 新的字节点数组(newCh)被遍历完(newStartIdx > newEndIdx
)。那就需要把多余的旧dom(oldStartIdx -> oldEndIdx
)都删除,上述例子中就是c,d
; - 新的字节点数组(oldCh)被遍历完(oldStartIdx > oldEndIdx
)。那就需要把多余的新dom(newStartIdx -> newEndIdx
)都添加。
说一说三栏布局的实现方案
三栏布局,要求左右两边盒子宽度固定,中间盒子宽度自适应,盒子的高度都是随内容撑高的,一般都是中间盒子内容较多,为了保证页面渲染快,在写结构的时候,需要把中间盒子放在左右盒子的前面。 实现三栏布局的方法通常是圣杯布局和双飞翼布局。 圣杯布局的实现方案:三个元素放在同一个父级元素中,代表中间盒子的元素放在最前面,父级盒子设置左右padding
,三个盒子全部浮动,设置中间盒子宽度100%,左右盒子设置固定宽度,设置左边盒子左边距-100%同时相对自身定位,右边平移自身宽度,右边盒子设置右边距-自身宽度,最后设置父级盒子清除浮动,否则父级盒子的高度无法被撑开 双飞翼布局的实现方案:三个盒子对应三个元素,其中中间盒子套了两层,中间盒子内部盒子设置margin
,三个盒子全部浮动,设置中间盒子宽度100%,左右盒子设置固定宽度,设置左边盒子左边距-100%,右边盒子设置右边距-自身宽度,最后设置父级盒子清除浮动,否则父级盒子的高度无法被撑开
说一下浏览器垃圾回收机制?
浏览器垃圾回收机制根据数据的存储方式分为栈垃圾回收和堆垃圾回收。 栈垃圾回收的方式非常简便,当一个函数执行结束之后,JavaScript 引擎会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文,遵循先进后出的原则。 堆垃圾回收,当函数直接结束,栈空间处理完成了,但是堆空间的数据虽然没有被引用,但是还是存储在堆空间中,需要垃圾回收器将堆空间中的垃圾数据回收。为了使垃圾回收达到更好的效果,根据对象的生命周期不一样,使用不同的垃圾回收的算法。在 V8 中会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。新生区中使用Scavenge算法,老生区中使用标记-清除算法和标记-整理算法。
说一说 vue 的 keep-alive ?
<keep-alive>
作用:缓存组件,提升性能,避免重复加载一些不需要经常变动且内容较多的组件。 <keep-alive>
的使用方法:使用<keep-alive>
标签对需要缓存的组件进行包裹,默认情况下被<keep-alive>
标签包裹的组件都会进行缓存,区分被包裹的组件是否缓存有两种方法,第一种是给keepalive 添加属性,组件名称指的是具体组件添加的name,不是路由里面的name。include 包含的组件(可以为字符串,数组,以及正则表达式,只有匹配的组件会被缓存)。exclude 排除的组件(以为字符串,数组,以及正则表达式,任何匹配的组件都不会被缓存)。第二种也是最常用的一种是,和路由配合使用:在路由中添加meta属性。 使用keepalive导致组件不重新加载,也就不会重新执行生命周期的函数,如果要解决这个问题,就需要两个属性进入时触发:activated 退出时触发:deactivated
CSRF攻击是什么?
CSRF跨站点请求伪造(Cross Site Request Forgery)和XSS攻击一样,有巨大的危害性,就是攻击者盗用了用户的身份,以用户的身份发送恶意请求,但是对服务器来说这个请求是合理的,这样就完成了攻击者的目标。CSRF攻击的过程原理是: - 用户打开浏览器,访问目标网站A,输入用户名和密码请求登录 - 用户信息在通过认证后,网站A产生一个cookie信息返回给浏览器,这个时候用户以可正常发送请求到网站A - 用户在没有退出网站A之前在同一个浏览器打开了另一个新网站B。 - 新网站B收到用户请求之后返回一些攻击代码,并发出一个请求要求访问返回cookie的网站A - 浏览器收到这些攻击性代码之后根据新网站B的请求在用户不知道的情况下以用户的权限操作了cookie并向网站A服务器发起了合法的请求。 预防CSRF攻击主要有以下策略: - 使用验证码,在表单中添加一个随机的数字或者字母验证码,强制要求用户和应用进行直接的交互。 - HTTP中Referer字段,检查是不是从正确的域名访问过来,它记录了HTTP请求的来源地址。 - 使用token验证,在HTTP请求头中添加token字段,并且在服务器端建立一个拦截器验证这个token,如果token不对,就拒绝这个请求
XSS攻击是什么?
XSS是跨站脚本攻击(Cross Site Scripting),不写为CSS是为了避免和层叠样式表(Cascading Style Sheets)的缩写混淆,所以将跨站脚本攻击写为XSS。攻击者可以通过向Web页面里面插入script代码,当用户浏览这个页面时,就会运行被插入的script代码,达到攻击者的目的。XSS的危害一般是泄露用户的登录信息cookie,攻击者可以通过cookie绕过登录步骤直接进入站点。XSS的分类分为反射型和存储型。反射型就是临时通过url访问网站,网站服务端将恶意代码从url中取出,拼接在HTML中返回给浏览器,用户就会执行恶意代码。存储型就是将恶意代码以留言的形式保存在服务器数据库,任何访问网站的人都会受到攻击。预防XSS攻击的方案基本是对数据进行严格的输出编码,比如HTML元素的编码,JavaScript编码,css编码,url编码等等。
说一说js继承的方法和优缺点?
原型链继承:让一个构造函数的原型是另一个类型的实例,那么这个构造函数new出来的实例就具有该实例的属性,原型链继承的。优点:写法方便简洁,容易理解。缺点:在父类型构造函数中定义的引用类型值的实例属性,会在子类型原型上变成原型属性被所有子类型实例所共享。同时在创建子类型的实例时,不能向超类型的构造函数中传递参数。 借用构造函数继承:在子类型构造函数的内部调用父类型构造函数;使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上。优点:解决了原型链实现继承的不能传参的问题和父类的原型共享的问题。缺点:借用构造函数的缺点是方法都在构造函数中定义,因此无法实现函数复用。在父类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。 组合继承:将原型链和借用构造函数的组合到一块。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。优点就是解决了原型链继承和借用构造函数继承造成的影响。缺点是无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部 原型式继承:在一个函数A内部创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。本质上,函数A是对传入的对象执行了一次浅复制。ECMAScript 5通过增加Object.create()方法将原型式继承的概念规范化了。这个方法接收两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个可选)。在只有一个参数时,Object.create()与这里的函数A方法效果相同。优点是:不需要单独创建构造函数。缺点是:属性中包含的引用值始终会在相关对象间共享 寄生式继承:寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。优点:写法简单,不需要单独创建构造函数。缺点:通过寄生式继承给对象添加函数会导致函数难以重用。 寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。优点是:高效率只调用一次父构造函数,并且因此避免了在子原型上面创建不必要,多余的属性。与此同时,原型链还能保持不变;缺点是:代码复杂
说一说defer和async区别?
浏览器会立即加载JS文件并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行 加上async属性,加载JS文档和渲染文档可以同时进行(异步),当JS加载完成,JS代码立即执行,会阻塞HTML渲染。
加上defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),当HTML渲染完成,才会执行JS代码。
说一下浏览器如何渲染页面的?
浏览器拿到HTML,先将HTML转换成dom树,再将CSS样式转换成stylesheet,根据dom树和stylesheet创建布局树,对布局树进行分层,为每个图层生成绘制列表,再将图层分成图块,紧接着光栅化将图块转换成位图,最后合成绘制生成页面。
说一说computed和watch的区别?
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值; watch: 更多的是观察的作用,支持异步,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
说一说 Vue 中 $nextTick 作用与原理?
Vue 在更新 DOM 时是异步执行的,在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。所以修改完数据,立即在方法中获取DOM,获取的仍然是未修改的DOM。 $nextTick的作用是:该方法中的代码会在当前渲染完成后执行,就解决了异步渲染获取不到更新后DOM的问题了。 n e x t T i c k 的原理: nextTick的原理: nextTick的原理:nextTick本质是返回一个Promise
说一说new会发生什么?
new
关键字会进行如下的操作: 1. 创建一个空的简单JavaScript对象(即{}
); 2. 为步骤1新创建的对象添加属性__proto__
,将该属性链接至构造函数的原型对象 ; 3. 将步骤1新创建的对象作为this
的上下文 ; 4. 如果该函数没有返回对象,则返回this
。
说一下token 能放在cookie中吗?
能。 token一般是用来判断用户是否登录的,它内部包含的信息有:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串) token
可以存放在Cookie
中,token
是否过期,应该由后端来判断,不该前端来判断,所以token
存储在cookie
中只要不设置cookie
的过期时间就ok了,如果 token
失效,就让后端在接口中返回固定的状态表示token
失效,需要重新登录,再重新登录的时候,重新设置 cookie
中的 token
就行。
说一下浏览器输入URL发生了什么?
输入地址,浏览器查找域名的 IP 地址。 浏览器向 该 IP 地址的web 服务器发送一个 HTTP 请求,在发送请求之前浏览器和服务器建立TCP的三次握手,判断是否是HTTP缓存,如果是强制缓存且在有效期内,不再向服务器发请求,如果是HTTP协商缓存向后端发送请求且和后端服务器对比,在有效期内,服务器返回304,直接从浏览器获取数据,如果不在有效期内服务器返回200,返回新数据。 请求发送出去服务器返回重定向,浏览器再按照重定向的地址重新发送请求。 如果请求的参数有问题,服务器端返回404,如果服务器端挂了返回500。 如果有数据一切正常,当浏览器拿到服务器的数据之后,开始渲染页面同时获取HTML页面中图片、音频、视频、CSS、JS,在这期间获取到JS文件之后,会直接执行JS代码,阻塞浏览器渲染,因为渲染引擎和JS引擎互斥,不能同时工作,所以通常把Script标签放在body标签的底部。 渲染过程就是先将HTML转换成dom树,再将CSS样式转换成stylesheet,根据dom树和stylesheet创建布局树,对布局树进行分层,为每个图层生成绘制列表,再将图层分成图块,紧接着光栅化将图块转换成位图,最后合成绘制生成页面。
说一说组件通信的方式?
Vue组件的通信方式分为两大类,一类是父子组件通信,另一类是任何关系类型组件通信(父子、兄弟、非兄弟) 父子组件的通信方式: 父给子传递数据,通过给子组件添加自定义属性,比如:,list是父组件给子组件传递的数据。子获取父的数据,在子组件中使用props属性获取 子给父传递数据,通过给子组件传递父组件的方法,子组件调用父组件的方法传递数据,比如:
,deleteHandler就是父组件的函数,在子组件中通过this.
e
m
i
t
(
′
方法
名
′
,
参数
)
,调用父组件的方法,并把数据传递到父组件。
p
r
o
p
s
是只读,不可以被修改,所有被修改都会失效和被警告任何关系类型组件通信(父子、兄弟、非兄弟)方式:
E
v
e
n
t
B
u
s
:使用方法是创建一个新的
V
u
e
实例,需要通信的组件都引入该
V
u
e
实例,传递数据的组件使用
‘
e
v
e
n
t
.
emit('方法名',参数),调用父组件的方法,并把数据传递到父组件。 props是只读,不可以被修改,所有被修改都会失效和被警告 任何关系类型组件通信(父子、兄弟、非兄弟)方式: EventBus: 使用方法是创建一个新的Vue实例,需要通信的组件都引入该Vue实例,传递数据的组件使用` event.
emit(′方法名′,参数),调用父组件的方法,并把数据传递到父组件。props是只读,不可以被修改,所有被修改都会失效和被警告任何关系类型组件通信(父子、兄弟、非兄弟)方式:EventBus:使用方法是创建一个新的Vue实例,需要通信的组件都引入该Vue实例,传递数据的组件使用‘event.emit(‘名称’,参数)发送数据,接收数据的组件使用
event.$on(‘名称’,方法)`接收数据。 VueX: 集中管理项目公共数据,Vuex 的状态存储是响应式的,当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。
说一说 v-if 和 v-show区别?
作用: 都是控制元素隐藏和显示的指令 区别: v-show: 控制的元素无论是true还是false,都被渲染出来了,通过display:none控制元素隐藏 v-if: 控制的元素是true,进行渲染,如果是false不渲染,根本在dom树结构中不显示
React生命周期的各个阶段是什么?
React的生命周期中常用的有:constructor,负责数据初始化。render,将jsx转换成真实的dom节点。componentDidMount,组件第一次渲染完成时触发。componentDidUpdate,组件更新完成时触发。componentWillUnmount,组件销毁和卸载时触发。不常用的有:getDerivedStateFromProps,更新state和处理回调。shouldComponentUpdate,用于性能优化。getSnapshotBeforeUpdate,替代了componentWillUpdate
React组件间传值的方法有哪些?
React中组件之间的传值方法有很多,按照不同的组件间关系可以把组件传值的方法分为父子组件传值,跨级组件传值和非嵌套关系组件传值。父子组件常用的传值方法是当父组件给子组件传值时通过props,子组件向父组件传值通过回调函数来传值。跨级组件常用的传值方法是props一层一层的传,或者使用context对象,将父组件设置为生产者而子组件都设置对应的contextType即可。非嵌套关系组件传值的方法是使用共同的父级组件进行props传值,或者通过context传值,推荐使用发布/订阅的自定义事件传值。
说一说盒模型?
CSS盒模型定义了盒的每个部分包含 margin, border, padding, content 。根据盒子大小的计算方式不同盒模型分成了两种,标准盒模型和怪异盒模型。 标准模型,给盒设置 width
和 height
,实际设置的是 content box。padding
和 border
再加上设置的宽高一起决定整个盒子的大小。 怪异盒模型,给盒设置 width
和 height
,包含了padding
和border
,设置的 width
和 height
就是盒子实际的大小 默认情况下,盒模型都是标准盒模型 设置标准盒模型:box-sizing:content-box
设置怪异盒模型:box-sizing:border-box
说一说伪数组和数组的区别?
伪数组它的类型不是Array,而是Object,而数组类型是Array。可以使用的length属性查看长度,也可以使用[index]获取某个元素,但是不能使用数组的其他方法,也不能改变长度,遍历使用for in方法。 伪数组的常见场景: -函数的参数arguments -原生js获取DOM:document.querySelector(‘div’) 等 -jquery获取DOM:$(“div”)
说一说如何实现可过期的localstorage数据?
localStorage只能用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除。所以要实现可过期的localStorage缓存的中重点就是:如何清理过期的缓存。 目前有两种方法,一种是惰性删除,另一种是定时删除。 惰性删除是指某个键值过期后,该键值不会被马上删除,而是等到下次被使用的时候,才会被检查到过期,此时才能得到删除。实现方法是,存储的数据类型是个对象,该对象有两个key,一个是要存储的value值,另一个是当前时间。获取数据的时候,拿到存储的时间和当前时间做对比,如果超过过期时间就清除Cookie。 定时删除是指,每隔一段时间执行一次删除操作,并通过限制删除操作执行的次数和频率,来减少删除操作对CPU的长期占用。另一方面定时删除也有效的减少了因惰性删除带来的对localStorage空间的浪费。实现过程,获取所有设置过期时间的key判断是否过期,过期就存储到数组中,遍历数组,每隔1S(固定时间)删除5个(固定个数),直到把数组中的key从localstorage中全部删除
说一说axios的拦截器原理及应用?
axios的拦截器的应用场景: 请求拦截器用于在接口请求之前做的处理,比如为每个请求带上相应的参数(token,时间戳等)。 返回拦截器用于在接口返回之后做的处理,比如对返回的状态进行判断(token是否过期)。 xios为开发者提供了这样一个API:拦截器。拦截器分为 请求(request)拦截器和 响应(response)拦截器。 拦截器原理:创建一个chn数组,数组中保存了拦截器相应方法以及dispatchRequest(dispatchRequest这个函数调用才会真正的开始下发请求),把请求拦截器的方法放到chn数组中dispatchRequest的前面,把响应拦截器的方法放到chn数组中dispatchRequest的后面,把请求拦截器和相应拦截器forEach将它们分unshift,push到chn数组中,为了保证它们的执行顺序,需要使用promise,以出队列的方式对chn数组中的方法挨个执行。
说一说创建ajax过程?
创建ajax过程: 1. 创建XHR对象:new XMLHttpRequest() 2. 设置请求参数:request.open(Method, 服务器接口地址); 3. 发送请求: request.send(),如果是get请求不需要参数,post请求需要参数request.send(data) 4. 监听请求成功后的状态变化:根据状态码进行相应的处理。 XHR.onreadystatechange = function () { if (XHR.readyState == 4 && XHR.status == 200) { console.log(XHR.responseText); // 主动释放,JS本身也会回收的 XHR = null; } };
说一下fetch 请求方式?
fetch是一种HTTP数据请求的方式,是XMLHttpRequest的一种替代方案。Fetch函数就是原生js,没有使用XMLHttpRequest对象。fetch()方法返回一个Promise解析Response来自Request显示状态(成功与否)的方法。
说一下有什么方法可以保持前后端实时通信?
保持前后端实时通信的方法有以下几种: 轮询是客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。其缺点也很明显:连接数会很多,一个接受,一个发送。而且每次发送请求都会有Http的Header,会很耗流量,也会消耗CPU的利用率。优点就是实现简单,无需做过多的更改。缺点是轮询的间隔过长,会导致用户不能及时接收到更新的数据;轮询的间隔过短,会导致查询请求过多,增加服务器端的负担 长轮询是对轮询的改进版,客户端发送HTTP给服务器之后,如果没有新消息,就一直等待。有新消息,才会返回给客户端。在某种程度上减小了网络带宽和CPU利用率等问题。由于http数据包的头部数据量往往很大(通常有400多个字节),但是真正被服务器需要的数据却很少(有时只有10个字节左右),这样的数据包在网络上周期性的传输,难免对网络带宽是一种浪费。优点是做了优化,有较好的时效性。缺点是保持连接会消耗资源; 服务器没有返回有效数据,程序超时。 iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长连接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。优点是消息能够实时到达;浏览器兼容好。缺点是服务器维护一个长连接会增加开销;IE、chrome、Firefox会显示加载没有完成,图标会不停旋转。 WebSocket是类似Socket的TCP长连接的通讯模式,一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。在客户端断开WebSocket连接或Server端断掉连接前,不需要客户端和服务端重新发起连接请求。在海量并发和客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。缺点是浏览器支持程度不一致,不支持断开重连。 SSE(Server-Sent Event)是建立在浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。SSE 是单向通道,只能服务器向浏览器发送,因为 streaming 本质上就是下载。 优点是SSE 使用 HTTP 协议,现有的服务器软件都支持。SSE 属于轻量级,使用简单;SSE 默认支持断线重连;
说一下重绘、重排区别如何避免?
重排 :当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。 重绘:当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,所以重绘跳过了创建布局树和分层的阶段。 重排需要重新计算布局树,重绘不需要,重排必定发生重绘,但是涉及到重绘不一定要重排。涉及到重排对性能的消耗更多一些。 触发重排的方法: -页面初始渲染,这是开销最大的一次重排 -添加/删除可见的DOM元素 -改变元素位置 -改变元素尺寸,比如边距、填充、边框、宽度和高度等 -改变元素内容,比如文字数量,图片大小等 -改变元素字体大小 -改变浏览器窗口尺寸,比如resize事件发生时 -激活CSS伪类(例如::hover
) -设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow -查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等 避免重排的方式 -样式集中改变 -使用 absolute 或 fixed 脱离文档流 -使用GPU加速:transform
说一说 Vue 列表为什么加 key?
为了性能优化 因为vue是虚拟DOM,更新DOM时用diff算法对节点进行一一比对,比如有很多li元素,要在某个位置插入一个li元素,但没有给li上加key,那么在进行运算的时候,就会将所有li元素重新渲染一遍,但是如果有key,那么它就会按照key一一比对li元素,只需要创建新的li元素,插入即可,不需要对其他元素进行修改和重新渲染
说一说vue-router 实现懒加载的方法?
vue-router 实现懒加载的方法有两种: ES6的impot方式: component: () => import(/* webpackChunkName: “about” */ ‘…/views/About.vue’), VUE中的异步组件进行懒加载方式: component: resolve=>(require([‘…/views/About’],resolve))
ReactRouter基本用法是什么?
react的路由保证了界面和URL的同步,拥有简单的API和强大的功能。react中的路由模式有两种,分别是:hash路由和history路由。 - 首先用析构的方法引入需要用到的路由方式,需要注意的是路由所有的配置都必须被包裹在hash路由或者history路由里面。 - 然后在路由标签内先再配置Route标签,它的参数有:path,路由匹配的路径。component,路由匹配成功之后渲染的组件。 - react中路由的跳转使用Link标签,它的参数to指路由匹配的路径,也需要引入。NavLink标签和Link的区别就是渲染成a标签之后会自带一个class属性,对应的是NavLink标签的active属性。 - react路由中有高阶路由组件withRouter,它和普通路由一样需要引入,主要作用是增加了路由跳转的方式,可以调用history方法进行函数中路由的跳转。 - react中路由的动态传值是一个重点,{/:属性名}和{/属性名/值}搭配的方式进行传值,在需要接收参数的组件通过this.props.match.params来进行接收。react中路由的query传值是通过问号的方法将参数拼接在url之后,在需要接收参数的组件通过url.parse(this.props.location.search).query获取参数。 - 路由的重定向需要用组件Redirect来完成,参数to是目标组件。 - 路由的懒加载需要从react中引入Suspense和lazy,引入组件时通过lazy(() => import())来引入,使用Suspense标签将Route包裹起来即可。
setState是同步还是异步的?
setState在合成事件和生命周期函数中是异步的,在原生事件和定时器中都是同步的。
React事件绑定原理
React中event事件不是原生事件,而是对原生event进行了封装的新类SyntheticBaseEvent,模拟出DOM事件的所有功能,通过event.nativeEvent可以获取到原生事件。 - React17版本开始所有事件都绑定在root根组件上,之前都是绑定在document上。 - React中event和DOM事件不一样,和Vue也不一样。
React中hooks的优缺点是什么?
优点: - 代码的可读性强,在使用hooks之前比如发布/订阅自定义事件被挂载在componentDidMount生命周期中,然后需要在componentWillUnmount生命周期中将它清楚,这样就不便于开发者维护和迭代。在使用hooks之后,通过useEffect可以将componentDidMount生命周期、componentDidUpdate生命周期和componentWillUnmount生命周期聚合起来,方便代码的维护。 - 组件层级变得更浅了,在使用hooks之前通常使用高阶组件HOC的方法来复用多个组件公共的状态,增强组建的功能,这样肯定是加大了组件渲染的开销,损失了性能。但是在hooks中可以通过自定义组件useXxx()的方法将多个组件之间的共享逻辑放在自定义hook中,就可以轻松的进行数据互通。 - 不再需要考虑class组件中this指向的问题,hook在函数组件中不需要通过this.state或者this.fn来获取数据或者方法。 缺点:hooks的useEffect只包括了componentDidMount、componentDidUpdate还有componentWillUnmount这三个生命周期,对于getSnapshotBeforeUpdate和componentDidCatch等其他的生命周期没有支持。
说一说前端性能优化手段?
前端性能优化分为两类,一类是文件加载更快,另一类是文件渲染更快。 加载更快的方法: 让传输的数据包更小(压缩文件/图片):图片压缩和文件压缩 减少网络请求的次数:雪碧图/精灵图、节流防抖 减少渲染的次数:缓存(HTTP缓存、本地缓存、Vue的keep-alive缓存等) 渲染更快的方法: 提前渲染:ssr服务器端渲染 避免渲染阻塞:CSS放在HTML的head中 JS放在HTML的body底部 避免无用渲染:懒加载 减少渲染次数:对dom查询进行缓存、将dom操作合并、使用减少重排的标签
说一说性能优化有哪些性能指标,如何量化?
常用的性能优化指标 - Speed Index(lighthouse,速度指数) - TTFB(Network,第一个请求响应时间) - 页面加载时间 - 首次渲染 - 交互动作的反馈时间 - 帧率FPS(动画 ctrl+shift+p) - 异步请求完成时间 使用性能测量工具进行量化 - Chrome DevTools - 开发调试、性能评测 - Audit(Lighthouse) - Throttling 调整网络吞吐 - Performance 性能分析 - Network 网络加载分析 - Lighthouse - 网站整体质量评估 - 还可以提出优化建议 - WebPageTest - 测试多地点(球各地的用户访问你的网站的性能情况) - 全面性能报告(first view,repeat view,waterfall chart 等等) - WebPageTest 还可以进行本地安装,让你的应用在还没上线的时候就可以测试。
说一说服务端渲染?
SSR是Server Side Render简称;页面上的内容是通过服务端渲染生成的,浏览器直接显示服务端返回的html就可以了。和它对应的是,CSR是Client Side Render简称;客户端在请求时,服务端不做任何处理,直接将前端资源打包后生成的html返回给客户端,此时的html中无任何网页内容,需要客户端去加载执行js代码才能渲染生成页面内容,同时完成事件绑定,然后客户端再去通过ajax请求后端api获取数据更新视图。 服务端渲染的优势:减少网络传输,响应快,用户体验好,首屏渲染快,对搜索引擎友好,搜索引擎爬虫可以看到完整的程序源码,有利于SEO。 在Vue项目中实现服务端渲染方法:Vue在客户端渲染中也是采用一定方法将虚拟DOM渲染为真实DOM的,那么服务端的渲染流程也是通过虚拟DOM的编译来完成的,编译虚拟DOM的方法是renderToString。在Vue中,vue-server-renderer 提供一个名为 createBundleRenderer 的 API,这个API用于创建一个 render,并且自带renderToString方法。
事件扩展符用过吗(…),什么场景下?
展开语法(Spread syntax), 可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。常见的场景:等价于apply的方式、将数组展开为构造函数的参数、字面量数组或字符串连接不需要使用concat等方法了、构造字面量对象时,进行浅克隆或者属性拷贝
说一说vue钩子函数?
钩子函数用来描述一个组件从引入到退出的全过程中的某个过程,整个过程称为生命周期。 钩子函数按照组件生命周期的过程分为,挂载阶段=>更新阶段=>销毁阶段。 每个阶段对应的钩子函数 挂载阶段:beforeCreate、created、beforeMounted、mounted 更新阶段:beforeUpdate、updated 销毁阶段:beforeDestroy、destroyed 每个阶段特点与适合做什么 created:实例创建完成,可访问data、computed、watch、methods上的方法和数据,未挂载到DOM,不能访问到el属性,el属性,ref属性内容为空数组常用于简单的ajax请求,页面的初始化 beforeMount:在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数 mounted:实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,$ref属性可以访问常用于获取VNode信息和操作,ajax请求 beforeupdate:响应式数据更新时调用,发生在虚拟DOM打补丁之前,适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器 updated:虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作避免在这个钩子函数中操作数据,可能陷入死循环 beforeDestroy:实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例,常用于销毁定时器、解绑全局事件、销毁插件对象等操作