一、HTML
1.doctype的作用是什么?
答:告诉浏览器解析器用什么文档标准解析这个文档,主要有两个模式怪异模式(默认,浏览器自己的模式)和标准模式(符合w3c标准的)。
2.什么是data-属性?
答:用于存储额外数据的属性,可以通过js获取
3.标签语义化的理解?
答:1)开发者友好,更容易维护;2)有利于seo
4.meta标签
答:用来描述网页文档的元信息的,作者、关键字、日期等等。一般由name+content组成,也有charset。http-equiv这些特殊的
5.viewport有哪些属性?
答:width/height/initail-scale/user-scalable/miniumum-scale/maxiumum-scale
6.http-quive可以定义哪些值?
答:
- expire/cache-control
- set-cookie
- refresh:重定向
- content-type
- keyword/description
7.src和href的区别
答:src是将资源下载到本文档,当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,href则是文档间的链接,直接并行下载另一文档,不阻塞本文档。
8.前端存储方式:
cookie(会自动加入请求头,数据量小,个数有限制)、sessionstorage、localstorage、web sql(关系型数据库,废弃)、indexDB(nosql数据库,h5,键值对)
9.iframe缺点
答:
- 阻塞页面的onload事件,用户体验差
- 与主页面共享连接池,慢
- 不利于SEO
二、CSS
1. 选择器优先级?
答:1)内联>id>类>标签;2)另一个范畴是!important;3)还有就是后覆盖前
2. link和@import的区别?
答:
- link为xhtml标签,二@import是css语法;
- link样式与页面同时加载,而@import则需要等到页面加载完毕;
- js无法操作@import只能操作link中样式
3.css隐藏页面元素方案?
答:1)visibility;2)display;3)opacity:0;4)z-index
4.em\px\rem区别?
答:px为绝度像素值,em为相对父元素字体大小,rem为相对于根元素(html)的大小
5.水平垂直居中
答:
//方案一:
// 父
display: flex;
justify-content: center;
align-items: center;
// 子:无
//方案二:
//父
position:relative;
//子
position:absolute;
left:50%;top:50%;
transform:translate(-50%,-50%);
//方案三:
//父
display: table-cell;
//子
text-align: center;
vertical-align: middle;
复制代码
6.怪异盒模型与标准盒模型?
答:怪异盒模型宽高为为content+padding,而标准和模型为content。
7.box-sizing有哪些值?
答:
- border-box:宽高为content+padding+border
- content-box:宽高为content
- inherit:继承父元素属性
8.说说BFC?
答:
- 定义:BFC是一块独立区域,使其内部 与外部元素互不影响
- 规则:
- BFC内元素(包括浮动 元素)不会超出其所在区域(父元素内子元素浮动时,高度坍塌)
- BFC区域不与其他float元素重叠(浮动两栏布局时,宽度自适应问题)
- BFC内外元素互不影响(兄弟区域margin重合问题,使其中一个形成BFC就不会重合了)
- 形成BFC办法
- position为fixed或absolute
- display为inline-block、table-cell、table-caption
- overflow为非visible
- float为非none
9.清除浮动的方法:
答:
- 使父元素形成BFC;
- 最后添加空元素,并设置clear:both
- 伪类设置clear:both
10.重排、重绘与合成?
答:
- 重排:reflow:尺寸发生改变时
- 重绘:repaint:屏幕的一部分进行了重画
- 合成:composition:只有css的tansform和opacity可以触发
11.伪类与伪元素?
答:伪类是假的类的概念,用于设置该元素某个状态下的样式,单冒号 伪元素则为假元素的概念,因为在dom中获取不到,可以设置内容样式等 ,双冒号
12.如何在多倍屏上实现1px边框?
答: - 阴影代替border - 根据dpr(drp=window.devicePixelRatio)设置viewport的initial-scale - background-image或者image
13.如何实现多行文本溢出?
答:
//多文本溢出省略号
{
width:xxx;//需要设置宽
overflow: hidden;//设置溢出不可见
display: -webkit-box;
-webkit-box-orient: vertical;//方向
-webkit-line-clamp: 1;//设置行数
}
//单行文本溢出(需要用不了块级元素包裹)
{
width:xxx//需要定宽
overflow: hidden;//溢出不可见
text-overflow: ellipsis;//第一行溢出
}
复制代码
14.谈一谈css modules?
答:用于对css样式进行作用域分割,原理:通过hash模式修改选择器名称,可以在css-loader中启用
15.谈一谈css in js?
答:就是将样式写在js中,主要两种模式,一种唯一选择器,一种内联样式
三、JavaScript
1.变量提升?
答:js引擎是先解析再执行,解析时会将所有变量声明提升到代码头部,然后再逐行执行代码。
2.null与undefined的区别?
答:都可表示空,undefined还可以表示为不存在
ps:检查对象属性:in、hasOwnProperty
3.谈谈对原型链的理解?
答:
- 每个函数都有一个prototype属性,这个属性也是一个对象,被称为这个函数的原型对象。当我们声明一个新函数时,这个prototype对象会默认添加一个constructor属性,指向函数本身,同时还可以人为在prototype属性上添加其他属性。
- 每个对象都有一个原型,这个原型指向的是这个对象创建时所依赖的那个对象
- 对象创建基本有四种方式:
- 字面量形式,则原型对象为object,
- object.create,则原型对象为所传参数,
- 函数类型,原型对象为function,
- new 的形式,其原型为这个函数的prototype,即prototype上的属性会被通过new形式创建的对象所共享。
- 某个对象的原型也是一个对象,还会有原型,这样一直追溯下去,知道遇到顶级object。这样就会形成原型链。
4.怎样判断一个数组?
答:
- Array.isArray(value)
- Object.prototype.toString.call(arg)==='[object Array]'
5.谈谈对this的理解?
答:
- 定义:this是代码运行时根据上下文生成的内部对象
- 具体指向:
- 默认情况下纯函数调用指向全局对象(window或global,严格模式为undefined)
- 作为对象的某个方法调用,发生隐式绑定为某对象
- call、apply、bind时,发生强制绑定
- new中指代的为当前对象
- 箭头函数中则表示为声明时外部this,若没有依次向上
6.new obj()发生了什么?
答:
- 创建一个新的空对象
- 将新的空对象的原型指向构造函数的prototype
- 执行构造函数,并将构造函数的this指向新空对象
- 构造函数执行结果有值则返回,无则返回该对象
ps:手动实现:
function createObjLikeNew(fn,param){ var o = {}; o.__proto__ = fn.prototype; var res = fn.call(obj,...param); return typeof res === 'object' ? res : o; } 复制代码
7.谈谈async/await?
答:
- async用于声明异步函数,会自动将普通函数转为promise,返回一个promise对象,await用于修饰异步代码并会等待异步执行完之后再向下执行
- 优势:
避免回调地狱 代码易读,易于调试 同步错误易于捕捉
8.js中参数是怎样传递的?
答:本质是赋值操作,简单数据类型会传值,复杂数据类型则是引用
9.怎样实现深拷贝?
答:
- JSON.parse(JSON.stringify(obj))(function、date等其他类型值无法copy,循环引用报错)
- for in 递归(循环引用和共同引用,递归之前先检查是否已经深拷贝,其他数据类型包括函数)
- postMessage和onMessage以及history.pushState
10.谈一谈事件循环?
答:
- 1)浏览器中
- 浏览器中js是怎样执行的:js代码的执行就是主线程执行调用栈内代码而调用栈内代码从任务队列中来,任务队列中的代码则是由script的加载,dom交互事件、异步事件等触发的。
- 什么叫事件循环:将代码从任务队列中调入调用栈的这套机制叫事件循环
- 事件循环的具体步骤:
- 检查微任务队列中有没有任务,有则依次执行(执行过程中可能还会产生个种宏任务和微任务,都会被追加到各自的队列中去),直至微任务队列为空(包括后代衍生的微任务),
- 进行宏任务检查,有则拿出一条宏任务任务进行执行(执行过程中也还可能会产生个种宏任务和微任务,都会被追加到各自的队列中去),直至单条宏任务执行完毕
- 重复循环step1和step2,直两个任务队列中都为空
- 怎样朝任务队列中加入任务:
- 任务来源:只有异步操作才会产生任务,同步操纵会在调用栈中直接完成
- 添加方式:在异步操作完成后,才会将对应的回调放入相应的队列。
ps:名词解释:
- 栈:后进先出
- 队列:先进先出
- 调用栈:通过栈结构维护的任务集合,初始为空
- 任务队列:任务队列是通过队列结构维护的任务集合,队列一直存在,初始为空
- 宏任务队列:定时器相关操作,script的加载,io操作,ui渲染等
- 微任务队列:promise相关操作,nextTick等
- 2)node中:
- 微任务分为nextTick和其他
- 宏任务分为六个阶段(六种)
- timer
- I/O callback
- idle prepare
- poll 轮询io
- check
- close callback
先执行微任务nextTick,再执行微任务中的其他,执行完毕后,将所有宏任务分为六个阶段依次执行,每个宏任务执行间隙执行微任务,然后进入下一次循环。
11.什么是面向对象?
答:
- 面向对象是一种编程思想,与面向过程相对。
- 编程主要是用于解决问题,在解决问题时,把问题看成是无数对象的相互作用的结果,将具有相同行为的对象抽象成不同种类的对象,研究明白这些种类的特征,更容易解决问题。
- js中的面向对象主要是基于原型和原型链实现的,其他语言比如java则是基于类的,但是js可以通过原型和原型链来模拟类。
- 类:封装:低耦合高内聚
- 多态:重载和重写
- 重载:方法名相同,参数个数或类型不同(js中不存在真正意义上的重载)
- 重写:在类的继承中,子类可以重写父类的方法
- 继承:子类继承父类中的属性和方法
12.什么是同源策略?
答:协议、域名、端口都相同,用于限制不同源之间的通信。
13.怎样解决跨域?
答:1)jsonp:返回一个前端函数,直接执行 2)设置header,白名单 3)nginx作中间层
14.怎样实现防抖?
答:
- 定义:防抖函数两次调用之间,若小于某一时间,则取消第一次调用,结果为执行最后一次操作。
- 场景:多次点击提交按钮,搜索关键字查询
- 实现:
function debounce(fn,delay){ let timeout; return (fn,delay) => { clearTimeout(timeout); timeout = setTimeout(()=>fn,delay) } } 复制代码
15.怎样实现节流?
答:
- 定义:节流是指,一个函数在一段时间内无论被调用多少次,要保证只执行一次。
- 场景:拖拽事件
- 实现:
function throttle(fn,delay){ var flag = true; return (fn,delay) => { if(flag){ fn(); flag = false; setTimeout(()=>{ flag=true },delay) } } } 复制代码
16.怎样实现一个eventbus?
答:
- 定义:
//step1:定义类,让其有个存储事件的map属性 function EventBus(){ this.eventTypeMap = new Map(); } //step2:定义emit方法,主要执行事件的回调 EventBus.prototype.emit = function(type,param){ const callBackList = this.eventTypeMap.get(type); if(callBackList && callBackList.length>0){ callBackList.forEach(callbackItem=>callBackItem.fn(param)); } } //step3:定义listen方法,主要是将回调存入map,并返回事件监听的id EventBus.prototype.listen = function(type,callBack){ const listenId = new Date().getTime(); const tempObj = { id:listenId, fn:callBack } const callBackList = this.eventTypeMap.get(type); if(callBackList){ callBackList.push(tempObj) }else{ this.eventTypeMap.set(type,[tempObj]) } return listenId; } //step4:定义removeListen,主要是将某类型的某个监听事件移除掉(需要一个事件监听的id) EventBus.prototype.removeListen = function(listenId){ this.eventTypeMap.forEach(callBackList=>{ const index = callBackList.findIndex(item=>item.id===listenId) if(!~index){ callBackList.splice(index,1) } }) } 复制代码
- 使用:
const eventBus= new EventBus(); eventBus.listen('type',callBack); eventBus.emit('type',param);//一旦发生,call就会回调 复制代码
17.怎样实现继承?
答:
- 原理:原型链+构造函数形式:
- 父类:
- 构造函数中定义父类实例私有属性和方法(不同实例不同)
- 原型上定义父类实例共用的属性和方法
- 有需要定义一些静态属性和方法
- 子类:
- 构造函数中调用父类的构造函数(本质是将父类的私有属性和方法在子类中重新定义一次)
- 将子类原型指向父类对象(通过Object.create(father),而不是new father())
- 将子类的原型对象的构造函数指向子类
- 构造函数中定义子类实例其他私有属性和方法
- 原型上定义子类实例其他共有的属性和方法
- 有需要定义一些静态属性和方法
- 父类:
- 实现:
- 子构造函数调用父构造函数方法
- 子构造函数原型指向父构造函数(通过Object.create(father),而不是new father())
- 将子类的原型对象的构造函数指向子类
18.谈谈私有属性、静态属性和原型属性?
答:这些属性或方法都是对于类而言的,会影响由此类创建的对象。
- 私有属性:由此类创建的实例所私有,挂在每个实例上,严格上说和这个类函数对象本身没有任何关系,可通过创建时传参修改或后续修改,不会影响其他实例。
- 静态属性:挂在这个类上,属于类对象的一个属性,实例不可调用,要在类上进行修改和调用
- 原型属性:挂在类函数的原型上,被所有此类创建的实例所共享,可在类的原型上进行修改,会影响所有实例。但当某实例一旦修改,实质是在该实例上重新挂了一个和其原型上相同的名称的方法,再次调用时发生屏蔽,不会再触及原型上的此方法,所以之后原型上此方法修改不再影响此实例。
19.手动实现instanceOf?
答:
function check(obj,fn){
if ( typeof fn !== 'function') {
throw new Error('参数错误')
}
if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')){
return false
}
const fP = fn.prototype
while (obj.__proto__) {
if (obj.__proto__ === fP) {
return true
}
obj = obj.__proto__
}
return false
}
复制代码
20.手动实现call?
答:
function myCall([fn,obj,...param]){
obj.fn =fn;
res = obj.fn(...param);
delete obj.fn;
return res;
}
复制代码
21.手动实现apply?
答:
function myAplay([fn,obj,param]){
obj.fn =fn;
res = obj.fn(...param);
delete obj.fn;
return res;
}
复制代码
22.手动实现bind?
答:
function myBind(fn,obj){
return () => {
fn.apply(obj,...arguments)
}
}
复制代码
23.手动实现promise?
答:与event bus类似,不同点有:
- 1)emit只在定义时触发(使其只决议一次)
- 2)then中进行订阅手机,根据状态,使其只执行一次
- 3)then的返回值为promise本身,实现链式调用
24.谈一谈强制类型转换?
答:数据类型:string、number、boolean、null、undefined、symbol、object
- 转为string:
- 规则:
- 基本数据类型:加引号,无特殊
- object调用tostring方法
- 方式:
- tostring
- String()
-
- ''
- 规则:
- 转为数字:
- 规则:
- 字符串:'' -> 0,数字字符串-> 数字, -> 其他NaN
- 布尔:false -> 0,true -> 1
- undefined:NaN
- null: 0
- symbol:报错
- 对象:先转成基本类型(valueof、tostring),成功则转为number否则报错
- 方式:
- 运算符号
- Number()
- parseInt
- 规则:
- 转为布尔值:
- 规则:
- 字符串:'' -> false,其他ture
- 数字:0,NaN为false,其他true
- null:false
- undefined:false
- symbol:true
- 对象:true
- 方式:
- Boolean()
- 表达式的判断
- 规则:
- 方式:
- 强制类型转换:
- 隐士类型转换:
25. 谈一谈垃圾回收机制?
答:
- 原理:找出不再继续使用的变量,将其占用内存释放
- 怎样找:对变量进行标识
- 怎样标识:1)计数,2)标记
- 怎样释放:周期性根据标识进行释放
- 优化:分代回收机制:根据对象的存活时间将内存进行了分代,并对不同分代的内存采用不同的高效算法进行垃圾回收
Ps:世代假说:大部分新生对象倾向于早死,不死的对象会活得更久
26.内存泄露有哪些原因?
答:
- 滥用全局变量
- 不销毁定时器和回调
- DOM引用不规范,dom的引用会是两份,一份在js中一份在dom树种,当某dom的父级删除时,其自己引用还在js中,并不会释放
- 滥用闭包:
27.为什么JavaScript里面typeof(null)的值是"object"?
答:typeof判断一个值得类型是否是object是通过其二进制的低三位全为0来判断的,而null的二进制表示全为0,所以被当做了object
28.谈一谈箭头函数?
答:
- 没有this,也无法改变this指向
- 无法访问arguments
- 不可以被new执行,没有prototype属性
29.谈一谈symbol?
答:
- 只能通过函数调用生成
- 结果为唯一值(即使参数相同),用于对象属性的唯一场景
- 遍历时无法遍历,需要时用Object.getOwnPropertySymbols(obj)获取(或者Reflect.ownKeys(obj))
- a = Symbol.for('ss');b = Symbol.for('ss');a===b为true;Symbol.keyFor(a)==='ss'//会将创建的结果保存到全局,从而使其相等,
30.querySelector与getElementBy系列的区别?
答:
- 参数不同,前者可以写各种选择器,后者要根据方法名不同
- 结果不同,前者结果为静态的,后者为动态的
- 兼容性不同,后者出来时间较晚
31.map与object的区别?(set存储的不是键值对)
答:
- map的key可以为任意数据类型,object则只能为string
- map的遍历有顺序、size,object则没有
32.promise原理:
答:
- 定义:promise是一种用于解决异步问题的方案
- 原理:
- promise对象内部维护一个状态管理器,对应有pending,fulfilled和rejected三种状态。
- 初始化对象时,状态为pending,当状态由pending转为fullfilled(调用resolve)或rejected(调用reject)时,会触发对应的then或catch回调,且这种状态改变一旦发生就不能再次发生。
- then的回调函数每次触发都会返回一个新的promise对象,从而形成链式调用。
- 类比eventbus:
- then相当于eventbus的listen,区别在于回调会返回一个心得promise从而形成调用链
- resolve/reject相当于eventbus的emit,区别在于,emit只会发生一次
- 没有监听取消
33.es5与es6的继承机制有什么不同?
答:
- es5实例的创建是先创建子类的this再this上添加父类的方法
- es6实例的创建则是先创建父类的this再对其进行加工(为什么要先调super才可使用this的原因)
34.谈一谈super?
答:
- 作为方法调用:只能且必须在子类的构造函数调用
- 作为对象调用:
- 必须调用其属性或方法,不能调用其本身
- 普通方法中指代父类的原型,静态方法中则指代父类本身
35. 你发过npm包吗?
答:做过demo,需要注册,登录,设定好package.json版本号之类的,然后使用publish命令。
36. 怎样快速为模块生命类型文件?
答:在typing文件夹下,创建对应的模块文件夹,然后在其下创建index.d.ts文件,使用declare导出,
37. 全局包变量类型的声明?
答:在根据全局变量的类型,在xxx.d.ts文件中声明,然后再tsconfig文件中引入即可
四、HTTP
1.post、put、patch区别?
答:
- post增改数据,url一般指向资源合集,不带某条数据标识
- put改数据,url上有某条数据标识,用于修改整体
- put改数据,url上有某条数据标识,用于修改部分属性
2.http请求报文是什么样的?响应呢?
- 请求报文
- 请求行:方法名 + url + 协议版本
- 请求头:各种键值对
- 空行
- 请求体:发送的数据
- 响应报文
- 响应行:协议版本 + 状态码 + 状态码原因短句
- 响应头:各种键值对
- 空行
- 响应体
3.http首部字段分类
- 通用首部:Cache-Control、Date等
- 请求首部:Accept、Range、User-Agent等
- 响应首部:ETag、Access-Control-Allow-Origin等
- 实体首部:Content-Type、Last-Modified等
4.常用状态码
- 200:响应成功
- 301:永久重定向
- 302:临时重定向
- 304:使用缓存
- 400:请求报文出错
- 401:权限认证出错
- 403:服务器拒绝访问
- 404:未找到
- 500:服务器出错
- 503:服务器不可用
5.https实现原理?
答:数据传输具有窃听风险、篡改风险和冒充风险,https是在http与tcp之间添加了一个tls/ssl协议,采用对称加密、非对称加密和CA验证的方式规避这些风险。
- 服务器:生成一对秘钥,私钥留着对客户端信息进行解密,公钥和个人信息提交到CA,进行证书申请。
- CA机构:
- 服务器端:对服务器提供的信息进行摘要,再用自己的私钥对摘要进行加密形成一个签名,再将签名和其他信息(服务器、CA、证书等的信息)形成签名证书,颁发给服务器。
- 客户端:将CA的公钥内置于浏览器
- 客户端:
- 第一次向服务器端请求,并得到证书(服务器、CA、证书等的信息都获取到了)
- 通过内置公钥对签名进行解密、比对验证(解密成功,则此服务器为合法服务器),
- 验证成功之后会随机产生一个用于数据交互的对称秘钥,并使用服务器的公钥进行加密
- 第二次请求会将加密后的秘钥发送给服务器端,之后二者之间的通信数据通过此协商秘钥进行加解密,摘要则通过服务器秘钥进行加密。
- 先用服务器公钥对摘要进行解密生成摘要1,再用协商秘钥对内容解密,将解密之后内容进行摘要形成摘要2,然后对比摘要1与摘要2(保证未被篡改)。
6.http2.0的优点?
- http2的解析是基于二进制的,之前是基于文本的
- 多路复用:
- http2中同域名下所有通信在一个链接上完成,且多个流可并行,多个流内的帧也可以穿插进行
- http1.x同域名连接数有限制(6-8个),且一个链接只可执行一个流
- header压缩,两端同时存header表,修改时才传
- http2有服务器推送功能
流:个完整的请求-响应数据交互过程
帧:最小的数据单位,对数据进行了标识
7.谈谈浏览器缓存?
答:
- 分类:浏览器缓存分为强制缓存和协商缓存
- 强制缓存:由响应头expire和cache-contral:max-age来标识,expire是绝对时间,会受浏览器本地时间影响,max-age是相对时间,缓存一旦被命中,就直接返回,不再请求服务器
- 协商缓存:由响应头last-modidfied、cache-control:no-cache、ETag标识,命中缓存后,需使用请求头if-last-modified、if-none-match等进行新鲜度查询,若是缓存新鲜服务器则返回304,浏览器直接使用缓存,否则,服务器返回200和最新数据
- 过程:
- 浏览区发起请求首先查看是否有缓存,没有则直接发起请求
- 有,则查看是否命中强制缓存,有则直接返回
- 没有则查看是否命中协商缓存,没有直接发起请求
- 有则与服务器通信验证新鲜度,新鲜则浏览器缓存直接返回,否则服务器返回数据
8.谈一谈tcp的三次握手四次挥手?
答:
- 三次握手:
- 发生时机:在建立tcp链接时发生
- 过程:
- 客户端向服务器发起建立链接请求
- 服务器向客户端发送可以建立连接确认
- 客户端向服务器发送链接确认已收到,之后又建立链接
- 问题:
- 为什么不可以是两次或是四次?答:第一次服务区确定客户端发送消息和自己接收消息没问题,第二次客户端确认自己接发和服务器接发没问题,第三次服务器确认自己的接发客户端的接发没问题,所以最少为三次,因为三次就可以验证双方的接发能力了,就没必要多次了
- 四次挥手:
- 发生时机:关闭tcp连接时发生
- 过程:
- 客户端向服务器发送关闭链接请求
- 服务器向客户端发送请求收到确认
- 服务器向客户端发送关闭链接确认
- 客户端向服务器发送关闭链接确认已收到,并等待一段时间
- 问题:
- 为什么需要第四次?答:若是第三次发生丢包,客户端就会一直持续等待状态,必须要保证第三次的成功才行。
- 为什么第四次要等待一段时间?答:因为若是第四次丢包,服务器会重发消息,若是不等待,浏览器无法重发通知服务器,服务器一直处于重发状态
9.手动实现ajax?
答:
function ajax(method,url,async,cb,data){
let http;
if(window.XMLHttpRequest){
http=new XMLHttpRequest();
}else{
http=ActiveXObject('Microsoft.XMLHttp')
}
http.onreadystatechange = function(){
if(http.readyState===4){
cb();
}
}
http.open(method,url,async);
http.send(data);
}
复制代码
Ps:readyState状态分别为:
- 0:未调用open之前
- 1:open与send之间
- 2:send与服务器开始响应之前
- 3:服务器正在响应
- 4:响应完毕
10.XMLHttpRequest与fetch的区别?
答:
- fetch返回值为promise(但400,500不会reject只有网络问题才会),XMLHttpRequest则为回调形式
- fetch不会自动将cookie带上
- fetch不可取消,XMLHttpRequest则可以
- fectch兼容性差一些
- fectch没有进度事件监听
- fectch不支持超时
Ps:axios是基于XMLHttpRequest的封装
11.DNS解析过程?
- 查看缓存:浏览器缓存->计算机系统缓存->路由缓存->DNS缓存
- 寻址:根域名服务器->顶级域名服务器->主域名服务器->依次向下...
- 查到后进行各级缓存
五、算法
1.说说你知道的排序算法?
答:
- 冒泡排序
- 原理:比较相邻两个数,若是前一个较大,则调换位置
- 空间复杂度:n²
- 实现:
function sortByBubble(arr){ for(let i=0;i<arr.length-1;i++){ for(let j=0;j<arr.length-1-i;j++){ if(arr[j]>arr[j+1]){ [a[j],a[j+1]]=[a[j+1],a[j]] } } } } 复制代码
- 选择排序
- 原理:先找到最小值,再放入最左侧,依次进行
- 空间复杂度:n²
- 实现
function sortBySelect(arr){ const newArr = []; while(arr.length>0){ let min = arr.shift(); for(let i=0;i<arr.length;i++){ console.log('--',min,arr[i]); if(min>arr[i]){ [min,arr[i]] = [arr[i],min]; } } newArr.push(min); } return newArr; } 复制代码
- 插入排序
- 原理:依次插入到已排数组的比其小的第一个后边,否则插入到第一个
- 空间复杂度:n²
- 实现
function sortByInsert(arr){ const newArr=[arr.shift()]; while(arr.length>0){ const insert = arr.shift(); for(let i=newArr.length-1;i>=0;i--){ if(newArr[i]<insert){ newArr.splice(i+1,0,insert); break; } if(i===0){ newArr.unshift(insert); } } } return newArr; } 复制代码
- 快速排序
- 原理:随便取一个值,对剩下值进行左右归类,再重复对归类后的数组采取同样操作,直至左右数组分别为0和1个,再拼接随机值即可
- 空间复杂度:nlogn
- 实现
function sortByQuick(arr){ const one=arr.shift(); let left=[]; let right=[]; for(let i=0;i<arr.length;i++){ arr[i]<one?left.push(arr[i]):right.push(arr[i]); } if(left.length>1){ left = sortByQuick(left); } if(right.length>1){ right = sortByQuick(right); } return [...left,one,...right]; } 复制代码
- 计数排序
- 原理:找到最小值,每个数减去这个值,以每个值为下标,值出现次数为值建立一个新数组,再根据下标与值还原数组
- 空间复杂度:n+k
- 实现
function sortByCount(arr){ let min = arr[0]; const newArr=[]; const finalArr=[]; for(let i=1;i<arr.length;i++){ if(min>arr[i]){ min=arr[i] } } for(let i=0;i<arr.length;i++){ newArr[arr[i]-min]=newArr[arr[i]-min]?newArr[arr[i]-min]+1:1 } debugger for(let i=0;i<newArr.length;i++){ for(let j=0;j<newArr[i];j++){ finalArr.push(i+min) } } return finalArr;Ï } 复制代码
- 限制性:用于差值范围小数量多的排序,如高考成绩、年龄统计等
2.dom树(多叉树)的遍历策略?
答:
- 广度优先策略:
- 实现:
function walk(node,callback){ let queue = [];//队列 while(node!==null){ callback(node); if(node.children && node.children.length>0){ for(let i=0;i<node.children.length;i++){ queue.push(node.children[i]); } } node=queue.shift(); } } 复制代码
- 实现:
- 深度优先策略:
- 实现:
//前序 function walk(node,callback){ let queue = [];//队列 while(node!==null){ callback(node); if(node.children && node.children.length>0){ for(let i=node.children.length-1;i>=0;i--){ queue.push(node.children[i]); } } node=queue.pop(); } } 复制代码
- 实现:
六、Vue
1.watch与computed的区别?
答:
- computed:具有缓存性、多依赖输出单个值、不支持异步
- watch:无缓存性、单依赖输出多个操作、支持异步
2.Proxy与Object.defineProperty的区别?
答:
- Proxy有13中拦截方法,而Object.defineProperty很少
- Proxy监听对象,Object.defineProperty则是属性,定义属性监听时更方便
- Proxy可以监听数组,Object.defineProperty不可以
- Proxy为ES6中的,兼容性略差
3.谈谈你对vue响应式的理解?
答:主要就是通过数据劫持的方式,在getter中对渲染watcher进行依赖收集,在setter时触发收集的依赖进行更新,类似于eventbus模式
4.既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行diff检测差异?
答:数据劫持是服务于model到view的变化,而dom的diff则服务于view怎样进行变化,完全没有重复
5.vue-router原理?
答:
- hash模式:主要采用location.hash赋值,hashchange监听hash变换实现重新渲染
- history模式:主要是pushstate修改url(但是并不会触发对应页面的加载)和实现eventbus对于pushstate进行监听(pushstate/replacestate不会直接触发popstate,需要history.back()之类的事件才行,这又会触发页面下载)
6.keep-alive原理?
答:将组建实例缓存下来,需要时直接挂载
7.vuex原理?
答:store存储数据,并进行响应式派发更新view
8.谈一谈template的编译?
答:
- 将字符转转为一个描述对象树ast:主要有三类节点,元素节点,表达式节点,注释节点
- 优化:遍历ast,对是否为静态节点进行标识
- 将ast转为代码字符串:主要根据ast调用createElement系列的方法
9. 谈一谈vue3.0
答:
- 性能更好
- 动态节点标识,非动态节点无需比较
- 非动态节点缓存
- 响应函数缓存
- Composition API -> setup
- Tree shaking -> 按需导入
- TS支持
- 开放底层渲染API
- Fragment等组件
七、React
1.redux工作原理?
答:主要有三个部分
- store:状态树,这个对象不可以直接修改,想要修改必须派发action,挂载有dispatch方法,同时这个store可被订阅(订阅的是action的派发而不是状态的改变)
- action:一个可序列化对象,必须包含标识符,对action的派发,就是执行reducer函数。
- reducer:一个纯函数,定义怎样对状态树进行修改,入参为前一个状态树和action,定义了根据action.type的不同,对状态树的不同操作,返回值为后一个状态树,
2.redux中间件原理?
答:
- 原理就是延迟执行dispatch函数,先做中间件做的事,再执行真正的dispatch。
- redux-thunk:将dispatch改造为,入参为函数(一个函数的执行返回结果,形成闭包,留住参数),执行完函数之后(可能为异步),再调用原来的dispatch方法(这个dispatch的参数为action,必须包含type等信息)
3.谈一谈react-redux?
答:
- 提供provider,是store可在全局共享
- 提供connect,将state和action挂到props上,同时自动进行订阅
4.componentWillReceiveProps调用时机?(被弃用)
答:在当前组件被重新渲染的时候会被调用(此时应用当前组件的组件发生setState,可能传入的prop并没有改变),第一次渲染不会调用
5.react中做过哪些性能优化?
答
- 使用pureComponent,此组件会自带shouldComponentUpdate方法,对于props未改变时,不会重新渲染
- immutable库+shouldComponentUpdate
6.说一说dom diff?
答:三种优化策略:
- 只进行平级比较
- 平级相同类型组件才比较(允许通过制定shouldcomponentupdate指定),不同类型直接替换
- 平级多个子节点,通过比较进行复用(通过key)
7. React Native中使用webview怎样进行通信?
答:
- rn -> webview
- 传:
- url
- this.refs.webview.postMessage()
- 接收:
- 解析url
- window.document.addEventListener('message',function(){})
- 传:
- webview -> rn
- 传:window.ReactNativeWebview.postMessage()
- 接收:onMessage
8. 谈一谈JSBridge?
答:基于webview实现web与native进行通信的一套机制,
- 原理:
- web -> native:
- native可以拦截webview中的请求,通过商定协议,可以实现web向native的消息传递
- native可以通过webview向web传入对象,在js中可以直接调用
- native -> web:native中的webview组件可以直接调用webview中挂载在window上的方法,这样实现native向web的消息传递
- web -> native:
- 设计:
- postMessage:封装location.href
- onMessage:对事件的调用实现一个事件监听即可
- js端:
- 调用native:postMessage(type,data,cb)
- 被native调用:onMessage:eventbus
- 调试:
- native的webview开启调试模式
- usb连接电脑
- 手机设置开发者选项
- chrome://inspect设置discover
9.redux与mobx区别
答:
- redux结合react-redux与mobx结合react-mobx基本功能相同,中小项目使用mobx
- mobx写法更简洁,没有redux的几个概念
- mobx状态唯一,不可回溯,redux可回溯
- redux异步需要中间件,不可直接修改state
10.setState是同步的还是异步的?
答:setState本质上是同步的,之所以有异步的表现,是因为合成事件或是钩子函数中的回调的调用时机被延迟了,会对多次状态进行合并,只执行一次。
11.react最新的生命周期有哪些?
答:删除componentWillMount,componentWillReceiveProps,componentWillUpdate
- 挂载时:
- constructor
- getDerivedStateFromProps
- render
- componentDidMount
- 更新时:
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidUpdate
- 卸载时:
- componentWillUnmount
为什么要使用虚拟dom?
答: 1)结构更简单;2)减少对真实dom的操作,避免重排重绘,提高性能
八、Angular
1.angular怎样进行数据监测的?
答:
- angular1时:angular在scope上维护了一个监听队列,当变量被绑定到view时进行插入,通过合适的时机对队列进行遍历就是脏检查
- angular2+时:数据的变化都是通过异步的宏任务和微任务触发的,基于zone.js库对浏览器的异步api进行了重写,即对异步进行了了劫持,通知试图更新
2.说一说双向绑定?
答:view到model是事件,model到veiw依靠数据监测
3.说一说依赖注入?
答:让类从外部源中获得它的依赖,而不必亲自创建它们
九、Webpack
1.说一说常用的loader和plugin?
答:从构建的流程说起:
- 处理html:
- HtmlWebpackPlugin:用于插入打包后的js
- CleanWebpackPlugin:用于删除原来的构建包
- 处理css文件:
- 添加浏览器前缀:使用postcss-loader的autoprefixer插件
- 解析@import语法:css-loader
- 将css解析为模块:style-loader
- 抽离css:MiniCssExtraPlugin
- 压缩css:OptimizeCssAssetsWebpackPlugin
- 处理js文件:
- babel-loader、@babel/core、@babel/preset-env为主,还可以配其他的preset、plugin等,很多,一般都是有问题的时候去查
- 处理静态资源的:
- file-loader:类似CopyWebpackPlugin,只能进行复制操作
- url-loader:可以将图片进行转base64等
- 处理scss/ts/vue/react等对应的loader
- 优化相关的
- 压缩css:MinimizeCssAssetsPlugin
- 热更新:HotModuleReplacementPlugin
- 压缩混淆js:TerserWebpackPlugin
2. loader和plugin的区别?
答:
- loader就是一个函数,入参为文件内容(字符串或二进制形式),将文件转换为js可以识别的模块
- plugin则是一个类,需要new调用,其内部其实就是对wepack生命周期钩子的订阅,在钩子广播时对代码进行转换。
3.热更新原理?
答:基于内置的HotModuleReplacementPlugin,就是对本地文件的变化进行监测,一旦发生变化,通过websocket将变化信息推送到客户端,客户端会通过比较,确定是否加载资源以及怎样进行更新
4.hash、chunkhash、contenthash的区别?
答:
- hash为整个项目构建的hash,当项目文件改变时会发生变化,
- chunkhash则和入口文件有关,当一个入口文件的打包有变化时不会影响其他的打包,- contenthash则是根据文件内容进行的hash,文件内容不变hash不会变
5.多页面应用的配置?
答:
- 设置多个入口文件
- 出口文件名采用占位符形式
- 创建多个HtmlWebpackPlugin,指定chunk值
6.怎样抽取公共代码?
答:多页应用抽取公共代码才有意义,设置optimization字段的splitChunks属性。主要分为第三方模块和自己的模块
7.懒加载原理?
答:主要依赖于import语法,其本质是jsonp,会返回promise,外加@babel/plugin-dynamic-syntax-import
8.优化打包速度?
答:
- 开启多线程打包:happy-webpack等
- 设定打包范围:主要是include/exclude等
- 指定不解析的依赖项:通过noParse、ingore-plugin等
- 开启缓存等
- 将几乎不变的代码转为静态资源,主要通过dllplugin
9.怎样定义环境变量?
答:通过DefineWebpackPlugin
10.webpack可以解决跨域问题吗?
答:可以,配置代理
11.怎样设置loader的加载顺序?
答:enforce字段,有pre、mormal、post等字段
12.babel编译原理?
答:主要分为三个步骤
- 编译:将字符串转为为ast,主要利用babylon
- 改造:对ast遍历改造,主要利用@babel/traverse、@babel/types
- 反编译:将ast转为字符串,主要利用@babel/generator
13.ast是怎样生成的?
答:
- 分词:将字符串分割为语法单元数组(即将字符串分割为若干个具有独立意义的最小串)
- 进行语义分析,将语法单元组装为ast(根据各个串的特性,进行组装)
Ps:
语法单元:指标识符、运算符、括号、数字和字符串等能够被解析的最小单元 语义分析:对语句和表达式的识别
十、杂项
1.浏览器运行机制?
- 浏览器是多进程的:
- browser主进程,负责协调调度,只有一个
- 第三方插件进程,插件被使用时创建,每种插件有一个
- GPU进程,图像显示相关,最多一个
- renderer进程(浏览器内核),用于页面渲染、脚本执行和事件处理等,默认每开一个tab页为一个进程(也有多个tab页进程合并的情况)
- renderer进程是多线程的
- GUI渲染进程:负责构建dom树render树,布局、绘制、重排、重绘等,与JS引擎线程互斥,当js引擎线程执行时,被挂起,GUI更新会被保存在一个队列中,等js引擎线程空闲时间立即执行,
- JS引擎线程:负责js的解析运行等,与GUI渲染线程互斥,js如果长时间执行会阻塞页面
- 事件触发线程:负责事件触发后将事件添加入队列
- 定时器触发线程:负责定时到期后将事件添加入队列
- 异步http请求线程:用于http请求,当状态改变后,由事件触发线程与js引擎通信
ps:
- 进程:cpu分配资源分配的最小单位,进程间相互独立。(也可以通信,只是代价比较大)
- 线程:cup调度的基本单位,一个进程由多个线程组成,同一进程下多线程共享资源。
- 并发:单核cpu只能同时运行一个进程,但可以切换的非常快,看起来像是同时运行,就是并发
- 并行:多核cpu可以实现真正的同时运行,多cpu也可以
- webworker原理:是js引擎线程的子线程,通过postMessage通信,不可操作dom
- shareworker则是一个单独的进程,所有页面共享的
2.在浏览器中输入URL到整个页面显示的过程?
答:
- 将域名解析为ip,先进行本地查询,再从根域名依次查询,寻到后,会依次进行DNS缓存。(每次的ip可能不一样,有时也可能会有ip到域名再到ip的过程,dns负载均衡做的)
- 建立tcp链接,三次握手四次挥手,再http1.x中一个域名下只允许同时建立6-8个,http2.0则没有这个限制
- http下载资源,请求响应的过程,包括html、css、js图片等(若是https则还有ssl层握手的过程)
- 对资源进行解析渲染页面,先解析html和css分别生成dom树和css规则树,合并二者生成render树,进行布局(layout),再进行绘制(paint),最后将各层信息发送给GPU,GPU进行各层合成(compisiton),显示,这中间会有js执行,事件循环机制托管,发生dom重新生成、重排、重绘、合成等
3.前端性能优化
答:
- 根据第一次页面过程中进行优化
- DNS加速:限制domain的数量,DNS的prefetch等
- tcp优化:减少重定向
- http层:使用http2
- 资源加载:
- 缓存:CDN缓存、强缓存、弱缓存
- 压缩:图片的压缩、代码的压缩,treeshaking
- 代码的分割,首屏资源先加载,
- 懒加载:可视资源优先加载
- 路由,页面间跳转:
- 模块提前加载(支付包tab页)
- loading、骨架占位
- keep-alive模式
- 交互:
- 防抖节流等
- 避免修改dom结构,重排重绘等
- 虚拟列表,长列表只绘制显示区域
- webworker
- loading
- 动画方面:
- 使用requestAnimationFrame(页面不激活自动暂停性能好,回调跟浏览器刷新频率一致流畅)
- 其他:
- 每个过程,资源未准备好时,友好loading提示,骨架屏等
- 寻找合适的测试性能工具
4.安全问题
- XSS:Cross Site - Scripting
- 定义:跨站脚本攻击:攻击者将代码植入到页面中
- 方式:
- 存储型攻击(评论中带有脚本,有form表单提交的地方)
- 反射型攻击(跳转时执行脚本,url上有脚本)
- 应对措施
- 对提交内容做过滤、转义
- url参数进行编码
- cookie开启HTTPOnly
- CSRF:Cross Site Request Forgery
- 定义:利用用户登录状态发起恶意请求
- 方式:(主要是利用第三方网站发起请求)
- get型:模拟get请求(超链接)
- post型:模拟post请求,提交表单
- 防范:
- 对请求源做甄别,请求头的referer字段
- token代替cookie
- sql注入:
- 定义:通过表单提交、输入url等形式执行恶意sql(提交数据为查询条件,查询条件中伪装sql)
- 方式:
- 登录注入:登录时不知道密码,密码为查询条件,修改密码为合法查询条件即可
- 低权限查询高权限数据
- 防范:过滤,转义
- 文件上传漏洞:
- 定义:通过window特殊字符命名不通过,将上穿文件变成可执行文件
- 防范:
- 上传存放地设置权限为不可执行
- 对文件重命名
ps:
- 同源策略:不同域的客户端脚本在没有明确授权的情况下,不能读写其他域的资源
- 同域:指协议、域名、端口(默认为80)都相同
- 授权:由响应指定,access-allow-control-origin
5.说一说你知道的设计模式?
答:
- 单例模式:一个类只允许创建一个实例,常用于全局唯一的情景,比如vue-router、vuex
- 工厂模式:返回一类对象
- 发布订阅模式
- 观察者模式
- 原型模式
- 迭代器模式
- 装饰器模式
- 代理模式
6.观察者模式与发布订阅模式的区别。
答:
- 观察者模式:被观察着直接与观察者进行交互,事件回调维护在被观察着内部。
- 发布订阅模式:发布者与订阅者中间隔了一个事件调度中心,订阅者的事件回调维护在事件调度中心里,事件调度中心维持某种机制进行派发工作。
7.谈一谈ssr?
答:
- 定义:ssr:服务器端渲染,页面的呈现切换采用请求后端资源的模式,而不是客户端浏览器通过js进行。
- 优点:
- 有利于seo
- 页面呈现更快
- 缺点:
- 服务器负载严重
- 需要node进行后台开发
- 开发条件受限(生命周期钩子,第三方库)
8.骨架屏原理?
答:
- 首页使用骨架屏:手写和自动生成两种
- 路由页面使用骨架屏:利用服务端渲染
- 使用loading图片
9.谈一谈微服务?
答:将一个大的项目分割为若干个小项目,每个小项目都可以独立开发、测试和部署
- iframe主项目作业面切换,其他业务为各自页面
- 一个多页面应用主项目,多个单页面应用(技术栈可以不统一)
- 一个单页面应用主项目,多个路由包(技术栈要求统一)
10.谈一谈模块化?
答:
- 概念:将复杂的程序分割为多个文件,并可以组装起来使用的策略。内部私有变量,外部暴露api。
- 优点:降低复杂度、解耦、复用性高
- 历史:全局function -> namespace -> 立即执行函数 -> common/es6等
- 分类
- commonjs:
- 适用环境:
- 服务器:同步加载(此时是对原模块的浅拷贝)
- 浏览器(需使用browserify编译):需要预编译(为引用)
- 语法:
- 定义:module.exports=.../exports.xxx=...
- 引用:require(path)
- 适用环境:
- AMD:
- 适用环境:浏览器(需要requirejs作依赖)
- 语法:define/require+config
- ES6:
- 适用环境:浏览器(需要babel进行编译)(为引用)
- 语法:export/import
- commonjs:
- 说明:
- 当使用es6导出,commonjs导入时,会出现default的情况