问:如何理解
html
标签语义化?
html5
新出的标签,每一个标签都有本身语义,什么标签作什么事。让人看的懂,也让机器能够看的懂,利于SEO
。问:
css
权重是什么?
important
:无限高1000
id
选择器:权重值为100
10
1
问:盒模型有几种,它们区别是什么?
使用box-sizing
属性设置:border-box
:怪异盒模型、content-box
:标准盒模型。css
问:什么是
BFC
?
BFC
能够清除浮动、让margin
不重叠。问:如何触发
BFC
?
float
的值不为none
。overflow
的值不为visible
。display
的值为table-cell
、table-caption
和inline-block
之一。position
的值不为static
或releative
中的任何一个。问:你经常使用的清除浮动方式是什么?
.clear:after, .clear:before { content: ' '; display: table; } .clear:after { clear: both; }
问:em、rem的区别?
em
:若是父级有设置字体大小,1em就是父级的大小,没有1em等于自身默认的字体大小。rem
:相对于html
标签的字体大小。问:不使用
border
属性画一条1px
的线?
<div style='height: 1px; background: #666; overflow: hidden;'></div> <hr size='1'></hr>
问:移动端
1px
问题?
box-shadow: 0 -1px 1px -1px #e5e5e5, //上边线 1px 0 1px -1px #e5e5e5, //右边线 0 1px 1px -1px #e5e5e5, //下边线 -1px 0 1px -1px #e5e5e5; //左边线 0 0 0 1px #e5e5e5; //四条线
问:定位的方式有哪几种,它们的区别是什么?
relative:
相较于自身定位,设置的位置相对于本身进行位移。不脱离文档流。absolute:
相较于最近有定位的父节点定位,设置的位置相较于父节点。会脱离文档流,致使父节点高度塌陷。fixed:
相较于当前窗口进行定位,设置的位置相较于窗口。脱离文档流。问:垂直水平居中的实现方式有哪些?
text-align: center
和line-height
等同高度。position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);
position: absolute;top:0;left:0;right:0;bottom:0;margin:auto;
display: table
,子节点设置display:table-cell;text-align:center;vertical-align:middle;
display: flex;justify-content:center;align-items:center;
display: grid;
,子节点设置:align-self:center;justify-self: center;
问:你知道的左右宽度固定,中间自适应的三栏布局方案有哪些?
浮动: .parent {overflow: hidden;} .left {float: left; width: 100px;} .right: {float: right; width: 100px;} <div class='parent'> <div class='left'></div> <div class='right'></div> <div class='center'></div> </div> 定位1: .parent {postion: relative}; .left {position: absolute; left: 0; width: 100px}; .right {position: absolute; right: 0; width: 100px}; .center {postion: absolute; left: 100px; right: 100px}; 定位2: .parent {postion: relative}; .left {position: absolute; left: 0; width: 100px}; .right {position: absolute; right: 0; top: 0; width: 100px}; .center {margin: 0 100px 0 100px}; 表格: .parent {dispaly: table; width: 100%;} .left {display: table-cell; width: 100px;} .center {display: table-cell;} .right {display: table-cell; width: 100px;} 弹性: .parent {display: flex;} .left {width: 100px;} .center {flex: 1;} .right {width: 100px;} 网格: .parent { display: grid; width: 100%; grid-template-rows: 100px; grid-template-columns: 100px auto 100px; }
问:实现三个圆形的水平自适应布局?
难点在于高度的自适应 .parent { display: table; width: 100%; } .child { display: table-cell; padding-top: 33.33%; background: red; border-radius: 50%; } .parent { overflow: hidden; } .child { float: left; width: 33.33%; padding-top: 33.33%; border-radius: 50%; background: red; } .parent { display: flex; } .child { flex: 1; padding-top: 33.33%; border-radius: 50%; background: red; }
问:介绍下
flex
布局?
主轴方向:水平排列(默认) | 水平反向排列 | 垂直排列 | 垂直反向排列 flex-direction: row | row-reverse | column | column-reverse; 换行:不换行(默认) | 换行 | 反向换行(第一行在最后面) flex-wrap: nowrap | wrap | wrap-reverse; flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap flex-flow: <flex-direction> || <flex-wrap>; 主轴对齐方式:起点对齐(默认) | 终点对齐 | 居中对齐 | 两端对齐 | 分散对齐 justify-content: flex-start | flex-end | center | space-between | space-around; 交叉轴对齐方式:拉伸对齐(默认) | 起点对齐 | 终点对齐 | 居中对齐 | 第一行文字的基线对齐 align-items: stretch | flex-start | flex-end | center | baseline; 多根轴线对齐方式:拉伸对齐(默认) | 起点对齐 | 终点对齐 | 居中对齐 | 两端对齐 | 分散对齐 align-content: stretch | flex-start | flex-end | center | space-between | space-around;
问:
JavaScript
的变量有哪些类型?
boolean
、null
、undefined
、number
、string
、symbol
。Array
、Object
、Function
。问:基础类型和引用的区别?
问:函数参数是对象时会发生什么问题?
问:
typeof
和instanceof
判断变量类型的区别?
typeof
对于基础类型除了null
之外均可以显示正确的类型,对于数组和对象都会显示object
,对于函数会显示function
。instanceof
主要是用来判断引用类型,它的原理是根据原型链来查找。问:有没有更好的判断变量类型的方法?
Object.prototype.toString.call(var)
,能够更加准确的判断某个变量的类型。问:类数组转为数组的方式有哪些?
[].slice.call(arguments) Array.from(arguments) [...arguments]
问:如何判断一个变量是不是数组?
arr instanceof Array Array.prototype.isPrototypeOf(arr) Array.isArray(arr) Object.prototype.toString.call(arr) === '[object Array]' arr.constructor === Array
问:将多维数组扁平化?
function flatten(arr) { return [].concat(...arr.map(v => { return Array.isArray(v) ? flatten(v) : v; })) } function flatten(arr) { return arr.reduce((pre, cur) => { return pre.concat(Array.isArray(cur) ? flatten(cur) : cur); }, []) } function flatten(arr) { return arr.flat(Infinity); } function flatten(arr) { // 纯数字 return arr.toString().split(',').map(Number); } function flatten(arr) { const ret = []; while (arr.length) { const item = arr.shift(); if (Array.isArray(item)) { arr.unshift(...item); } else { ret.push(item); } } return ret; }
问:数组去重?
function unique(arr) { return [...new Set(arr)]; } function unique(arr) { return arr.filter((v, i, a) => { return a.indexOf(v) === i; }) } function unique(arr) { const tmp = new Map(); return arr.filter(v => { return !tmp.has(v) && tmp.set(v); }) }
问:字符串的
test
、match
、search
它们之间的区别?
`test`是检测字符串是否匹配某个正则,返回布尔值; /[a-z]/.test(1); // false `match`是返回检测字符匹配正则的数组结果集合,没有返回`null`; '1AbC2d'.match(/[a-z]/ig); // ['A', 'b', 'C', 'd'] `search`是返回正则匹配到的下标,没有返回`-1`。 '1AbC2d'.search(/[a-z]/); // 2
问:字符串的
slice
、substring
、substr
它们之间的区别?
`slice`是返回字符串开始至结束下标减去开始下标个数的新字符串,下标是负数为倒数; 'abcdefg'.slice(2,3); // c // 3 - 2 'abcdefg'.slice(3,2); // '' // 2 - 3 'abcdefg'.slice(-2,-1); // f // -1 - -2 `substring`和`slice`正常截取字符串时相同,负数为0,且下标值小的为开始下标; 'abcdefg'.substring(2,3); //c // 3 - 2 'abcdefg'.substring(3,2); // c // 3 - 2 'abcdefg'.substring(3,-3); // abc // 3 - 0 `substr`返回开始下标开始加第二个参数(不能为负数)个数的新字符串。 'abcdefg'.substr(2, 3); // cde 'abcdefg'.substr(3, 2); // de 'abcdefg'.substr(-3, 2); // ef
问:
==
和===
的区别?
===
会判断两边变量的类型和值是否所有相等,==
会存在变量类型转换的问题,因此并不推荐使用,只用一种状况会被使用,var == null
是var === undefined || var === null
的简写,其他状况一概使用===
。问:是否
===
就彻底靠谱?
0 === -0
就为true
,NaN === NaN
为false
,判断两个变量是否彻底相等可使用ES6
新增的API
,Object.is(0, -0)
,Object.is(NaN, NaN)
就能够准确区分。问:在类型转换中哪些值会被转为
true
?
undefined
、null
、false
、NaN
、''
、0
、-0
之外的值都会被转为true
,包括全部引用类型,即便是空的。问:谈谈对
this
的理解?
this
表示为当前的函数调用方,在运行时才能决定。如谁调用了某个方法,谁就是这个方法执行时的this
。问:改变当前调用
this
的方式?
call
:会当即执行调用call
方法的函数,不过是以第一个参数为this
的状况下调用,方法内能够传递不等的参数,做为调用call
方法的参数。apply
:运行方式和call
是一致的,只是接受的参数不一样,不能是不定参数,得是一个数组。bind
:会改变当前的this
,接受不定参数,不过不会立刻执行调用bind
方法的函数,而是返回一个函数做为结果,执行后才是调用函数的结果。问:谈谈对闭包的理解?
return
关键字。问:谈谈对原型以及原型链的理解?
JavaScript
引用类型(数组/对象/函数)都有一个__proto__
属性,这个属性是一个对象格式,也就是原型属性。在原型属性里面有一个constructor
属性,这个属性是这个引用类型的构造函数,在constructor
里面又有一个prototype
的属性,这个属性又指回了引用类型的原型属性。__proto__
属性层层链接起来造成的,而构造函数的prototype
是一个对象属性,再构造函数实例化时就会将这个属性赋值给实例化后对象的__proto__
属性,因此函数的继承也会相应的构造出对象的原型链。问:原型继承的方式有哪些?
function Parent(name) { this.name = name; } function Child(name, age) { Parent.call(this, name); this.age = age; } Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child
问:如何解决引用类型变量共享的问题?
JSON.parse(JSON.stringify(obj))
,不过也会存在诸多问题,更加完善的深拷贝须要手写递归方法对不一样参数分别处理,参考深拷贝的终极探索(90%的人都不知道)。问:函数防抖和节流的区别?
wow
为例:2.5s
施法的寒冰箭,再读条的过程当中,你身子抖动打断了施法,再次触发技能时麻烦您从新读条。cd
为8s
,因此即便8s
内按了10
次,也只能来1
发,节省点体力吧。问:请手写一下map、call、new、instanceof、Events、深拷贝、节流、Promise等?
问:
var
、let
、const
的区别 ?
var
类型会有变量提高的状况,也就是说声明会首先提高到当前做用域的顶端,在使用到时再读取定义的值,并且在全局做用域下定义的变量会挂载到window
下,而let
和const
并不会。let
和const
没有变量提高的状况,必需要先声明再使用,不然就会出现暂时性死区的状况。const
和let
的区别在于一经定义后不得再次改变const
定义的值,若是是引用类型只要不改变指针,改变里面的值是没问题的。const
定义时必须赋值,let
没必要。问:
Set
、WeakSet
的区别?
Set
类型内存储的是不会重复的值,建议存储基础类型的值,由于引用类型的指针都不一样。WeakSet
只能存储对象参数,不然会报错,并且是存储的引用类型的弱引用。WeakSet
不可被迭代,不支持forEach
、for-of
、keys
、values
方法,没有size
属性。const set = new Set(); const obj = {name: 'cc'}; set.add(obj); obj = null; [...set][0]; // {name: 'cc'} 转数组后依然能够访问到 const weakSet = new WeakSet(); const obj = {}; weakSet.add(obj); obj = null; // 会移除引用 weakSet.has(obj); // false
问:
Map
、WeakMap
的区别?
Map
是解决了对象key
会被自动转为字符串的一种加强key/value
集合。WeakMap
是弱引用的Map
集合,key
必须是非null
的对象格式,一样不能够被迭代。const obj = Object.create(null); obj[1] = 'cc'; obj['1']; // cc const map = new Map(); map.set(1, 'cc'); map.has('1'); // false 1 和 '1'不会被转换
问:箭头函数和普通函数的区别?
this
是由包裹它的普通函数的this
来决定;Generator
函数;arguments
访问,须要使用Es6
的不定参数访问;bind
方法无效。问:请实现plus(1)(2)(3)(4)等于8?
方法1: function plus(n) { debugger let sum = n; const _plus = function (n) { sum += n; return _plus; }; _plus.toString = function () { return sum; }; return _plus; } 方法2: function multi() { const args = [].slice.call(arguments); const fn = function () { const newArgs = args.concat([].slice.call(arguments)); return multi.apply(this, newArgs); } fn.toString = function () { return args.reduce(function (a, b) { return a + b; }) } return fn; }
问:谈谈对
class
的理解 ?
JavaScript
没有真正的类,一直也是经过函数加原型的形式来模拟,class
也不例外,只是语法糖,本质仍是函数。须要先声明再使用,内部的方法不会被遍历,且没有函数的prototype
属性。不过相较ES6
以前不管是定义仍是继承都好理解了不少。继承主要是使用extends
和super
关键字,本质相似于ES5
的寄生组合继承:class Parent { constructor(name) { this.name = name; } } class Child extends Parent { constructor(name, age) { super(name); // 至关于Parent.call(this, name) this.age = age; } }
问:谈谈对
Promise
的理解 ?
Promise
主要解决的问题就是异步回调嵌套过深形成代码难以维护和理解。Promise
构造函数内的代码是同步执行的,而以后then
或catch
方法是异步执行的,构造函数接受两个函数参数resolve
和reject
,它们执行时接受的参数分别会传递给then
和catch
表示成功的回调以及失败回调接受到的值。Promise
一共有三种状态pending
等待状态、resolved
已完成状态、rejected
已拒绝状态,状态的改变只能由等待转为已完成或等待转为已拒绝状态,并且状态的改变只会发生一次。then
方法且方法里必需要返回一个Promise
对象,若是是返回其余的类型会尝试包装成Promise
对象;then
能够被链式的调用。Promise
链中途没法取消;错误须要经过回调函数捕获。问:谈谈对
ES-Module
的理解 ?
ES-Module
是ES6
原生支持模块化方案,经过import
来引入模块,经过export default
或export
来导出模块。问:谈谈对
Proxy
的理解 ?
Object.defineProperty
有些相似,它的做用是用来自定义对象中操做。Proxy
的构造函数接受两个参数,第一个参数是须要代理的对象,第二个参数是一个对象,里面会定义get
和set
方法,当代理对象中的某个值被访问或从新赋值就会触发相应的get
和set
方法。vue3.0
就抛弃了Object.defineProperty
而拥抱了Proxy
,它的优势是只须要代理一次,对象内的值发生了改变就会被感知到,再也不须要像之前为对象的每一个值进行数据劫持;并且之前对象的新增,数组的下标设置0
清空等状况均可以被感知到,在响应式里也不在须要为数组和对象收集两次依赖,相信会大大提高性能。谈谈对
Generator
的理解?
JavaScript
方便建立迭代器的新语法,在方法名前面添加*
号,表示这个方法是一个生成器函数,在函数内部配合yield
关键字指定next()
方法返回值及顺序。yield
相似与在函数内部打上了断点,yield
就是每一处的debugger
,执行next()
方法后进入下一个断点。问:谈谈对
async
及await
的理解 ?
Genneator
的语法糖形式,解决的问题是以同步的形式写异步代码,让代码流程能很好的表示执行流程。在函数的前面加上async
代表是一个异步函数,函数的内部须要配合await
关键字使用,每个await
关键字至关因而yield
,会暂停函数的执行,直到异步函数执行完毕后内部会自动执行next()
方法,执行以后的代码,函数的返回结果是一个Promise
对象。由于是以同步的形式书写异步代码,因此错误捕获是使用try/catch
的形式。问:谈谈对
Event-Loop
的理解 ?
JavaScript
的执行机制简单来讲就先执行同步代码,而后执行异步代码,而异步的代码里面又分为宏任务代码和微任务代码,先执行微任务,而后执行宏任务。首先会将全部JavaScript
做为一个宏任务执行,遇到同步的代码就执行,而后开始分配任务,遇到宏任务就将它的回调分配到宏任务的队列里,遇到微任务的回调就分配到微任务的队列里,而后开始执行全部的微任务。执行微任务的过程仍是遵循先同步而后分配异步任务的顺序,微任务执行完毕以后,一次Event-Loop
的Tick
就算完成了。接着挨个去执行分配好的宏任务,在每一个宏任务里又先同步后分配异步任务,完成下一次Tick
,循环往复直到全部的任务所有执行完成。process.nextTick
,promise
,MutationObserver
。script
, setTimeout
,setInterval
,setImmediate
,I/O
,UI rendering
。问:对浏览器或元素的各类距离参数你知道哪些?
document.documentElement.clientHeight
:当前窗口内容区 + 内边距的高度window.innerHeight
: 当前窗口内容区 + 内边距 + 边框 + 滚动条高度window.outerHeight
:整个浏览器的高度(包括工具栏)clientHeight
: 当前元素内容区 + 内边距的高度clientTop
: 当前元素上边框的宽度offsetHeight
: 当前元素内容区 + 内边距 + 边框 + 滚动条的高度offsetTop
: 当前元素的边框距离父元素上外边距的距离scrollHeight
: 当前内部能够滚动区域的高度,若是不能滚动则为本身内容区 + 内边距的高度scrollTop
: 当前元素滚动离顶部的距离问:怎么肯定当前浏览器的类型?
navigator.userAgent
获取浏览器信息,根据里面的关键字来肯定。问:什么是简单请求和复杂请求?
get
、head
、post
。Content-type
仅限text/plain
、multipart/form-data
、application/x-www-form-urlencoded
。不符合以上条件者就为复杂请求,首先会发起一个option
方法的预检请求,来知道服务端是否容许跨域请求。
有一个坑就是服务端设置了CORS
,但当客户端发其复杂请求时会验证Authorization
字段,可是客户端并无,因此须要将option
方法过滤掉。html
问:从输入域名到页面显示都经历了什么?
DNS
解析为对应的IP
地址,而后经过Socket
发送数据,通过tcp
协议的三次握手,向该地址发起HTTP
请求,服务器处理,浏览器收到HTTP
响应的数据,关闭tcp
链接,开始渲染。问:谈谈浏览器的渲染机制?
JavaScript
、Css
、Html
在网络传输中都是0
和1
的字节流,因此浏览器首先会把接受到的这些字节流转为字符串。而后首先将html
字节流解析为字符串,对字符串进行标记化,肯定标签名以及里面的内容,而后生成对应的node
节点,根据节点的结构关系生成DOM
树。而后开始解析css
,和解析html
相似,css
通常有嵌套或继承的状况,浏览器会从里到外的递归来肯定每一个节点的样式是什么,从而生成一颗CSSOM
树。最后是将这两颗树结合起来生成一颗渲染树,浏览器根据渲染树进行布局,调用GPU
绘制生成页面显示在屏幕上。问:什么是重绘和回流?
color
这个行为;回流是指改变布局或几何属性发生改变时引发的行为,如添加移除Dom
,改变尺寸。它们频繁的触发会影响页面性能。问:如何减小重绘和回流?
transform
替代位移,使用translate3d
开启GPU
加速visibility
替代display:none
tanle
布局问:什么是事件流/模型?
window
=> document
=> body
=> 目标元素window
,沿途触发相同类型的事件问:什么是事件代理?
问:什么是事件对象?
问:什么是跨域?
ajax
请求就会失败。浏览器有同源策略主要是为了防止CSRF
攻击,防止利用户的登陆状态发起恶意请求。问:你知道的解决跨域的方式有几种?
JSONP:
利用script
标签不受同源策略限制,具体能够参考40行封装一个jsonp包。CORS:
使用自定义的HTTP
头部让浏览器和服务器进行沟通,实现CORS
的关键是后端,服务端设置Access-Control-Allow-Origin
就能够开启,表示哪些域名能够访问资源。document.domain:
当二级域名相同时,例如a.test.html
和b.test.html
,只须要给两个页面都设置document.domain = 'test.html'
,就能够实现跨域。postMessage:
如a.html
页面经过iframe
嵌入了b.html
页面,其中一个能够经过postMessage
方法发送信息,另外一页面经过监听message
事件判断来源并接受消息。问:
cookie
和session
分别是什么?
cookie
是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器发起请求时被携带并发送到服务器,它一般是告知服务端两个请求是否来自同一浏览器,保持用户的登陆状态。session
表明着服务器在和客户端一次会话的过程,存储着用户会话所需的属性及配置信息,当用户在不一样页面之间跳转时,使整个用户会话一直存在。问:
cookie
和session
有什么不一样?
cookie
存储在客户端,session
保存在服务器端。cookie
只能保存ASCⅡ
,session
能够存取任意数据类型。cookie
可设置长时间保持,session
通常失效时间较短,或客户端关闭就会失效。cookie
保存的数据不能超过4k
,session
可存储数据远高于cookie
。问:为何须要
cookie
和session
?
sessionID
存储到cookie
中,当再次发起请求时服务端根据携带的cookie
里的sessionID
来查找对应的session
信息,没有找到就说明没登陆或登陆失效,找到说明已经登陆,能够进行以后的操做。问:若是浏览器禁止了
cookie
怎么办?
SessionID
的参数;或者使用token
令牌,登陆后服务端生成一个Token
返回给客户端,之后客户端携带token
进行数据请求。问:使用
cookie
有哪些注意点?
value
:若是做用于登陆状态,须要加密。http-only
:不能经过JavaScript
访问到cookie
,防止XSS
攻击。same-site
:不能在跨域请求中携带cookie
,防止CSRF
攻击。问:先后端实现登陆的方式有哪些?
cookie
+ session
:前端登陆后,后端会种一个httpOnly
的cookie
在前端,里面就有这个用户对应的sessionId
,之后每一次前端发起请求会携带上这个cookie
,后端从里面解析到sessionId
后找到对应的session
信息,就知道是谁再操做了。缺点是后端须要空间存储session
,用户多了,服务器多了都不方便,这种方式基本属于淘汰边缘。jwt
+ token
:前端登陆后,后端会返回一个包括用户信息加密的token
字符串(可能还有过时时间,手机端有设备惟一码等信息),客户端本身保存了,将这个token
设置到header
里的Authorization
,以后每次请求都带上,服务器解码这个token
以后就知道是谁在访问了。优势是不占存储空间,后端解码便可。问:浏览器实现本地存储的方式有哪几种?
cookie:
存储大小4kb
,会随请求发送到服务端,可设置过时时间。localStorage:
存储大小为5M
,不参与请求,除非被清理,不然一直存在。sessionStorage:
存储大小为5M
,不参与请求,页面关闭清除。indexDB:
存储大小没限制,不参与请求,除非被清理,不然一直存在,运行在浏览器上的非关系型数据库。问:了解
Service Worker
嘛?
HTTPS
。使用Service-Worker
实现缓存功能通常分为三个步骤:首先注册,而后监听install
事件缓存须要的文件,最后拦截请求事件,若是缓存中已经有请求的数据就直接使用。问:谈谈对浏览器缓存的理解?
问:从哪些地方能够读取到浏览器缓存?
Service Worker:
和浏览器其余内建缓存机制不一样,它可让咱们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存且缓存是持续性的。Memory Cache:
从内存中读取,速度快,不过缓存的持续性并不高,关闭页面后内存中的缓存会被释放,什么东西能进内存肯定不了。Disk Cache:
速度没有内存快,不过存储的容量和持续性会高不少,在浏览器缓存中硬盘的覆盖面是最大的。能够根据HTTP Header
中的字段判断哪些资源须要缓存,哪些能够不请求直接使用,哪些已过时须要从新请求。而且即便在跨站点的状况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。Push Cache:
是HTTP/2
中的内容,存储时间很短暂,只在会话中,一旦会话结束就被释放。问:什么是浏览器缓存策略?
HTTP-Header
来实现的,在强缓存没有命中以后才会尝试协商缓存。HTTP-Header
的Expires
和Cache-Control
实现。强缓存表示在缓存期间不须要发起请求,状态码为200
。Expires:
是HTTP/1
的产物,值为缓存的过时时间,若是资源过时了就须要再次发起请求,若是修改本地时间,可能会形成缓存失效。Cache-control:
出现于HTTP/1.1
,若是和Expires
同时设置,优先级会更高。能够在请求头或者响应头中设置,能够组合多种指令使用。HTTP-Header
的Last-Modified
和ETag
实现。若是缓存过时了,就须要发起请求验证资源是否有更新。若是发起请求验证资源没有改变,返回状态304
,而且更新浏览器缓存的有效期。Last-Modified和If-Modified-Since
:Last-Modified
表示本地文件最后修改日期,If-Modified-Since
会将Last-Modified
的值发送给服务器,询问该日期以后的资源是否有更新,有就将新资源发送来,没有返回304
状态码。Last-Modified
的弊端:1. 若是本地硬盘打开了缓存文件,即便没修改也会被误认为已经修改了,从而致使服务器不能准确命中缓存,致使发送相同的内容;2. 只能以秒计时,若是在不可感知的时间内修改完成文件,服务端会认为资源仍是命中了,不会返回正确的资源。ETag和If-None-Match
:为了解决Last-Modified
的弊端,ETag
出现于HTTP/1.1
,他的优先级比Last-Modified
高。ETag
相似于文件的指纹,If-None-Match
会将ETag
发送给服务器,询问该ETag
是否变更,有变更的话就将新的资源发送回来。问:浏览器缓存适用的应用场景有哪些?
Cache-Control:no-cache
,使浏览器每次都请求服务器,而后配合ETag
或Last-Modified
来验证资源是否有效,虽然请求数量没少,不过能显著减小响应的数据大小。js
、css
等文件最后加上哈希值,只有当代码修改后才会生成新的文件名。因此咱们能够给这些文件设置Cache-Control:max-age=31536000
为一年的有效期,文件名变动了就下载新的文件,不然一直使用缓存。问:什么是
XSS
?
XSS
的类型分为两种:持久型和非持久型。URL
参数的方式加入攻击代码,从而诱导用户访问连接从而进行攻击。问:如何防范
XSS
?
script
标签;使用CSP
告诉浏览器限制外部资源能够加载和执行,开启CSP
有两种方式:1. 设置HTTP-Header
中的Content-sesurity-Policy
。 2. 设置<meta>
标签的方式<meta http-equiv="Content-Security-Policy">
。问:什么是
CSRF
?
问:如何防范
CSRF
?
get
请求对数据进行修改;不让第三方网站访问到用户的cookie
,设置cookie
的SameSite
属性,不让cookie
随跨域请求携带;组织第三方网站请求接口,验证Referer
;Token
验证,登录后服务器下发一个随机token
,以后的请求带上。问:什么是点击劫持?
iframe
嵌入的方式嵌入到本身的网页里,将iframe
设置为透明,在页面中透出一个按钮诱导用户点击。问:如何防范点击劫持?
HTTP
响应头X-FRAME-OPTIONS
,这个响应头就是为了防护点击劫持的;远古浏览器使用js
防护,当经过iframe
的方式加载页面时,让攻击者网站不显示内容。问:什么是中间人攻击?
问:若是防范中间人攻击?
wifi
;只使用https
协议,并关闭http
的访问。问:你知道的性能优化方式有哪些?
base64
,减小请求css
放最上面,js
放在body
最下面,渲染优化Dom
操做和避免回流,渲染优化cookie
的使用,减小请求携带大小问:
babel
是如何将ES6
代码转为ES5
的?
ES6
的代码字符串,生成 ES6
的AST
语法树;ES6
的AST
转为ES5
的 AST
语法树;ES5
的AST
转换成字符串代码。问:有哪些方式能够提高
webpack
的打包速度?
loader:
使用include
和exclude
指定搜索文件的范围。babel-loader:
配置后面加上loader:'babel-loader?cacheDirectory=true'
将编译过的文件缓存起来,下次只须要编译更改过的代码文件便可。HappyPack:
使用这个插件开启loader
多线程打包。DllPlugin:
将特定的类库提早打包而后引入,减小打包类库的次数,只有当类库更新版本才进行从新打包。resolve.alias:
配置别名,更快找到路径。module.noParse:
肯定这个文件没有其余依赖时,让webpack
打包时不扫描该文件。问:有哪些方式能够减少
webpack
的打包后的体积?
Scope Hoisting:
分析模块之间的依赖关系,尽量的把打包出来的模块合并到一个函数里。Tree Shaking:
删除项目中未被引用的代码。问:对
HTTP
协议的理解?
TCP/IP
协议家族的子集,属于通讯传输流中链路层、网络层、传输层、应用层中的应用层,主要职责是生成针对目标web
服务器的HTTP
请求报文和请求内容的处理。HTTP
是无状态的协议,不对请求和响应之间的通讯状态进行保存,响应完成后就会断开链接。问:什么是持久链接以及管线化?
HTTP/1.1
以前的时代,每一次HTTP
请求就须要先TCP
创建三次握手,传输完毕后就断开链接,会增长不少的通讯开销。HTTP/1.1
增长了持久链接,也就是说在一次TCP
链接里面能够发送屡次HTTP
请求,只要任意一端没有明确提出断开链接,则保持TCP
的链接状态,也就是响应头里面的Connection:keep-alive
。HTTP
的方式是,发送响应完成后才能发起下一个请求,而管线化解决的问题是能够一次发起多个HTTP
请求,且能够同时返回屡次响应结果。问:为何发起
HTTP
请求前须要TCP
三次握手?
HTTP
请求就能够传输数据了。问:为何关闭
HTTP
请求前须要TCP
四次挥手?
问:
HTTP
请求报文和响应报文里分别有什么?
ETag
、日期、内容类型等,以及响应的报文主体。问:
http1.0
和http1.1
的区别?
http1.1
加如了持久链接。问:
http
和https
的区别?
url
开头不一致是最明显的区分;其次http
没有https
安全,http
没有通过SSL/TLS
加密、身份验证;还有默认的端口不同,http
是80
、https
是443
,https
须要证书,https
是防止中间人攻击方式的一种。前端
问:为何
https
更安全?
问:常见的响应状态码有哪些?
2
开头表示成功、3
开头表示重定向、4
开头客户端错误、5
开头服务器错误。204:
如当浏览器发出请求处理后,返回204
响应,表示客户端显示的页面不发生更新。206:
客户端只要想要响应内容中的一部分,服务端成功执行了此次响应。响应报文中的Content-Range
则指定了哪部分的内容。301:
永久重定向,表示请求的资源已被分配到了新的URI
,之后使用新的吧302:
临时重定向,表示请求的资源已被分配到了新的URI
,如今使用新的吧303:
临时重定向,表示请求的资源已被分配到了新的URI
,请使用get
方法获取资源。304:
服务端找不到根据客户端发送附带条件的请求。(附带条件指get
请求报文中包含If-Match
、If-Modified-Since
、If-None-Match
、If-Range
、If-Unmodified-Since
中的一个)400:
请求报文存在语法错误。403:
请求资源被服务器拒绝。503:
代表服务器暂时处于超负载或正在停机维护,没法处理请求。问:get和post的区别?
get
回退不会从新发起请求,post
会;get
默认会被浏览器主动缓存,post
不会;get
只能进行url
编码,post
支持多种编码方式;get
的请求参数会被拼接到url
后面,post
放在request-body
中;get
产生一个tcp
数据包,post
会产生两个tcp
包;get
主要是应用为获取资源,post
主要是应用于传输或修改资源。问:什么是
UDP
协议?
UDP
是面向无链接的,传输双方没有确认机制,也就是说你要传就传吧,没有HTTP
那样须要事先三从握手。问:谈谈
vue
初始化从数据到视图的过程,能详细些吗?
vue
的内部会执行_init()
方法,进行一系列的初始化,首先会进行配置的合并,将用户传入的对象和自身的方法属性进行合并。render
函数内的数据转为VNode
的方法(手写render
函数里的h
方法),接着执行第一个生命周期钩子beforeCreate
函数。inject
、data
、computed
、watch
、provide
等,挂载到当前实例this
下,并完成数据的响应式,紧接着执行created
钩子函数。render
函数,完毕以后执行beforeMount
钩子。render
函数,获得VNode
,以后执行patch
由内而外的将VNode
转为真实Dom
,完成以后执行mounted
钩子。patch
的过程当中遇到了子组件的VNode
,就会转为去执行子组件的初始化到真实Dom
的状态过程,完毕以后才执行父组件的mounted
钩子。问:vue生命周期钩子有哪些,每一个钩子阶段都作了什么?
beforeCreate:
创建了组件的父子关系,完成了事件的初始化,还不能访问到定义的状态。created:
完成了provide/inject
和状态的初始化,能够访问到data
、props
等状态。beforeMount:
内部执行了$mount
函数,找到挂载点后执行beforeMount
,主要做用是做为挂载到完成阶段性能检测的起始时间以及将模板编译为render
函数。mounted:
开始将render
函数转为VNode
,并建立为真实的DOM
,挂载完成以后执行mounted
。beforeUpdate:
对于已经挂载完成的组件更新了组件模板的数据时,在异步更新以前触发。updated:
更新完成以后执行。更新相关的钩子连官网都不推荐使用,推荐使用watch
监听数据变化。activated:
被keep-alive
包裹的组件,由于不会销毁,因此也不会重建,它的created
、mounted
钩子不会触发。被缓存的组件激活时执行activated
钩子。deactivated:
离开当前激活的组件时触发。beforeDestroy:
组件销毁以前执行,这个时候尚未销毁任何东西,全部状态均可以访问到。destroyed:
当前组件的子组件都销毁完,当前组件也销毁完以后执行。问:组件之间通讯方式有哪些?
$emit
触发,传值给父组件的回调使用。props
给子组件;父组件使用ref
引用子组件实例,访问子组件的数据和方法。$children
属性,经过name
找到对应的兄弟组件实例。provide/inject
,父组件能够向全部子组件传值。vuex
或者Event Bus
;当前组件找到须要传值组件的实例,使用$on
和$emit
传值。问:父子组件如何完成数据双向绑定?
v-model形式: 只能绑定一个数据 父组件: <template> <Child v-model='msg'/> /* 可使用value和@input的形式 */ </template> export default { data() { return { msg: '' } } } 子组件: <template> <input v-model='currentMsg'/> </template> export default { props: ['value'], data() { return { currentMsg: this.value } }, watch: { value(newVal) { this.currentMsg = newVal }, currentMsg(newVal) { this.$emit('input', newVal) } } } sync形式: 绑定数据条数没限制 父组件: <template> <Child :name.sync='name' :sex.sync='sex'/> </template> export default { data() { return { name: '', sex: '' } } } 子组件: <template> <div> <input v-model='currentName'/> <input v-model='currentSex'/> </div> </template> export default { props: ['name', 'sex'], data() { return { currentName: this.name, currentSex: this.sex } }, watch: { name(newName) { this.currentName= newName }, sex(newSex) { this.currentSex = newSex }, currentName(newName) { this.$emit('update:name', newName) }, currentSex(newSex) { this.$emit('update:sex', newSex) } } }
问:什么是插槽、具名插槽、做用域插槽?
默认插槽: <button> <slot>插槽默认属性</slot> </button> 具名插槽: <div> <slot name="head">Head</slot> <slot name="center">Center</slot> <slot name="footer">Footer</slot> <slot>Default</slot> </div> <TestComp> <template v-slot:head> <div>添加到head</div> </template> <template #center> // 简写 <div>添加到center</div> </template> <template #footer> <div>添加到footer</div> </template> <template>添加到默认</template> </TestComp> 做用域插槽: <div> <slot :user="user">{{user.lastName}}</slot> </div> <TestComp v-slot="{user}"> <div>{{user.firstName}}</div> </TestComp> 具名插槽加做用域插槽 <div> <slot :user="user" name="info">{{user.lastName}}</slot> </div> <TestComp #info="{ user }"> <div>{{user.firstName}}</div> </TestComp>
问:
v-show
与v-if
区别?
v-if:条件渲染
,当条件为false
时,渲染时根本就不会渲染出Dom
。v-show:条件展现
,不管条件是什么,都会进行渲染出Dom
,显隐由display:none
切换。v-show
,若是不频繁就使用v-if
。问:
vue
里数组绑定class的用法?
key/value
的形式,value
是一个布尔值。computed: { classes() { return [ `${prefixCls}`, { [`${prefixCls}-${this.type}`]: this.type !== '' } ] } }
问:
vue
里的动画?css动画钩子
1.v-enter
:定义进入过渡的开始状态。在元素被插入以前生效,在元素被插入以后的下一帧移除。
2.v-enter-active
:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入以前生效,在过渡/动画完成以后移除。这个类能够被用来定义进入过渡的过程时间,延迟和曲线函数。
3.v-enter-to
: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入以后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成以后移除。
4.v-leave
: 定义离开过渡的开始状态。在离开过渡被触发时马上生效,下一帧被移除。
5.v-leave-active
:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时马上生效,在过渡/动画完成以后移除。这个类能够被用来定义离开过渡的过程时间,延迟和曲线函数。
6.v-leave-to
: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发以后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成以后移除。vue
JavaScript动画钩子v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
在 enter 和 leave 中必须使用 done 进行回调,不然,它们将被同步调用,过渡会当即完成。
appear会完成初始渲染。html5
问:如何实现一个自定义过滤器?
Vue.filter
方法进行自定义过滤器的全局注册,第一个参数是过滤器的名称,第二个参数是一个回调,回调内的第一个参数就是须要处理的文本值。同一个文本能够通过多个过滤器,使用 |
进行分割便可,过滤器也能够像使用方法般接受参数。问:如何实现一个自定义指令?
Dom
时,会更加的方便,通常是使用Vue.directive
方法进行自定义指令的全局注册。注册自定义时接受两个参数,第一个是指令的名称(不加v-
),第二个是一个对象,里面主要是一些指令的钩子函数,如bind
(绑定到节点时执行)、inserted
(渲染到父节点时执行)、update
(指令所在组件更新时)、componentUpdated
(所在组件及其子组件更新时)、unbind
(解除绑定时),在这些钩子的内部第一个参数就是指令绑定对应的真实Dom
,第二个参数binding
,指令对应的一些信息或传的参数。在钩子函数里完成对Dom
的操做就是自定义指令作的事情。问:谈谈对
vuex
的理解?
state
内,能够被全部组件所引用,一经修改后引用state
内状态的组件都会响应式的更新。state
不能被直接修改,必须commit
内提交mutation
才行,且mutation
必须是同步函数。提交action
能够在内部进行异步操做,可同时提交多个mutation
。store
内的state
能够经过this.$store.state.xxx
访问。store
内的getter
能够经过this.$store.getters.xxx
访问。store
内的mutation
能够经过this.$store.commit('xxx')
提交。store
内的action
能够经过this.$store.dispatch('yyy')
提交。问:
vuex
语法糖方法有哪些以及如何使用?
computed: { ...mapState(['xxx', 'yyy']) } computed: { ...mapGetters(['xxx', 'yyy']) } methods: { ...mapMutations({ zzz: 'XXX_YYY' }) } methods: { ...mapActions(['yyy']) }
问:
vue-router
如何传递参数?
方式1: { path:"/home/:id", component:Home } this.$router.push({ path:`/home/${id}`, }) 在Home组件中获取参数 this.$route.params.id 方式2:须要命名路由 { path:'/home', name: 'home' component:Home } this.$router.push({ name:'home', params:{id:9527} }) 在Home组件中获取参数 this.$route.params.id 方式3: { path:"/home", component:Home } this.$router.push({ path:'/home', query:{id:9527} }) 在Home组件中获取参数 this.$route.query.id
问:
vue-router
有哪些导航守卫钩子?以及它们的执行顺序?
beforeEach
:只要当某个导航被触发时,就会执行这个钩子。beforeResolve
:在路由的beforeEnter
和组件的beforeRouteEnter
执行以后执行。afterEach
:在全部的导航守卫执行完毕以后执行,没有next
方法。node
beforeEnter
:在路由内定义,在全局beforeEach
以后执行。webpack
beforeRouteEnter
:在渲染组件对应路由被确认以前调用,不能访问this
,在路由beforeEnter
钩子以后执行。beforeRouteUpdate
:在当前路由改变但组件被复用时调用,例如在动态子路由以前调转时。beforeRouteLeave
:导航离开该路由时调用。web
beforeRouteLeave
beforeEach
beforeRouteUpdate
beforeEnter
beforeRouteEnter
beforeResolve
afterEach
问:如何实现异步组件?
方式1: const Home= () => import('components/home') export default new Router({ routes: [ { path: '/home', component: Home, }, ] }) 方式2:能够指定多个路由为相同`chunk`名,会打包在一块儿 export default new Router({ [{ path: '/home', component: r => require.ensure([], () => r(require('../components/home')), 'home' /* chunk名 */) }] }) 方式3: export default new Router({ [{ path: '/promisedemo', component: resolve => require(['../components/home'], resolve) } ]}) 方式4:高级异步组件,带`loading`和`error`组件 const Home= () => lazyLoadView(import('components/home')) export default new Router({ routes: [ { path: '/home', component: Home, }, ] }) function lazyLoadView(AsyncView) { const AsyncHandler = () => ({ component: AsyncView, loading: require('@/components/loading').default, error: require('@/components/error').default, delay: 200, timeout: 10000 }); return Promise.resolve({ functional: true, render(h, { data, children }) { return h(AsyncHandler, data, children); } }); }
问:请实现一个最小化
vue
响应式示例?
class Watcher { update() { console.log('更新~'); } } class Dep { constructor() { this._watchers = new Set(); } add(watcher) { if(!this._watchers.has(watcher)) { this._watchers.add(watcher); } } notify() { this._watchers.forEach(watch => { watch.update(); }) } } Dep.target = new Watcher(); function observer(target) { if (typeof target === 'object' && target !== null) { Object.keys(target).forEach(key => { defineReactive(target, key, target[key]); }) } } function defineReactive(target, key, val) { const dep = new Dep(); if (typeof val === 'object' && val !== null) { observer(val); } Object.defineProperty(target, key, { get() { dep.add(Dep.target); return val; }, set(newVal) { dep.notify(); val = newVal; } }) }
问:工厂模式?
function Coder(name, age) { this.name = name this.age = age this.career = 'coder' this.work = ['写代码', '写系分', '修Bug'] } function ProductManager(name, age) { this.name = name this.age = age this.career = 'product manager' this.work = ['订会议室', '写PRD', '催更'] } ... 简单封装,不一样再去一个个的new具体的角色 function Factory(name, age, career) { if (career === 'coder') { return new Coder(name, age); } else if (career === 'product manager') { return new ProductManager(name, age); } ... } 将角色抽象成User类,使用工厂进一步封装 function User(name, age, career, work) { this.name = name; this.age = age; this.career = career; this.work = work; } function Factory(name, age, career) { let work; if (career === 'coder') { word = ['写代码', '写细分', '修Bug']; } else if (career === 'product manager') { word = ['订会议室', '写PRD', '催更']; } ... return new User(name, age, career, word); }
问:单例模式?
vue
里的插件,安装一次以后不会再次安装,直接返回以前已经实例化的结果。实现Storage类,使得该对象为单例,基于localStorage进行封装。实现方法 setItem(key,value) 和 getItem(key)?
静态方法版: class Storage { static create() { if (!Storage.instance) { Storage.instance = new Storage(); } return Storage.instance; } setItem(key, value) { return localStorage.setItem(key, value); } getItem(key, value) { return localStorage.getItem(key, value); } } 闭包版: function Storage() { } Storage.prototype.setItem = function (key, value) { return localStorage.setItem(key, value); } Storage.prototype.getItem = function (key) { return localStorage.getItem(key, value); } const createStorage = (function () { let instance; return function () { if (!instance) { instance = new Storage(); } return instance } })()
问:实现一个全局惟一的模态框?
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>modal</title> <style> #modal { position: fixed; height: 200px; width: 200px; line-height: 200px; top: 50%; left: 50%; transform: translate(-50%, -50%); border: 1px solid #000; text-align: center; } </style> </head> <body> <button id='open'>打开模态框</button> <button id='close'>关闭模态框</button> <script> const Model = (function () { let box; return function () { if (!box) { box = document.createElement('div'); box.innerHTML = '惟一'; box.id = 'modal'; box.style.display = 'none'; document.body.appendChild(box); } return box; } })() document.getElementById('open').addEventListener('click', () => { const model = Model(); model.style.display = 'block'; }) document.getElementById('close').addEventListener('click', () => { const model = Model(); model.style.display = 'none'; }) </script> </body> </html>
问:观察者模式和发布订阅模式的区别?
vue
内的自定义事件的Event Emitter
,发布者彻底不用感知到订阅者,事件的注册和触发都发生在事件总线上,实现了彻底的解耦。Dep
和Watcher
就是观察者模式,Dep
直接add
以及notify
触发watcher
的更新。观察者模式: class Subject { constructor() { this.observers = []; } add(observer) { this.observers.push(observer); } remove(observer) { const index = this.observers.indexOf(observer); if (index > -1) { this.observers.splice(index, 1); } } notify() { const obs = this.observers; for (let i = 0; i < obs.length; i++) { obs[i].update(); } } } class Observer { constructor(name) { this.name = name; } update() { console.log('my name is' + this.name); } } 发布订阅模式: class Events { constructor() { this._evnets = Object.create(null); } on(event, fn) { // 往事件中心添加事件 if (Array.isArray(event)) { for (let i = 0; i < event.length; i++) { this.on(evnet[i], fn); } } else { (this._evnets[event] || (this._evnets[event] = [])).push(fn); } } emit(event, ...args) { // 触发事件中心对应事件 const cbs = this._evnets[event]; if (cbs) { for (let i = 0; i < cbs.length; i++) { cbs[i].apply(this, args); } } } off(event, fn) { // 移除事件 if (!arguments) { this._evnets = Object.create(null); return this; } if (Array.isArray(event)) { for (let i = 0; i < event.length; i++) { this.off(event[i], fn); } return this; } if (!fn) { this._evnets[event] = null; return this; } const cbs = this._evnets[event]; let i = cbs.length; while (i--) { const cb = cbs[i]; if (cb === fn || cb.fn === fn) { cbs.splice(i, 1); break; } } return this; } once(evnet, fn) { // 只执行一次 function on() { this.off(evnet, on); fn.apply(this, arguments); } on.fn = fn; this.on(evnet, on); return this; } }