前端百问

问:如何理解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-celltable-captioninline-block之一。
  • position的值不为staticreleative中的任何一个。

问:你经常使用的清除浮动方式是什么?

.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: centerline-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的变量有哪些类型?

  • 分为两种:基础类型和引用类型。基础类型目前有六种,分别是booleannullundefinednumberstringsymbol
  • 除了以上的基础类型以外,其余就是引用类型了,有ArrayObjectFunction

问:基础类型和引用的区别?

  • 它们在内存中存储的方式不一样。基础类型存储的是值,而引用类型存储的是指向内存中某个空间的指针;
  • 基础类型赋值就是把值赋给另一个变量,而引用类型的赋值是赋值的原来变量的指针,因此当引用类型发生改变时,只要是指向同一个指针的变量的都会发生改变。

问:函数参数是对象时会发生什么问题?

  • 函数参数是对象时,至关因而将对象的指针传递给了函数,若是在函数的内部改变了对象的值,外面对象的值也会发生改变,数组也是如此。

问:typeofinstanceof判断变量类型的区别?

  • 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);
  })
}

问:字符串的testmatchsearch它们之间的区别?

`test`是检测字符串是否匹配某个正则,返回布尔值;
/[a-z]/.test(1);  // false

`match`是返回检测字符匹配正则的数组结果集合,没有返回`null`;
'1AbC2d'.match(/[a-z]/ig);  // ['A', 'b', 'C', 'd']

`search`是返回正则匹配到的下标,没有返回`-1`。
'1AbC2d'.search(/[a-z]/);  // 2

问:字符串的slicesubstringsubstr它们之间的区别?

`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 == nullvar === undefined || var === null的简写,其他状况一概使用===

问:是否===就彻底靠谱?

  • 也是不必定的,例如0 === -0就为trueNaN === NaNfalse,判断两个变量是否彻底相等可使用ES6新增的APIObject.is(0, -0)Object.is(NaN, NaN)就能够准确区分。

问:在类型转换中哪些值会被转为true

  • 除了undefinednullfalseNaN''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施法的寒冰箭,再读条的过程当中,你身子抖动打断了施法,再次触发技能时麻烦您从新读条。
  • 函数节流:火冲为瞬发技能,不过你规定cd8s,因此即便8s内按了10次,也只能来1发,节省点体力吧。

问:请手写一下map、call、new、instanceof、Events、深拷贝、节流、Promise等?

问:varletconst的区别 ?

  • var类型会有变量提高的状况,也就是说声明会首先提高到当前做用域的顶端,在使用到时再读取定义的值,并且在全局做用域下定义的变量会挂载到window下,而letconst并不会。
  • letconst没有变量提高的状况,必需要先声明再使用,不然就会出现暂时性死区的状况。
  • 并且它们的做用域存在最近的大括号以内,也就是块级做用域,而且一经定义后,同一个做用域内不能再次定义。
  • constlet的区别在于一经定义后不得再次改变const定义的值,若是是引用类型只要不改变指针,改变里面的值是没问题的。
  • const定义时必须赋值,let没必要。

问:SetWeakSet的区别?

  • Set类型内存储的是不会重复的值,建议存储基础类型的值,由于引用类型的指针都不一样。
  • WeakSet只能存储对象参数,不然会报错,并且是存储的引用类型的弱引用。
  • WeakSet不可被迭代,不支持forEachfor-ofkeysvalues方法,没有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

问:MapWeakMap的区别?

  • 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以前不管是定义仍是继承都好理解了不少。继承主要是使用extendssuper关键字,本质相似于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构造函数内的代码是同步执行的,而以后thencatch方法是异步执行的,构造函数接受两个函数参数resolvereject,它们执行时接受的参数分别会传递给thencatch表示成功的回调以及失败回调接受到的值。
  • Promise一共有三种状态pending等待状态、resolved已完成状态、rejected已拒绝状态,状态的改变只能由等待转为已完成或等待转为已拒绝状态,并且状态的改变只会发生一次。
  • 必需要实现then方法且方法里必需要返回一个Promise对象,若是是返回其余的类型会尝试包装成Promise对象;
  • then能够被链式的调用。
  • 缺点是Promise链中途没法取消;错误须要经过回调函数捕获。

问:谈谈对ES-Module的理解 ?

  • ES-ModuleES6原生支持模块化方案,经过import来引入模块,经过export defaultexport来导出模块。

问:谈谈对Proxy的理解 ?

  • Object.defineProperty有些相似,它的做用是用来自定义对象中操做。Proxy的构造函数接受两个参数,第一个参数是须要代理的对象,第二个参数是一个对象,里面会定义getset方法,当代理对象中的某个值被访问或从新赋值就会触发相应的getset方法。vue3.0就抛弃了Object.defineProperty而拥抱了Proxy,它的优势是只须要代理一次,对象内的值发生了改变就会被感知到,再也不须要像之前为对象的每一个值进行数据劫持;并且之前对象的新增,数组的下标设置0清空等状况均可以被感知到,在响应式里也不在须要为数组和对象收集两次依赖,相信会大大提高性能。

谈谈对Generator的理解?

  • JavaScript方便建立迭代器的新语法,在方法名前面添加*号,表示这个方法是一个生成器函数,在函数内部配合yield关键字指定next()方法返回值及顺序。
  • yield相似与在函数内部打上了断点,yield就是每一处的debugger,执行next()方法后进入下一个断点。
  • 不能使用箭头函数来建立生成器。

问:谈谈对asyncawait的理解 ?

  • Genneator的语法糖形式,解决的问题是以同步的形式写异步代码,让代码流程能很好的表示执行流程。在函数的前面加上async代表是一个异步函数,函数的内部须要配合await关键字使用,每个await关键字至关因而yield,会暂停函数的执行,直到异步函数执行完毕后内部会自动执行next()方法,执行以后的代码,函数的返回结果是一个Promise对象。由于是以同步的形式书写异步代码,因此错误捕获是使用try/catch的形式。

问:谈谈对Event-Loop的理解 ?

  • JavaScript的执行机制简单来讲就先执行同步代码,而后执行异步代码,而异步的代码里面又分为宏任务代码和微任务代码,先执行微任务,而后执行宏任务。首先会将全部JavaScript做为一个宏任务执行,遇到同步的代码就执行,而后开始分配任务,遇到宏任务就将它的回调分配到宏任务的队列里,遇到微任务的回调就分配到微任务的队列里,而后开始执行全部的微任务。执行微任务的过程仍是遵循先同步而后分配异步任务的顺序,微任务执行完毕以后,一次Event-LoopTick就算完成了。接着挨个去执行分配好的宏任务,在每一个宏任务里又先同步后分配异步任务,完成下一次Tick,循环往复直到全部的任务所有执行完成。
  • 微任务包括:process.nextTickpromiseMutationObserver
  • 宏任务包括:scriptsetTimeoutsetIntervalsetImmediateI/OUI rendering

问:对浏览器或元素的各类距离参数你知道哪些?

  • document.documentElement.clientHeight:当前窗口内容区 + 内边距的高度
  • window.innerHeight: 当前窗口内容区 + 内边距 + 边框 + 滚动条高度
  • window.outerHeight:整个浏览器的高度(包括工具栏)
  • clientHeight: 当前元素内容区 + 内边距的高度
  • clientTop: 当前元素上边框的宽度
  • offsetHeight: 当前元素内容区 + 内边距 + 边框 + 滚动条的高度
  • offsetTop: 当前元素的边框距离父元素上外边距的距离
  • scrollHeight: 当前内部能够滚动区域的高度,若是不能滚动则为本身内容区 + 内边距的高度
  • scrollTop: 当前元素滚动离顶部的距离

问:怎么肯定当前浏览器的类型?

  • 经过navigator.userAgent获取浏览器信息,根据里面的关键字来肯定。

问:什么是简单请求和复杂请求?

  • 简单请求:
  1. 请求方法仅限getheadpost
  2. Content-type仅限text/plainmultipart/form-dataapplication/x-www-form-urlencoded
  • 复杂请求:

不符合以上条件者就为复杂请求,首先会发起一个option方法的预检请求,来知道服务端是否容许跨域请求。
有一个坑就是服务端设置了CORS,但当客户端发其复杂请求时会验证Authorization字段,可是客户端并无,因此须要将option方法过滤掉。html

问:从输入域名到页面显示都经历了什么?

  • 首先将域名DNS解析为对应的IP地址,而后经过Socket发送数据,通过tcp协议的三次握手,向该地址发起HTTP请求,服务器处理,浏览器收到HTTP响应的数据,关闭tcp链接,开始渲染。

问:谈谈浏览器的渲染机制?

  • 书写的JavaScriptCssHtml在网络传输中都是01的字节流,因此浏览器首先会把接受到的这些字节流转为字符串。而后首先将html字节流解析为字符串,对字符串进行标记化,肯定标签名以及里面的内容,而后生成对应的node节点,根据节点的结构关系生成DOM树。而后开始解析css,和解析html相似,css通常有嵌套或继承的状况,浏览器会从里到外的递归来肯定每一个节点的样式是什么,从而生成一颗CSSOM树。最后是将这两颗树结合起来生成一颗渲染树,浏览器根据渲染树进行布局,调用GPU绘制生成页面显示在屏幕上。

问:什么是重绘和回流?

  • 重绘是节点的外观发生改变而不改变布局时,如改变了color这个行为;回流是指改变布局或几何属性发生改变时引发的行为,如添加移除Dom,改变尺寸。它们频繁的触发会影响页面性能。
  • 回流必定触发重绘,而重绘不必定引发回流。回流的成本比重绘高不少,并且子节点的回流,可能引发父节点一系列的回流。

问:如何减小重绘和回流?

  • 使用transform替代位移,使用translate3d开启GPU加速
  • 尽可能使用visibility替代display:none
  • 不要使用tanle布局
  • 不要在循环里读取节点的属性值
  • 动画速度越快,回流次数越少

问:什么是事件流/模型?

  • 当某一个事件被触发时,分为三个阶段:
  • 1.事件经过捕获从window => document => body => 目标元素
  • 2.事件到达注册的目标上
  • 3.目标元素经过冒泡返回到window,沿途触发相同类型的事件

问:什么是事件代理?

  • 利用事件流的冒泡特性,将子节点的事件绑定在父节点上,而后在回调里面使用事件对象进行区分,优势是节省内存且不须要给子节点销毁事件。

问:什么是事件对象?

  • 这个对象里面存放着触发事件的状态,如触发事件的当前元素,键盘事件是哪一个按键触发的,滚动事件的位置等等。

问:什么是跨域?

  • 也就浏览器的同源策略,出于安全的考虑,只要是协议、域名、端口有一个不一样就算是跨域,ajax请求就会失败。浏览器有同源策略主要是为了防止CSRF攻击,防止利用户的登陆状态发起恶意请求。

问:你知道的解决跨域的方式有几种?

  • JSONP: 利用script标签不受同源策略限制,具体能够参考40行封装一个jsonp包
  • CORS:使用自定义的HTTP头部让浏览器和服务器进行沟通,实现CORS的关键是后端,服务端设置Access-Control-Allow-Origin就能够开启,表示哪些域名能够访问资源。
  • document.domain:当二级域名相同时,例如a.test.htmlb.test.html,只须要给两个页面都设置document.domain = 'test.html',就能够实现跨域。
  • postMessage:a.html页面经过iframe嵌入了b.html页面,其中一个能够经过postMessage方法发送信息,另外一页面经过监听message事件判断来源并接受消息。

问:cookiesession分别是什么?

  • cookie是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器发起请求时被携带并发送到服务器,它一般是告知服务端两个请求是否来自同一浏览器,保持用户的登陆状态。
  • session表明着服务器在和客户端一次会话的过程,存储着用户会话所需的属性及配置信息,当用户在不一样页面之间跳转时,使整个用户会话一直存在。

问:cookiesession有什么不一样?

  • 做用范围不一样:cookie存储在客户端,session保存在服务器端。
  • 存取的方式不一样:cookie只能保存ASCⅡsession能够存取任意数据类型。
  • 有效期不一样:cookie可设置长时间保持,session通常失效时间较短,或客户端关闭就会失效。
  • 存储大小不一样:单个cookie保存的数据不能超过4ksession可存储数据远高于cookie

问:为何须要cookiesession

  • 让服务器知道根它打交道的用户是谁以及用户的状态,浏览器发起第一次请求后服务端会返回一个sessionID存储到cookie中,当再次发起请求时服务端根据携带的cookie里的sessionID来查找对应的session信息,没有找到就说明没登陆或登陆失效,找到说明已经登陆,能够进行以后的操做。

问:若是浏览器禁止了cookie怎么办?

  • 每次请求都携带一个SessionID的参数;或者使用token令牌,登陆后服务端生成一个Token返回给客户端,之后客户端携带token进行数据请求。

问:使用cookie有哪些注意点?

  • 不建议做为存储方式使用。首先会随请求携带,影响请求的性能,其次存储空间也过小,最后一些属性的使用也须要注意。value:若是做用于登陆状态,须要加密。http-only:不能经过JavaScript访问到cookie,防止XSS攻击。same-site:不能在跨域请求中携带cookie,防止CSRF攻击。

问:先后端实现登陆的方式有哪些?

  • cookie + session:前端登陆后,后端会种一个httpOnlycookie在前端,里面就有这个用户对应的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-HeaderExpiresCache-Control实现。强缓存表示在缓存期间不须要发起请求,状态码为200
  • Expires:HTTP/1的产物,值为缓存的过时时间,若是资源过时了就须要再次发起请求,若是修改本地时间,可能会形成缓存失效。
  • Cache-control:出现于HTTP/1.1,若是和Expires同时设置,优先级会更高。能够在请求头或者响应头中设置,能够组合多种指令使用。

cache.png

  • 协商缓存:经过设置HTTP-HeaderLast-ModifiedETag实现。若是缓存过时了,就须要发起请求验证资源是否有更新。若是发起请求验证资源没有改变,返回状态304,而且更新浏览器缓存的有效期。
  • Last-Modified和If-Modified-SinceLast-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,使浏览器每次都请求服务器,而后配合ETagLast-Modified来验证资源是否有效,虽然请求数量没少,不过能显著减小响应的数据大小。
  • 不频繁变更的资源:通常使用打包工具会为生成的jscss等文件最后加上哈希值,只有当代码修改后才会生成新的文件名。因此咱们能够给这些文件设置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,设置cookieSameSite属性,不让cookie随跨域请求携带;组织第三方网站请求接口,验证RefererToken验证,登录后服务器下发一个随机token,以后的请求带上。

问:什么是点击劫持?

  • 是一种视觉欺骗的攻击手段,攻击者将须要攻击的网站经过iframe嵌入的方式嵌入到本身的网页里,将iframe设置为透明,在页面中透出一个按钮诱导用户点击。

问:如何防范点击劫持?

  • 现代浏览器设置HTTP响应头X-FRAME-OPTIONS,这个响应头就是为了防护点击劫持的;远古浏览器使用js防护,当经过iframe的方式加载页面时,让攻击者网站不显示内容。

问:什么是中间人攻击?

  • 就是在攻击者在服务端和客户端创建了链接,并让对方认为链接是安全的,攻击者不只能得到双方的通讯信息,还能修改通讯信息。

问:若是防范中间人攻击?

  • 不要使用公共wifi;只使用https协议,并关闭http的访问。

问:你知道的性能优化方式有哪些?

    1. 文件压缩,减少资源大小
    1. 异步组件,按需加载
    1. 小图片转base64,减小请求
    1. 雪碧图,减小请求
    1. 选择合适的图片格式和尺寸
    1. 懒加载,按需加载
    1. css放最上面,js放在body最下面,渲染优化
    1. 事件节流,减小操做
    1. 减小Dom操做和避免回流,渲染优化
    1. 浏览器缓存,减小请求次数或响应数据
    1. 减小cookie的使用,减小请求携带大小

问:babel是如何将ES6代码转为ES5的?

  • 首先解析ES6的代码字符串,生成 ES6AST语法树;
  • 而后将ES6AST转为ES5 AST语法树;
  • 最后将ES5AST转换成字符串代码。

问:有哪些方式能够提高webpack的打包速度?

  • loader:使用includeexclude指定搜索文件的范围。
  • 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.0http1.1的区别?

  1. http1.1加如了持久链接。
  2. 加入了更多的请求头、响应头(缓存方面的)。
  3. 新增了一些错误状态码、如409(请求的资源和资源当前状态发生冲突)、410(服务器上的某个资源被永久性的删除)。

问:httphttps的区别?

url开头不一致是最明显的区分;其次http没有https安全,http没有通过SSL/TLS加密、身份验证;还有默认的端口不同,http80https443https须要证书,https是防止中间人攻击方式的一种。前端

问:为何https更安全?

  • 通常使用公钥加密或私钥加密:
  1. 公钥加密双方都须要事先知道一个都知道加密方式的密钥,优势是加密速度快,缺点是过程当中可能会被窃取,安全性没有非对称加密高。
  2. 非对称加密会加入公钥和私钥,客户端使用第三方证书去服务端获取公钥,而后用获取到的公钥把共享密钥进行加密发送给服务端,服务端使用私钥解密出共享密钥,服务端用获取到的共享密钥进行消息的加密,客户端用用共享密钥进行解密。

问:常见的响应状态码有哪些?

  • 大体能够分为2开头表示成功、3开头表示重定向、4开头客户端错误、5开头服务器错误。
  • 204:如当浏览器发出请求处理后,返回204响应,表示客户端显示的页面不发生更新。
  • 206:客户端只要想要响应内容中的一部分,服务端成功执行了此次响应。响应报文中的Content-Range则指定了哪部分的内容。
  • 301:永久重定向,表示请求的资源已被分配到了新的URI,之后使用新的吧
  • 302:临时重定向,表示请求的资源已被分配到了新的URI,如今使用新的吧
  • 303:临时重定向,表示请求的资源已被分配到了新的URI,请使用get方法获取资源。
  • 304:服务端找不到根据客户端发送附带条件的请求。(附带条件指get请求报文中包含If-MatchIf-Modified-SinceIf-None-MatchIf-RangeIf-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函数。
  • 接下来会初始化一些状态,好比injectdatacomputedwatchprovide等,挂载到当前实例this下,并完成数据的响应式,紧接着执行created钩子函数。
  • 开始组件的挂载阶段,若是是带编译器版本且须要编译时,这个时候就开始将模板编译render函数,完毕以后执行beforeMount钩子。
  • 接下来执行render函数,获得VNode,以后执行patch由内而外的将VNode转为真实Dom,完成以后执行mounted钩子。
  • 若是在patch的过程当中遇到了子组件的VNode,就会转为去执行子组件的初始化到真实Dom的状态过程,完毕以后才执行父组件的mounted钩子。

问:vue生命周期钩子有哪些,每一个钩子阶段都作了什么?

  • beforeCreate:创建了组件的父子关系,完成了事件的初始化,还不能访问到定义的状态。
  • created:完成了provide/inject和状态的初始化,能够访问到dataprops等状态。
  • beforeMount:内部执行了$mount函数,找到挂载点后执行beforeMount,主要做用是做为挂载到完成阶段性能检测的起始时间以及将模板编译为render函数。
  • mounted:开始将render函数转为VNode,并建立为真实的DOM,挂载完成以后执行mounted
  • beforeUpdate:对于已经挂载完成的组件更新了组件模板的数据时,在异步更新以前触发。
  • updated:更新完成以后执行。更新相关的钩子连官网都不推荐使用,推荐使用watch监听数据变化。
  • activated:keep-alive包裹的组件,由于不会销毁,因此也不会重建,它的createdmounted钩子不会触发。被缓存的组件激活时执行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-showv-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

  1. beforeRouteLeave
  2. beforeEach
  3. beforeRouteUpdate
  4. beforeEnter
  5. beforeRouteEnter
  6. beforeResolve
  7. 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,发布者彻底不用感知到订阅者,事件的注册和触发都发生在事件总线上,实现了彻底的解耦。
  • DepWatcher就是观察者模式,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;
  }
}
相关文章
相关标签/搜索