评论区可以纠错完善,也可以留言面试题目css部分rem原理rem布局的本质是等比缩放,一般是基于宽度,假设将屏幕宽度分为100份,每份宽度是1rem,1rem的宽度是屏幕宽度/100,,然后子元素设置rem单位的属性,通过改变html元素的字体大小,就可以设置子元素的实际大小。rem布局加载闪烁的问题解决方案,媒体查询设置根元素字体大小,比如设计稿是750px;对应的开发方式是1rem=100px,那375px的font-size 大小就是50px(具体方法可以百度一下)比rem更好的方案(缺点兼容不好)vw(1vw是视口宽度的1%,100vw就是视口宽度),vh(100vh就是视口高度)实现三栏布局(两侧定宽,中间自适应)采用了 absolute,导致父元素脱离了文档流,那所有的子元素也需要脱离文档流。如果页面复杂,那开发的难度可想而知利用浮动 当中间内容高于两侧时,两侧高度不会随中间内容变高而变高弹性盒子布局(flex)利用负边距和浮动,实现起来比较复杂利用网格布局.container { display: grid; grid-template-columns: 100px auto 200px;}BFC(块级格式化上下文)BFC 的原理其实也就是 BFC 的渲染规则(能说出以下四点就够了)。包括:1. BFC 内部的子元素,在垂直方向,边距会发生重叠。2. BFC在页面中是独立的容器,外面的元素不会影响里面的元素,反之亦然。3. BFC区域不与旁边的float box区域重叠。(可以用来清除浮动带来的影响)。4. 计算BFC的高度时,浮动的子元素也参与计算。如何生成BFC方法1:overflow: 不为visible,可以让属性是 hidden、auto。【最常用】方法2:浮动中:float的属性值不为none。意思是,只要设置了浮动,当前元素就创建了BFC。方法3:定位中:只要posiiton的值不是 static或者是relative即可,可以是absolute或fixed,也就生成了一个BFC。方法4:display为inline-block, table-cell, table-caption, flex, inline-flexBFC应用阻止margin重叠可以包含浮动元素 —— 清除内部浮动(清除浮动的原理是两个div都位于同一个 BFC 区域之中)自适应两栏布局可以阻止元素被浮动元素覆盖flex(面试常问,略)js部分call, apply, bind区别? 怎么实现call,apply方法Function.prototype.myBind = function(content) { if(type of this !=‘function’){ throw Error(’not a function’) } let _this = this; let args = […arguments].slice(1) let resFn=function(){ return _this.apply(this instanceof resFn?this:content,content.concat(…arguments)) } return resFn}; /** * 每个函数都可以调用call方法,来改变当前这个函数执行的this关键字,并且支持传入参数 /Function.prototype.myCall=function(context=window){ context.fn = this;//此处this是指调用myCall的function let args=[…arguments].slice(1); let result=content.fn(…args) //将this指向销毁 delete context.fn; return result;}/* * apply函数传入的是this指向和参数数组 /Function.prototype.myApply = function(context=window) { context.fn = this; let result; if(arguments[1]){ result=context.fn(…arguments[1]) }else{ result=context.fn() } //将this指向销毁 delete context.fn; return result;}函数柯里化js继承,构造函数,原型链,构造函数、原型链组合式继承,寄生式组合继承,Object.create polyfill;数组去重[…new Set(arr]var arr = [1,2,1,2,3,5,4,5,3,4,4,4,4], init=[]var result = arr.sort().reduce((init, current)=>{ console.log(init,current) if(init.length===0 || init[init.length-1]!==current){ init.push(current); } return init;}, []);console.log(result);//1,2,3,4,5防抖节流var deBounce=function(fn,wait=300){ let timer return function(){ if(timer){ clearTimeOut(timer) } timer=setTimeOut(()=>{ fn.apply(this,arguments) },wait) } } var throttle = function (fn, wait = 300) { let prev = +new Date(); return function () { const args = argument, now = +new Date(); if (now > prev + wait) { prev = now; fn.apply(this, args) } } }实现Promise思路//0 pending,1 resolve,2 reject function Promise(fn) {… this._state = 0 // 状态标记 doResolve(fn, this)}function doResolve(fn, self) { var done = false // 保证只执行一个监听 try { fn(function(value) { if (done) return done = true resolve(self, value) }, function(reason) { if (done) return; done = true reject(self, value) }) } catch (err) { if (done) return done = true reject(self, err) }}function resolve(self, newValue) { try { self._state = 1; … } catch (err) { reject(self, err) }}function reject(self, newValue) { self._state = 2; … if (!self._handled) { Promise._unhandledRejectionFn(self._value); }}实现深拷贝funtion deepCopy(obj){ let result; if(typeofObj==‘object’){ //复杂数据类型 result=obj.constructor==Array?[]:{} for (let i in obj){ result[i]=typeof obj[i]==‘object’?deepCopy(obj[i]):obj[i] } }else{ //简单数据类型 result=obj } return result}正则实现千位分隔符function commafy(num) { return num && num .toString() .replace(/(\d)(?=(\d{3})+.)/g, function($0, $1) { return $1 + “,”; }); } console.log(commafy(1312567.903000))js事件循环javascript是单线程语言,任务设计成了两类,同步任务和异步任务同步和异步任务分别进入不同的执行“场所”,同步进入主线程,异步进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。主线程内的任务执行完毕为空,回去了Event Queue读取对应的函数,进入主线程。上述过程会不断重复,也就是常说的Event Loop(事件循环)。但是,JS异步还有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入event queue,然后再执行微任务,将微任务放入eventqueue,但是,这两个queue不是一个queue。当你往外拿的时候先从微任务里拿这个回调函数,然后再从宏任务的queue拿宏任务的回调函数宏任务一般包括:整体代码script,setTimeout,setInterval。微任务:Promise,process.nextTick事件流机制,事件委托 event.targe和event.currentTarget的区别事件捕获处于目标阶段事件冒泡阶段事件委托 事件委托是指将事件绑定目标元素的到父元素上,利用冒泡机制触发该事件可以减少事件注册,节省大量内存占用可以将事件应用于动态添加的子元素上event.target返回触发事件的元素event.currentTarget返回绑定事件的元素new的过程以及实现new//方法1function create(){ //1.创建一个空对象 let obj={} //2.获取构造函数 let Con=[].shift.call(arguments) //3.设置空对象的原型 obj.proto=Con.prototype //4.绑定this并执行构造函数,给新对象添加属性和方法 let result=Con.apply(obj,arguments) //5.确保返回值为对象 return result instanceof Object?result:obj}//方法2//通过分析原生的new方法可以看出,在new一个函数的时候,// 会返回一个func同时在这个func里面会返回一个对象Object,// 这个对象包含父类func的属性以及隐藏的__proto__function New(f) { //返回一个func return function () { var o = {"proto": f.prototype}; f.apply(o, arguments);//继承父类的属性 return o; //返回一个Object }}封装ajax/ 封装ajax函数 * @param {string}opt.type http连接的方式,包括POST和GET两种方式 * @param {string}opt.url 发送请求的url * @param {boolean}opt.async 是否为异步请求,true为异步的,false为同步的 * @param {object}opt.data 发送的参数,格式为对象类型 * @param {function}opt.success ajax发送并接收成功调用的回调函数 */function myAjax(opt){ opt = opt || {}; opt.method = opt.method.toUpperCase() || ‘POST’; opt.url = opt.url || ‘’; opt.async = opt.async || true; opt.data = opt.data || null; opt.success = opt.success || function () {} let xmlHttp = null; if (XMLHttpRequest) { xmlHttp = new XMLHttpRequest(); }else{ xmlHttp =new ActiveXObject(‘Microsoft.XMLHTTP’) } let params; for (var key in opt.data){ params.push(key + ‘=’ + opt.data[key]); } let postData = params.join(’&’); if (opt.method.toUpperCase() === ‘POST’) { xmlHttp.open(opt.method, opt.url, opt.async); xmlHttp.setRequestHeader(‘Content-Type’, ‘application/x-www-form-urlencoded;charset=utf-8’); xmlHttp.send(postData); }else if (opt.method.toUpperCase() === ‘GET’) { xmlHttp.open(opt.method, opt.url + ‘?’ + postData, opt.async); xmlHttp.send(null); } xmlHttp.onreadystatechange= function () { if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { opt.success(xmlHttp.responseText);//如果是json数据可以在这使用opt.success(JSON.parse( xmlHttp.responseText)) } };}url拿参数var url = “http://www.taobao.com/index.php?key0=0&key1=1&key2=2";function parseQueryString(url){ var str = url.split(”?")[1], //通过?得到一个数组,取?后面的参数 items = str.split("&"); //分割成数组 var arr,name,value; for(var i=0; i<items.length; i++){ arr = items[i].split("="); //[“key0”, “0”] name = arr[0]; value = arr[1]; this[name] = value; }}var obj = new parseQueryString(url);alert(obj.key2)HTTP部分http协议HTTP协议(超文本传输协议)1.0 协议缺陷:无法复用链接,完成即断开,重新慢启动和 TCP 3次握手head of line blocking: 线头阻塞,导致请求之间互相影响1.1 改进:长连接(默认 keep-alive),复用host 字段指定对应的虚拟站点新增功能:断点续传身份认证状态管理cache 缓存Cache-ControlExpiresLast-ModifiedEtag2.0:多路复用二进制分帧层: 应用层和传输层之间首部压缩服务端推送HTTP之请求消息Request请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本.请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息空行,请求头部后面的空行是必须的请求数据也叫主体,可以添加任意的其他数据。HTTP之响应消息ResponseHTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。消息报头,用来说明客户端要使用的一些附加信息第三部分:空行,消息报头后面的空行是必须的第四部分:响应正文,服务器返回给客户端的文本信息。在浏览器地址栏键入URL,按下回车之后会经历以下流程:浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;建立TCP连接(三次握手);浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;释放 TCP连接(四次挥手);浏览器将该 html 文本并显示内容;三次握手SYN (同步序列编号)ACK(确认字符)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。四次挥手第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。为什么建立连接是三次握手,而关闭连接却是四次挥手呢?这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。网页生成的过程,大致可以分为五步:html代码转化为domcss代码转化为cssom结合dom和cssom,生成一颗渲染树生成布局layout,即将所有的渲染树的节点进行平面合成将布局绘制paint在屏幕上(可以拓展讲一下减少浏览器渲染的回流和重绘)HTTPS的工作原理非对称加密与对称加密双剑合璧,使用非对称加密算法传递用于对称加密算法的密钥,然后使用对称加密算法进行信息传递。这样既安全又高效浏览器缓存当浏览器再次访问一个已经访问过的资源时,它会这样做:看看是否命中强缓存,如果命中,就直接使用缓存了。如果没有命中强缓存,就发请求到服务器检查是否命中协商缓存。如果命中协商缓存,服务器会返回 304 告诉浏览器使用本地缓存。否则,返回最新的资源。强缓存ExpiresCache-control协商缓存Last-Modified/If-Modified-SinceEtag/If-None-Matchvue&reactVirtual DOM其实 VNode 是对真实 DOM 的一种抽象描述,它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其它属性都是都是用来扩展 VNode 的灵活性以及实现一些特殊 feature 的。由于 VNode 只是用来映射到真实 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常轻量和简单的。Virtual DOM 除了它的数据结构的定义,映射到真实的 DOM 实际上要经历 VNode 的 create(用JS对象模拟DOM树)、diff(比较两棵虚拟DOM树的差异)、patch(把差异应用到真正的DOM树上) 等过程。diff算法diff算法比较新旧节点的时候,比较只会在同层级比较,不会跨层级比较当数据发生变化的时候会生成一个新的VNode,然后新VNode和oldNode做对比,发现不一样的地方直接修改在真实的dom上,比较新旧节点,一边比较一边给真是的dom打补丁节点设置key可以高效的利用dom(key最好不要设置成index索引)虚拟DOM diff算法主要就是对以下三种场景进行优化:tree diff对树进行分层比较,两棵树只会对同一层次的节点进行比较。(因为 DOM 节点跨层级的移动操作少到可以忽略不计)如果父节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。注意:React 官方建议不要进行 DOM 节点跨层级的操作,非常影响 React 性能。在开发组件时,保持稳定的 DOM 结构会有助于性能的提升。例如,可以通过 CSS 隐藏或显示节点,而不是真的移除或添加 DOM 节点。component diff如果是同一类型的组件,按照原策略继续比较 virtual DOM tree(tree diff)。对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。如果不是,直接替换整个组件下的所有子节点。element diff对处于同一层级的节点进行对比。这时 React 建议:添加唯一 key 进行区分。虽然只是小小的改动,性能上却发生了翻天覆地的变化!如: A B C D –> B A D C添加 key 之前: 发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D。添加 key 之后: B、D 不做任何操作,A、C 进行移动操作,即可。建议:在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。vue的响应式原理Object.defineProperty(obj, prop, descriptor)obj 是要在其上定义属性的对象;prop 是要定义或修改的属性的名称;descriptor 是将被定义或修改的属性描述符。比较核心的是 descriptor,它有很多可选键值,具体的可以去参阅它的文档。这里我们最关心的是 get 和 set,get 是一个给属性提供的 getter 方法,当我们访问了该属性的时候会触发 getter 方法;set 是一个给属性提供的 setter 方法,当我们对该属性做修改的时候会触发 setter 方法。一旦对象拥有了 getter 和 setter,我们可以简单地把这个对象称为响应式对象- 对象递归调用- 数组变异方法的解决方法:代理原型/实例方法observeobserve 方法的作用就是给非 VNode 的对象类型数据添加一个 Observer,如果已经添加过则直接返回,否则在满足一定条件下去实例化一个 Observer 对象实例。observe 的功能就是用来监测数据的变化.Observer 是一个类,它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新:依赖收集和派发更新收集依赖的目的是为了当这些响应式数据发生变化,触发它们的 setter 的时候,能知道应该通知哪些订阅者去做相应的逻辑处理,我们把这个过程叫派发更新,其实 Watcher 和 Dep 就是一个非常经典的观察者设计模式的实现派发更新就是数据发生变化的时候,触发 setter 逻辑,把在依赖过程中订阅的的所有观察者,也就是 watcher,都触发它们的 update 过程,这个过程又利用了队列做了进一步优化,在 nextTick 后执行所有 watcher 的 run,最后执行它们的回调函数vue编译Compile的过程主要分以下几步parse(生成AST)=> optimize(优化静态节点) => generate(生成render function)// 解析模板字符串生成 ASTconst ast = parse(template.trim(), options)//优化语法树optimize(ast, options)//生成代码const code = generate(ast, options)vue compute和watch的区别computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容。watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。所以一般来说需要依赖别的属性来动态获得值的时候可以使用computed,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用 watch。对vuex的理解,单向数据流vuexstate: 状态中心mutations: 更改状态actions: 异步更改状态getters: 获取状态modules: 将state分成多个modules,便于管理mutations和action的区别前端路由的两种实现原理Hash模式window对象提供了onhashchange事件来监听hash值的改变,一旦url中的hash值发生改变,便会触发该事件。History 模式popstate监听历史栈信息变化,变化时重新渲染使用pushState方法实现添加功能使用replaceState实现替换功能前端安全XSS和CSRFXSS:跨站脚本攻击,是一种网站应用程序的安全漏洞攻击,是代码注入的一种。常见方式是将恶意代码注入合法代码里隐藏起来,再诱发恶意代码,从而进行各种各样的非法活动。预防:使用XSS Filter输入过滤,对用户提交的数据进行有效性验证,仅接受指定长度范围内并符合我们期望格式的的内容提交,阻止或者忽略除此外的其他任何数据。输出转义,当需要将一个字符串输出到Web网页时,同时又不确定这个字符串中是否包括XSS特殊字符,为了确保输出内容的完整性和正确性,输出HTML属性时可以使用HTML转义编码(HTMLEncode)进行处理,输出到<script>中,可以进行JS编码。使用 HttpOnly Cookie将重要的cookie标记为httponly,这样的话当浏览器向Web服务器发起请求的时就会带上cookie字段,但是在js脚本中却不能访问这个cookie,这样就避免了XSS攻击利用JavaScript的document.cookie获取cookie。CSRF:跨站请求伪造,也称 XSRF,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。与 XSS 相比,XSS利用的是用户对指定网站的信任,CSRF利用的是网站对用户网页浏览器的信任。预防:用户操作限制——验证码机制方法:添加验证码来识别是不是用户主动去发起这个请求,由于一定强度的验证码机器无法识别,因此危险网站不能伪造一个完整的请求。优点:简单粗暴,低成本,可靠,能防范99.99%的攻击者。缺点:对用户不友好。请求来源限制——验证 HTTP Referer 字段方法:在HTTP请求头中有一个字段叫Referer,它记录了请求的来源地址。 服务器需要做的是验证这个来源地址是否合法,如果是来自一些不受信任的网站,则拒绝响应。优点:零成本,简单易实现。缺点:由于这个方法严重依赖浏览器自身,因此安全性全看浏览器。额外验证机制——token的使用方法:使用token来代替验证码验证。由于黑客并不能拿到和看到cookie里的内容,所以无法伪造一个完整的请求。基本思路如下:服务器随机产生token(比如把cookie hash化生成),存在session中,放在cookie中或者以ajax的形式交给前端。前端发请求的时候,解析cookie中的token,放到请求url里或者请求头中。服务器验证token,由于黑客无法得到或者伪造token,所以能防范csrf面试相关正在整理中的问题写过webpack loader吗vue怎么监听数组手写快排,时间复杂度,优化webpack用过吗?摇树是什么,什么场景下用过?你遇到过最难的问题是什么react 虚拟 dom实现,diff算法;手写快排,怎么优化;说下sort实现原理;解释一个你最近遇到的技术挑战