前端2-4年开发经验常见面试题汇总
常见面试题
http协议
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。常见的网络协议有:IP TCP SMTP POP3 HTTPS HTTP
http1.0,1.1,2.0,3.0的区别
HTTP协议又叫做超文本传输协议,是一种用于在web浏览器和web服务器之间交换数据的应用层协议,http协议到目前为止,所有的版本可以分为http0.9,1.0,1.1,2.0,3.0,其中普遍应用的是1.1版本,正在推进是2.0版本,以及未来的3.0版本,
http1.0:HTTP1.0规定浏览器和服务器保持短连接,浏览器每次请求都需要与服务器建立一个TCP连接,http1.0还规定下一个请求必须在前一个请求响应到达之前才能发送,如果前一个请求的响应一直不到达,那么下一个请求就不发送,后面的请求就都阻塞了,所以http1.0存在请求的队头阻塞,http1.0还不支持断点续传,每次都会传送全部的页面和数据,在只需要部分数据的情况下就会浪费多余带宽。
http1.1:不等待响应,直接发送下一个请求,HTTP1.1解决了1.0版本存在的问题,它可以保持长连接,避免每次请求都要重复建立TCP连接,提高了网络的利用率,HTTP1.1可以使用管道传输,支持多个请求同时发送,但服务器还是按照顺序先回应前面的请求,在回应后面的请求,如果前面的回应特别慢,后面就会有许多请求排队等着处理,所以http1.1还是存在响应的队头阻塞问题,另外http1.1已经可以断点续传。
http2.0:HTTP2.0是http协议的第一个主要修订版,它与前面的版本用于传递数据的方法有很大的差异,http2.0会压缩头部,如果同时有多个请求其投不一样或相似,那么协议会消除重复部分,http2.0将请求和响应消息编码为二进制,而不再使用之前的纯文本消息,增加了数据传输的效率,http2.0可以在一个TCP连接中并发多个请求或回应,而不用按照顺序一一对应,从而彻底解决了HTTP层面队头阻塞的问题,大幅度提高了连接的利用率,HTTP2.0还在一定程度上改善了传统的请求应答工作模式,服务端不再是被动地响应,而是可以主动向客户端发送消息,推送额外的资源,http2.0虽然通过多个请求复用一个TCP连接解决了HTTP的对头阻塞,但是一旦发生丢包,就会阻塞住所有的http请求,这就属于TCP层队头阻塞,为了解决这个问题HTTP3.0直接放弃使用TCP。
http3.0:HTTP3.0直接放弃使用TCP,将传输层协议改成UDP,但是因为UDP是不可靠传输,所以这就需要QUIC实现可靠机制,QUIC全称“快速UDP互联网连接”,是由Google提出的使用UDP进行多路并发传输的协议,QUIC有自己的一套机制可以保证传输的可靠性的,当某一对请求响应发生丢包时,只会阻塞当前的请求响应,其他请求响应不会受到影响,因此完全不存在队头阻塞问题,http3.0使用了UDP作为传输层协议,能够减少三次握手的时间延迟,从而达到快速建立连接的效果,此外QUIC协议可以使用连接ID来标记通信的两个端点,即使移动设备的网络发送变化,导致IP地址变化了,只要还有连接ID和TLS密钥等上下文信息,就可以复用原连接,从而实现连接迁移
闭包
1,背景:在函数内部定义的变量是局部变量,局部变量没办法直接修改;局部变量使用完毕后,会被JS垃圾回收清理
2,如果形成了闭包,局部变量就不会被清理
3,闭包的基本结构:函数嵌套函数;外层函数有局部变量;里层函数使用到了外层函数的局部变量;里层函数可以反复被调用
function wai(){ var count=0; return function li(){//里函数返回给外部,里函数生命周期得到延续 count++;//这里的局部变量count也被延续 console.log(count); } } var res=wai(); res(); res();
闭包和递归的区别:
相同点:都是函数;在函数的内部都会调用函数,都是低效率; 不同点: 1,闭包相当于函数的返回值为另一个函数,而递归则相当于函数的参数为另一个函数; 2,闭包函数调用的内部函数需要引用外部函数的变量、参数;递归函数是自己调用自己,不需要引用变量参数 3,闭包函数调用一次结束调用,递归函数在满足递归条件时需要多次调用; 4,闭包函数会比其他函数更占内存。
js中堆和栈的区别
变量接受的是基本数据类型,则存储它的值。存储在栈里面。
变量接受的是引用数据类型,则存储它的地址。值则存储在堆里面。
深拷贝和浅拷贝
1,浅拷贝只能拷贝一层,如果第一层是基本数据类型,可以用浅拷贝
注:如果第一层包含了复杂数据类型,复杂数据类型的赋值是引用关系,所以还是会互相影响
//浅拷贝,直接赋值会影响原数组 var arr1=[11,22,33,44] var arr2=arr1 arr2[0]=6666 console.log(arr1);//此时结果是arr1=[6666,22,33,44] //深拷贝 var arr1=[11,22,33,44] var arr2=[] for(var i=0;i arr2[i]=arr1[i] } arr2[0]=666 console.log(arr1,arr2)//此时结果是arr1=[11,22,33,44] arr2=[666,22,33,44] {}}计算和处理props或$emit的传值{str}},从而实现了数据双向绑定原理。 for(var j=i+1;j if(arr[i]==arr[j]){ arr.splice(j,1) len--; j--; } } } console.log(arr); color:['red','green'] } const oldArrayProto=Array.prototype; const newArrProto=Object.create(oldArrayProto); ['push','pop','shift','unshift','splice'].forEach(methodName={ newArrProto[methodName]=function(){ console.log('数组视图更新'); oldArrayProto[methodName].call(this,...arguments); } }) observer(data); function observer(target){ if(typeof target !=='object' || target === null){return target;} if(Array.isArray(target)){target._proto_=newArrProto}; /*for(let key in target){ defineReactive(target,key,target[kery]) }*/ } //function defineReactive(target,key,value){...} console.log(data.colors.push)//此时打印出来的数组push方法是上面改写后的push方法 data.colors.push('blue') name: 'lx' }, { age: 23 }] for (const val of list) { console.log(val) // 输出{ name: 'lx' }, { age: 23 } for (const key in val) { console.log(val[key]) // 输出 lx,23 } } 总结:for in适合遍历对象,for of适合遍历数组。for in遍历的是数组的索引,对象的属性,以及原型链上的属性。h3cookie和本地存储的区别/h3 pre class="brush:python;toolbar:false" cookie localStorage sessionStorage 大小 4Kb 10Mb 5Mb 兼容 H4/H5 H5 H5 访问 任何窗口 任何窗口 同一窗口 有效期 手动设置 无 到窗口关闭 存储位置 浏览器和 浏览器 浏览器 服务器 与请求一起发送 是 否 否 语法 复杂 简易 简易 /pre p 注:对cookie,编码 encode,解码 decode/p h3vue2和vue3的区别/h3 pre class="brush:python;toolbar:false"Vue2和Vue3最大的区别: 1,Vue2的对象响应式原理是通过Object.defineProperty来监听每一个key的变化。所以带来的缺点显而易 见,当对象的key增加或者删除之后,Object.defineProperty无法get到上述key的变化,无法及时响应。 2,Vue2的数组响应式原理,通过重写数组后,此时栈内存中的数组地址也产生了变化,接收者就会响应数组的变 化,缺点是当数组内数据产生变化,栈内存地址没有变更时,响应式失效。 VUe3是通过proxy(代理)来劫持每一个数据的变化,数据每一个值产生变动时,都会走到Proxy(代理)里 面,劫持数据的变化并通知接收者,然后再通过reflect反射回原数据进行更改。 VUe2 Diff 是服务器返回的HTML文本,浏览器解析,构建真实DOM,然后vue会构建虚拟DOM(js对象),如果数据更新,会先反应到虚拟DOM上,也就是一个新的js对象,然后会有diff算法对比两个虚拟DOM对象,然后patch方法,仅仅对发生变化的DOM节点进行更新。 VUe3 Diff 上述说了Vue2会对比两个新旧虚拟DOM对象,这种是全量对比,比较消耗性能,vue3在动态的DOM节点上新增了patch flag,只需要对比动态节点,触发更新即可。 host static 静态提升 Vue2中的DOM元素无论是否是静态DOM,在DOM更新时都会重新参与渲染,而在Vue3中,通过提升静态DOM达成复用避免重复渲染,以此提升性能。 /pre h3vue2源码学习/h3 pre class="brush:python;toolbar:false"1,源码用到的方法:Object.defineProperty,Object.keys; 2,当创建vue实例时,利用Object.keys将属性转为数组,进行属性的遍历,并且为每个属性添加getter和setter函 数,对数据的读取进行劫持,getter用来收集依赖,setter用来派发更新,并且通过回调函数的形式通知订阅者进行 更新。 3,组件也一样,每个组件都有相应的watcher实例,会在组件渲染过程中进行依赖收集,当依赖项被改动时,setter方 法会通知watcher实例派发更新,从而使关联的组件重新渲染。 4,为了方便对属性进行读取,Vue构造函数中为data执行了一个proxy代理函数,配和call借用函数,将属性代理到vue 实例上。 自我学习重写响应式的总结: 重写源码流程: 1,首先实现new Vue初始化选项和数据 2,通过代理,实现vm属性访问,原理是Object.defineProperty 3,将data所有的变量,实现第一层响应式变化:通过Observer观察者函数的walk方法循环每一个属性去defineReactiveData,深层次原理还是Object.defineProperty.value涉及到闭包知识。 4,变量的值可能是对象,实现对象深层次的属性响应式变化:defineReactiveData内部,对value值Observer,同时新赋予的值可能是对象,同样要对新值newValue进行Observer,这样会达到一个不同模块之间的递归。 5,变量的值也可能是数组,在对于数组也要实现每一项的响应式变化,对每一项进行Observer通过Array.isArray进行判断data是数组还是对象。 6,重写数组的7个方法,首先通过Object.create创建一个以Array的原型为原型的对象,在新对象上添加7个方法,同时保留原有7个方法的功能,通过apply借用函数改变里面的this指向来保留原来的功能。同时对每个插入的项又要进行Observer观察。当改写完7个方法后,在Observer观察者函数去改变数组的原型,通过setPrototypeOf指向刚才改造的方法。 /pre h3父子组件加载顺序/h3 pre class="brush:python;toolbar:false"1,父子组件加载顺序: 父beforeCreated-父created->父beforeMounted->子beforeCreated->子created->子beforeMounted-> 子mounted->父mounted 2,父组件更新顺序: 父beforeUpdate->父updated 3,父子组件更新顺序: 父beforeUpdate->子beforeUpdate->子updated->父updated 4,父子组件销毁顺序: 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
vue中$set方法实现思路
1,在创建Observer实例时,也创建一个新的“筐”,挂在Observer实例上,然后把Observer实例挂载到对象的_ob_属性上; 2,触发getter时,不光把watcher收集一份到之前的“筐”中,也收集一份在这个新的“筐”中; 3,用户调用$set时,手动的触发_ob_.dep.notify(); 4,最后别忘了在notify()之前调用defineReactive把新的属性也定义成响应式的;
面试学习资料
B站 前端乐哥 学习资料 【HTML+CSS+项目】 链接:https://pan.baidu.com/s/1fnlVtN0UBXRwmWep1HhBIA 提取码:ixkl 【Ts+Vue3.0(基础+项目实战)】 链接:https://pan.baidu.com/s/1OEcduMKzn4hvr8Ct8rwcFQ 提取码:fegc 【javascript基础】 链接:https://pan.baidu.com/s/1YfPnzyOIrTnXdBROP8287Q 提取码:m8fk 【javascript进阶】 链接:https://pan.baidu.com/s/1bC7d9EEgit_ro-kCCYhmGg 提取码:g7wi 【jQuery及项目实战】 链接:https://pan.baidu.com/s/1LlI8gCMgwAPW52ynY2Ff6A 提取码:mwwe
一些标签简洁写法
ul>li{这是第$个li}*9
XSS和CSRF攻击
明确考察点:明确xss和csrf是什么;明确xss和csrf的特点;如何防范xss和csrf攻击。
首先明确xss(cross-site-scripting)是跨站脚本攻击;csrf(Cross-Site-Request-Forgery)是跨站请求伪造,然后分别举一下两种攻击的例子,最后谈下两种攻击的防范措施。
xss 跨站脚本攻击 csrf 跨站请求伪造 xss:跨站脚本攻击 黑客攻击你的浏览器,篡改浏览器正常显示,窃取用户信息 xss: 浏览器向服务器请求的时候被注入脚本攻击; 分成三种类型:反射型(非持久型),存储型(持久型),基于DOM 防范手段: 1,输入过滤,进行验证 2,输出过滤,对字符进行编码转义 3,加httponly请求头,锁***cookie 对编码转义案例如下: function htmlEncodeByRegExp(str){ var s=''; if(str.length==0)return ''; s=str.replace(/&/g,"&"); s=s.replace(//g,">"); s=s.replace(/ /g," "); s=s.replace(/\'/g,"'"); s=s.replace(/\"/g,"&quto;"); return s; } var tmpStr="123
"; var html=htmlEncodeByRegExp(tmpStr); console.log(html)//<p>123</p> document.querySelector(".conent").innerHtml=html;//123
csrf 黑客通过网站B诱使用户去访问已经登录了的网站A进行一些违法用户意愿的请求 造成用户损失 防范手段: 1,服务器验证 http请求的refer头信息 2,请求的时候 传token 3,加验证码 跨站请求伪造防范 主要是在服务端做的: 1,服务器中验证请求头refer字段; 2,加token 3,加验证码
XSS:侧重于脚本,千方百计的注入并执行恶意脚本;
CSRF:不注入恶意脚本,侧重于请求伪造,借刀杀人,在用户不知情的情况下,借用户的名义干坏事。
XSS:跨站脚本攻击,用户提交的数据中可以构造恶意代码,并且执行,从而实现窃取用户信息等攻击。 修复方式: 1,对实体字符进行转义 2,使用http Only来禁止javascript读取cookie值 3,输入时校验,浏览器与web应用端采用相同的字符编码 CSRF:跨站请求伪造攻击 修复方式:筛选出需要防范CSRF的页面,然后嵌入Token,再次输入密码,校验referer XEE:XML外部实体注入攻击,敏感文件读取 修复方式:XML解析库在调用时严格禁止对外部实体的解析
路由懒加载
在配置路由的时候这样写组件的导入:
component:()=>import(/*.webpackChunkName: "about".*/.'../views/About.vue'); 加上这个:/*.webpackChunkName: "about".*/. 可以实现按需加载对应的js文件,可以在network看到文件名.js和这个文件的大小,不然就是默认:0.js 0kb
函数柯里化
特点:函数嵌套函数
函数柯里化,柯里化通常也称部分求值,其含义是给函数分步传递参数,每次传递参数进行处理,并返回一个更具体的函数接收剩下的参数,
这中间可嵌套多层这样的接收部分参数函数,直至返回最后结果
案例:
const nameList1=[ {mid:'aaa',profession:'aaref'} ] const namelist2=[ {adc:"bbb",profession:'bbref'} ] const curring=name=>val=>val[name]; const name_mid=curring('mid'); const name_adc=curring('adc'); console.log(nameList1.map(name_mid)) console.log(nameList2.map(name_adc)); 2,function add(){ let args=Array.prototype.slice.call(arguments); let inner=function(){ args.push(...arguments); return inner; } inner.toString=function(){ return args.reduce(function(prev,cur){ return prev+cur; }) } return inner; } const result=add(1)(2)(3)(4); console.log(result); // 数组求和 const sumFn = (...args) => { return args.reduce((a, b) => { return a + b }) } //进行升序排序 const sortFn = (...args) => { return args.sort((a, b) => a - b) } const currying = function (func) { const args = [] return function result(...rest) { if (rest.length === 0) { return func(...args) } else { args.push(...rest) return result } } } console.log(sumFn(1, 3)) console.log(currying(sumFn)(1)(2)(3)()) // 6 console.log(currying(sumFn)(1, 2)(3, 4)(5)()) // 15 console.log(currying(sumFn)(1)(2, 3, 4, 5)(6)()) // 21 console.log(currying(sortFn)(1)(3)(2)(6, 4)(5)()) // [1,2,3,4,5,6] // 最简单的柯里化 function add(x) { return function (y) { return x + y } } console.log(add(2)(3)) // 5
call,apply,bind的区别
var name = 'amy',age=17; var obj = { name:'tom', objAge:this.age, myFun:function(fm,t){ console.log(this.name+'年龄'+this.age,'来自'+fm+'去往'+t) } } var db ={ name:'lucy', age:99 } obj.myFun.call(db,'成都','上海'); // lucy 年龄99 来自成都去往上海 obj.myFun.apply(db,['成都','上海']); // lucy 年龄99 来自成都去往上海 obj.myFun.bind(db,'成都','上海')(); // lucy 年龄99 来自成都去往上海 obj.myFun.bind(db,['成都','上海'])(); // lucy 年龄99 来自成都,上海去往 undefined
JS判断英文字母大小写(两种方法)
在javascript中,ASCII的值在65-90这个范围内,则是大写的A-Z;ASCII的值在97-122这个范围内,则是小写的a-z;ASCII码的值在45-57这个范围内,则是数字0-9;其他的值则为其他字符。
浏览器事件循环,宏任务和微任务
JavaScript是单线程语言,所以一次只能够执行一个任务,但是遇到一些耗时任务也没有阻塞,这是因为浏览器是多线程相互协作运行的。
宏任务
宏队列,macrotask,也叫tasks。常见的宏任务有:
script(整体代码) setTimeout setInterval requestAnimationFrame(浏览器独有) 。。。
微任务
微队列,macrotask,包括:
Promise MutationObserver(html5新特性) 。。。 (注:这里只针对浏览器)
JS数据结构与算法大纲
一,初识数据结构与算法 常见的数据结构;算法 二,数组结构 创建一个数组;数组的length;数组的索引;数组的常用方法 三,栈结构 认识栈结构 封装栈结构 应用:十进制转二进制,进制转换法 四,队列 队列是什么;队列的封装;队列的应用-击鼓传花;双端队列 五,链表 单链表;双向链表;循环链表 六,集合 集合类;ES6的Set;集合的运算 七,字典 字典的封装;散列表;ES6的Map 八,树 二叉树 二叉搜索树:遍历;移除 九,二叉堆 最小堆;最大堆 十,排序算法 冒泡排序;选择排序;插入排序;归并排序;快速排序;计数排序;桶排序;基数排序 十一,搜索算法 顺序搜索;二分搜索;内插搜索 十二,随机算法 十三,算法设计 分而治之 动态规划:背包问题;最长公共子序列 贪心算法 回溯算法 十四,算法复杂度