前端面试知识点整理

403 阅读25分钟

CSS

flex属性

flex-direction:row
justify-concent:center//水平居中
align-items:center//垂直居中

flex-direction:column
justify-concent:center//垂直居中
align-items:center//水平居中

flex 实现左边自适应,右边定宽高

.box{
    display:flex;
    Width:100%;
    Height:1000px;
}
.left{
    Flex:1
}
.right{
    Width:200px
}

写出元素垂直居中的 n 种方法

1. 子元素绝对定位
.box {
   width: 100px;
   height: 100px;
   position: absolute;
   left: 0px;
   right: 0px;
   bottom: 0px;
   top: 0px;
   margin: auto;
   background-color: red;
}
2.flex
body{
  display:flex;
  justify-content:center;
  align-items:center;
  height: 100vh;
  width:100%
}
.box{
  width: 100px;
  height: 100px;
  background:red;
}
3.绝对定位
.box {
   position: absolute;
   width: 100px;
   height: 100px;
   background: red;
   top: 50%;
   left: 50%;
   margin-left: -50px;
   margin-top: -50px;
}
4.绝对定位+transform
.box{
   position:absolute;
   width:100px;
   height:100px;
   background:red;
   top:50%;
   left:50%;
   transform:translate(-50%,-50%)
}

垂直居中

1.table属性
#wrapper {
	display: table;
}

#cell {
	display: table-cell;
	vertical-align: middle;
}
2.绝对定位
#content {
	position: absolute;
	top: 50%;
	height: 240px;
	margin-top: -120px; /* negative half of the height */
}
3.浮动
#floater {
	float: left;
	height: 50%;
	margin-bottom: -120px;
}
#content {
	clear: both;
	height: 240px;
	position: relative;
}
4.定位
#content {
	position: absolute;
	top: 0;
	bottom: 0;
	left: 0;
	right: 0;
	margin: auto;
	height: 240px;
	width: 70%;
}

css3哪些属性

动画怎么去优化

transform

BFC

清除浮动

  1. 父级添加overflow:hidden
  2. 最后一个浮动元素后添加标签,给其设置clear:both
  3. 使用after伪元素
.clearfix:after{/*伪元素是行内元素 正常浏览器清除浮动方法*/
      content: "";
      display: block;
      height: 0;
      clear:both;
      visibility: hidden;
   }
   .clearfix{
      *zoom: 1;/*ie6清除浮动的方式 *号只有IE6-IE7执行,其他浏览器不执行*/
   }
 
<body>
    <div class="fahter clearfix">
        <div class="big">big</div>
        <div class="small">small</div>
        <!--<div class="clear">额外标签法</div>-->
    </div>
    <div class="footer"></div>
</body>
  1. 使用before和after双伪元素清除浮动
.clearfix:after,.clearfix:before{
   content: "";
   display: table;
}
.clearfix:after{
   clear: both;
}
.clearfix{
   *zoom: 1;
}

<div class="fahter clearfix">
   <div class="big">big</div>
   <div class="small">small</div>
</div>

元素隐藏

  1. display:none
  2. visibility:hidden
  3. opacity:0

JS

数组去重

indexOf

let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
function unique(ary){
   let arr = [];
   for (let i = 0; i < ary.length; i++) {
      let item = ary[i],
         args = ary.slice(i + 1)
      if (args.indexOf(item) < 0) {
         arr.push(item)
      }
   }
   return arr;
}
console.log(unique(ary));

Set

function unique(ary){
   return Array.from(new Set(ary))
}

forEach/includes

function unique(ary) {
    let arr = [];
    ary.forEach((i) => {
        return arr.includes(i) ? "" : arr.push(i);
    });
    return arr;
}

includes

function unique(ary) {
    let arr = [];
    for (let i = 0; i < ary.length; i++) {
        if (!arr.includes(ary[i])) {
            arr.push(ary[i]);
        }
    }
    return arr;
}

先排序再相邻元素比较

function unique(ary) {
    const formatArr = ary.sort();
    let arr = [];
    // console.log(formatArr);
    for (let i = 0; i < formatArr.length; i++) {
        if (formatArr[i] !== formatArr[i + 1]) {
            arr.push(formatArr[i]);
        }
    }
    return arr;
}

数组排序

冒泡排序

  1. 每一轮都拿当前项和后一项作对比,每一轮结束后最大值会到末尾
  2. 一共需要比多少轮:数组长度-1
  3. 一共需要比较多少次:最多(数组长度-1)次,而且要把之前放到末尾的大值排除掉
  4. 两者交换位置
  5. 时间复杂度O(n^2)
function bubble(arr) {
    let len = arr.length - 1;
    for (let i = 0; i < len; i++) {
        for (let j = 0; j < len - i; j++) {
            if (arr[j] > arr[j + 1]) {
                let temp = arr[j + 1];
                arr[j + 1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}
console.log(bubble(ary));

插入排序

1.手里先抓一张牌
2.一次从桌面上取牌
    - 每一次取出一张牌,都和手里的牌进行比较(倒着比)
        - 如果新取出的牌比当前比较的这张大,则放到当前这张的后面
        - 如果新取出的牌比当前比较的这张小,继续向前比,直到遇到比他大的
        -如果比到头都没有发现比新抓牌小的,则放到开始位置即可
function insert(ary) {
    //准备一个新数组,用来存储拿到手里的牌
    let handle = [];
    handle.push(ary[0]);
    // 从第二项开始一次抓牌,一直把台面上的牌抓光
    for (let i = 1; i < ary.length; i++) {
        //A是新抓的牌
        let A = ary[i];
        // 和handle手里的牌依次比较(从后往前比)
        for (let j = handle.length - 1; j >= 0; j--) {
            // 每一次要比较的手里的牌
            let B = handle[j];
            // 如果当前新牌A比要比较的牌B大了,把A放到B的后面
            if (A > B) {
                handle.splice(j + 1, 0, A);
                break;
            }
            if (j === 0) {
                handle.unshift(A);
            }
        }
    }
    return handle;
}
let ary = [12, 8, 24, 16, 1];
console.log(insert(ary));

快速排序

1.取出数组中间项
2.拿每一项和中间项比较
    - 比中间项小的放在左边
    - 比中间项大的放在右边
3.再依次把左边和右边用同样的方式处理
4.直到某一边的数组是一项或者没有,则不再拆分
function quick(ary) {
    // 结束递归(当数组中小于等于一项,则不用处理)
    if (ary.length < 2) {
        return ary;
    }
    // 找到数组的中间项,在原有的数组中把它移除
    let midIdx = Math.floor(ary.length / 2);
    let midVal = ary.splice(midIdx, 1)[0];
    // 准备左右两个数组,循环剩下数组中的每一项,比当前项小的放到左边数组中,比当前项大的放到右边数组中
    let leftAry = [],
        rightAry = [];
    for (let i = 0; i < ary.length; i++) {
        let item = ary[i];
        item < midVal ? leftAry.push(item) : rightAry.push(item);
    }
    return quick(leftAry).concat(midVal, quick(rightAry));
}
let ary = [12, 8, 24, 16, 1];
console.log(quick(ary));

数组扁平化

flat

arr = arr.flat(Infinity);

toString转换为字符串

arr = arr.toString().split(',').map(i=>ParseFloat(i));

循环验证是否为数组

while(arr.some(i=>Array.isArray(i))){
    arr=[].concat(...arr)
}
function flatten(arr) {
    return arr.reduce((prev, item) => {
        return prev.concat(Array.isArray(item) ? flatten(item) : item);
    }, []);
}

复杂度

  1. 时间复杂度
    1. Ο(1)<Ο(log2(n))<Ο(n)<Ο(n^2)<Ο(n^3)<…<Ο(2^n)

Promise

map 和 forEach 区别

  1. 相同点
    1. 都是循环遍历数组中每一项
    2. 都支持三个参数(item,index,原数组)
    3. this 都指向 window
    4. 只能遍历数组
  2. 不同点
    1. forEach 没有返回值,map 有

js 中有几种作用域

Map,Set,weakMap,weakSet

  1. Set
    1. 类数组,成员值唯一,Set 是一个构造函数,需要 new
    2. Set.add/Set.delete/Set.has/Set.clear
    3. 数组去重
      1. […new Set(arr)]
    4. 并集
      1. Array.from(new Set([arr1,arr2])
    5. 交集
      1. […arr1].filter(item=>arr2.has(item))
    6. 差集
      1. […arr1].filter(item=>!arr2.has(item))
  2. WeakSet
    1. 与 Set 类似,也是不重复的值得结合,区别在于成员只能是引用类型
    2. WeakSet 中的对象都是弱引用,垃圾回收机制不会考虑 WeakMap 对该对象的应用,如果其他对象都不再引用该对象,name 垃圾回收机制就会自动回收该对象所占用的内存,不考虑对象是否还存在于 WeakSet 中,所以 WeakSet 没有 size,不能遍历
    3. let ws = new WeakSet();=>ws.add()/ws.has()/ws.delete()
    4. 深拷贝
  3. Map
    1. 键值对的合集,键的范围不限于字符串
    2. map.size()/map.set()/map.get()/map.delete()/map.has()/map.clear()
    3. 转换为数组=>[…map]
  4. WeakMap
    1. WeakMap 只接受对象为键名 null 除外,为弱引用,不计入垃圾回收机制
    2. WeakMap 只是键名弱引用
    3. vm.set()/vm.get()/vm.has()/vm.delete()

浏览器垃圾回收机制

  1. 内存泄漏
    1. 如果那些不想再使用的变量所占用的内存没有被释放,就会造成内存泄漏
    2. 内存泄漏值我们程序中已经动态分配的堆内存,由于某些原因没有被释放,造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果
  2. 垃圾回收机制
    1. 标记清除
      1. 当代码执行在一个环境中时,每声明一个变量,就会对该变量做一个标记,当代码执行进入另一个环境中时,这时对上一个环境中的变量做一个标记,等到垃圾回收执行时,会根据标记来觉得要清除那些变量进行释放内存
    2. 引用计数
      1. 创建了堆内存,被占用一次,则浏览器计数+1,取消占用计数—1,当记录的数字为零的时候,则内存释放掉
    3. 垃圾回收算法
      1. 从根节点出发,遍历所有的对象,可以遍历到的是可达的,不能遍历到的是不可达的
        1. 根节点:不会被回收
          1. window
          2. DOM
          3. 存放在栈上的变量
      2. 标记完成后,统一清理内存中不可达的对象
      3. 做内存整理

Array.slice(start,end)

  1. start:必须,从何处开始选取,如果是负数,则是从数组尾部开始算起的位置(-1 指倒数第一个元素)
  2. end:可选,规定从何处结束选取,是数组片段结束出的数组下标,如果没有此参数,则切分的数组包含从 start 到数组结束的所有元素,如果是负数,则规定从数组尾部开始算起的元素
  3. 返回一个新数组

Array.splice():方法向/从数组中添加/删除项目,然后返回被删除的项目

String.split()/Array.join()

  1. String.split(''):'1111'=>["1", "1", "1", "1"]
  2. Array.join(''):["1", "1", "1", "1”]=>'1111'

Array.flat():返回一个新数组

  1. flatMap()只能展开一层数组:[2, 3, 4].flatMap((x) => [ x, x * 2])//[2,4,3,6,4,8]

concat:连接两个或多个数组,不会改变现有的数组,会返回被连接数组的副本

Super()作用

  1. 子类必须在 constructor 中调用 super,因为子类没有自己的 this 对象,而是继承父类的 this 对象,如果不调用 super,子类就拿不到 this 对象

reduce 方法

  1. arr=[1,2,3,4,5,6]
  2. arr.reduce((a,b)=>{return a+b},a)
    1. a=1,b=2
    2. a=3,b=3
    3. a=6,b=4
    4. a=10,b=5
    5. a=15,b=6
    6. 21

  1. 展开运算符:在等号右边或者实参上
  2. 剩余运算符:在等好左边或者形参上

Object.create()

  • 创建一个新对象,使用现有的对象来提供新创建对象的__proto__
  • [Child.prototype = Object.create(Parent.prototype)]====[Child.prototype.proto=Parent.prototype]

proto 和 prototype

  1. 每一个类(函数)都具备 prototype,并且属性值是一个对象
  2. 对象上天生具备一个属性 constructor,指向类本身
  3. 每一个对象都具备proto,属性值是当前实例所属类的原型

new 方法做了什么

  1. 创建一个新的实例对象
  2. 新的实例对象的proto关联到构造函数的 prototype
  3. 改变构造函数的 this 指向,让其指向新创建的实例对象
  4. 如果返回的是空对象或是基本对象,则 return 实例本身,否则 renturn 原本的结果

实现异步的 forEach 方法

var arry = [...];
Promise.all(arry.map(function(elem){
  return new Promise(function(resolve, reject){
    ...
    resolve(result);
  })
})).then(function(data){
  //在这就可以等所有的返回结果可以得到
})

for in/for of数组遍历

  1. for in获取的是key
  2. for of获取的是value
  3. for in会遍历对象的整个原型链,for of 只遍历当前对象
  4. for in会返回数组中所有可枚举的属性,for of只会返回数组的下标对应的属性值

基础类型和引用类型有什么区别

  1. 基本类型是存放在栈内存中,引用类型的值是同事保存在栈内存和堆内存中的对象
  2. 基本类型的值是不可变的,引用类型的值是可变的
  3. 基本类型的比较是值得比较,引用类型的比较是引用的值
  4. 在从一个变量向另一个变量赋值基本类型时,会在该变量上创建一个新值,肉厚再把该值复制到为新变量分配的位置上
  5. 当从一个变量向另一个变量赋值引用类型时,同样也会将存储在变量中的对象的值赋值一份方法哦为新变量分配的空间中,保存在变量中的是对象在堆内存中的地址,这个值得副本实际上是一个指针,二这个指针指向存储在堆内存的一个对象,赋值后,两个边路都保存了同一个对象地址,则这两个变量指向了同一个对象,因此改变其中任何一个变量,都会相互影响。
  6. 引用类型的赋值其实是对象保存在栈区地址指针的赋值,因此两个变量指向同一个对象,任何操作都会互相影响

类数组转换为数组

1. Array.from
2. Array.prototype.slice.call()/[].slice.call()
3. […ele]
4. for(let I =0;i<arguments.length;i++){
    let item = arguments[I];arr.push()
  }

数据类型检测

  1. Typeof
    1. 返回一个字符串
    2. null 会返回 object
    3. 所有对象都是 000 开头,所以 typeof 无法细分是普通对象还是数组对象
  2. instanceof
    1. 用来检测当前实例是否属于这个类
    2. 应用于普通对象,数组对象,正则对象,日期对象等的具体细分
    3. Array Instanceof Object 是 true
    4. 无法应用到原始数据类型的检测上
  3. constructor
    1. 获取实例的构造函数
    2. 比 instanceof 好用
    3. 但是不准确,constructor 是可以随意被修改
  4. Object.prototype.toString.call(value)

import exports/require export

柯里化

REACT

setState

  1. 更新数据是一个异步操作
  2. 不能在 render 中触发 setState
  3. 只在合成事件和钩子函数中是异步的,原生事件和 settimerout 中是同步的
  4. 在 setState 函数中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中延时更新,默认是 false,但当 react 调用事件处理函数之前会先调用 batchedUpdates 将 isBatchingUpdates 修改为 true,这样 setState 就不会同步更新 this.state

React 的 hooks

  1. useState:让 function 组件可以自己维护 state
  2. userCallback:避免组件重复渲染,提高性能
  3. userReducer:function 组件需要维护复杂数据的时候使用(函数,初始值) 1.
  4. userEffect
    1. 第一个参数是一个回调函数
    2. 第二个参数是一个数组,数组中放的是依赖
    3. 只有当数组中的依赖改变的时候才会触发这个回调函数
    4. 第二个参数可以不穿,不传的时候相当于类组件的 didmount 和 didupdate 的合体
    5. 第二个参数如果是一个空数组。则类似于我们类组件的 didmount
    6. return 的函数可以用来清除当前回调函数的副作用,类似 componentwillUnmount
  5. useMemo
    1. shouldcomponentupdate 类似作用,在渲染中避免重复渲染

为什么使用 function 组件

  1. class 组件冗余,每次都会创建一个实例
  2. 学习成本大,this 指向有困难

react 的生命周期

  1. 生命周期是一个抽象的概念(挂载、更新,卸载)
    1. 挂载阶段:指组件从初始化到完成加载的过程
    2. 更新阶段:外部 props 传入或者 state 发生变化时的阶段
    3. 卸载阶段:清理工作,解除定时器和事件绑定
  2. constructor:类通用的构造函数,常用于初始化
    1. constructor 中并不推荐区处理初始化以外的逻辑
    2. constructor 并不属于 React 的生命周期,只是 Class 的初始化函数
    3. 代码更加简洁
  3. getDerivedStateFromProps(props, state)
    1. 可以把 props 转到 state 中
    2. 把返回的对象合并到 state 中
    3. 使组件在 props 变化时更新 state
    4. 触发时机
      1. props 被传入
      2. State 发生变化时
      3. forceUpdate 被调用时
  4. UNSAFE_componentWillMount()
    1. 用于组件加载前做某些操作
    2. 对比成 vue 的 beforeMount 但是已经被废弃
    3. 由于异步渲染机制,该方法可能会被多次调用
      1. 同构渲染时在此方法请求数据,会在服务端和客户端触发两次
    4. 不能和 1 同时出现
  5. render()
    1. 返回 jsx 结构,用于描述具体的渲染内容
    2. render 并没有真正的渲染组件
    3. 不能 setstate 和绑定事件
  6. componentDidMount()
    1. 主要用于组件加载完成时做某些操作
    2. 对比与 vue 的 mounted
    3. ajax 请求的发送都在这个钩子中进行
    4. 接着 render 之后调用
    5. 在真实 DOM 绘制完成之后调用
  7. UNSAFE_componentWillReceiveProps
    1. 可被 getDerivedStateFromProps 替代
  8. shouldComponentUpdate(nextProps,nextState)
    1. 返回一个 true,当前组件更新
    2. 返回一个 false,当前组件不更新
  9. UNSAFE_componentWillUpdate()
    1. 被废除.
  10. getSnapshotBeforeUpdate
    1. 配合 react 新的异步渲染机制,在 dom 更新发生前被调用,返回值作为 componentDidUpdate 的第三个参数
  11. componentDidUpdate()
    1. 类比于 vue 的 updated
  12. componentWillUnmount()
    1. 类比于 vue 的 beforeDestory()
    2. 一般用来销毁定时器或者某些事件

如何避免生命周期中的坑

  1. 重新渲染的三种情况
    1. 函数组件任何情况下都会重新渲染,优化手段 React.memo
    2. React.Component
      1. State 发生变化时
      2. 当父组件的 props 传入时,无论 props 有没有变化,只要传入就会引发重新渲染
    3. React.PureComponent
      1. 仅在 props 和 state 进行浅比较后,确认有变更时才会触发重新渲染
  2. 避免生命周期中的坑需要做好两件事:
    1. 不在恰当的时候调用了不该调用的代码;
    2. 在需要调用时,不要忘了调用。
  3. 主要有 7 种情况容易造成生命周期的坑。
    1. getDerivedStateFromProps 容易编写反模式代码,使受控组件与非受控组件区分模糊。
    2. componentWillMount 在 React 中已被标记弃用,不推荐使用,主要原因是新的异步渲染架构会导致它被多次调用。所以网络请求及事件绑定代码应移至 componentDidMount 中。
    3. componentWillReceiveProps 同样被标记弃用,被 getDerivedStateFromProps 所取代,主要原因是性能问题。
    4. shouldComponentUpdate 通过返回 true 或者 false 来确定是否需要触发新的渲染。主要用于性能优化。
    5. componentWillUpdate 同样是由于新的异步渲染机制,而被标记废弃,不推荐使用,原先的逻辑可结合 getSnapshotBeforeUpdate 与 componentDidUpdate 改造使用。
    6. 如果在 componentWillUnmount 函数中忘记解除事件绑定,取消定时器等清理操作,容易引发 bug。
    7. 如果没有添加错误边界处理,当渲染发生异常时,用户将会看到一个无法操作的白屏,所以一定要添加。

PureComponent:

  1. 内部默认调用了 shouldComponentUpdate
  2. 如果是引用类型可能不生效,因为地址不同

React 的请求应该放在哪里,为什么?

  1. 对于异步请求,应该放在 componentDidMount 中去操作。从时间顺序来看,除了 componentDidMount 还可以有以下选择:
    1. constructor:可以放,但从设计上而言不推荐。constructor 主要用于初始化 state 与函数绑定,并不承载业务逻辑。而且随着类属性的流行,constructor 已经很少使用了。
    2. componentWillMount:已被标记废弃,在新的异步渲染架构下会触发多次渲染,容易引发 Bug,不利于未来 React 升级后的代码维护。
  2. 所以 React 的请求放在 componentDidMount 里是最好的选择。

函数组件和类组件的区别

  1. 作为组件而言,类组件与函数组件在使用与呈现上没有任何不同
  2. 类组件是基于面向对象编程的,它主打的是继承、生命周期等核心概念;而函数组件是函数式编程,主打的是 immutable、没有副作用、引用透明等特点
  3. 如果存在需要使用生命周期或使用继承的组件,那么主推类组件。
  4. 但现在由于 React Hooks 的推出,函数组件可以完全取代类组件。
  5. 类组件主要依靠 shouldComponentUpdate 阻断渲染来提升性能,而函数组件依靠 React.memo 缓存渲染结果来提升性能。
  6. 类组件更容易上手,由于 React Hooks 的推出,函数组件成了社区未来主推的方案。

说一下Redux

  1. 是一种数据管理模型,和 react 没有什么关系
  2. 想把 redux 应用到 react 中必须借助 react-redux
  3. state、reducer、action
  4. 单一数据源
  5. state 只读
  6. 使用纯函数来执行修改 reducer

Redux有哪些弊端

  1. getState返回返回的值和原始容器中的state用的同一个堆内存,这样会导致获取state后直接就能修改容器中的状态信息
  2. 向subscribe中追加方法的时候没有做去重处理
  3. dispatch:如果状态改变通知事件池中的方法会全部依次执行,应该做一个判断,对应的组件中用到的事件执行即可

react-redux:专门为react项目封装的redux处理库,简化redux在组件中的应用代码

  1. Provider:把store挂载到祖先元素的上下文忠,方便后期后台组件的调用
  2. connect高阶函数
    1. 把redux容器中存储的状态以及需要派发的行为任务都通过属性传递给当前组件
    2. 会自动像事件池中追加当前组件的重新渲染方法,保证状态更新,组件会重新渲染(省略了subscribe方法)
    3. mapStateToProps:把redux容器中的公共状态当做属性传递给当前的组件
    4. mapDispatchToProps:把需要派发的行为方法通过属性传递给当前的组件
      1. connect会默认把actionCreator对象变为mapDispatchToProps这种函数的模式

redux中间件

  1. applyMiddleware:应用中间件
    1. createrStore(reducer,applyMiddleware(...要用的中间件))
  2. redux-logger:在控制台输出每一次派发任务的情况
  3. 管控actioncreators中异步处理(2选1即可)
    1. redux-thunk
      1. reducer中将返回的对象改写返回dispatch函数,函数里面做异步处理
      2. 每次点击会进行两次派发
    2. redux-promise
      1. 传递给reducer的action对象中的参数必须叫做payload,返回的还是一个对象
      2. payload:new Promise(resolve=>{settimeout(()=>{resolve(10)},1000)})
      3. 每次点击也是两次派发

高阶函数:一个函数执行返回一个函数接着执行

React 的 diff

React fiber

react16 新增

VUE

Vue 的 diff 算法

  1. 同层比较,看两个节点是否一致
    1. 不一样:直接替换
    2. 一样:调用 patchVnode 方法
      1. 老节点有子节点,新节点没有,将老节点的子节点移除
      2. 老节点没有子节点,新节点有,给新节点新增子节点
      3. 都只有文本节点则直接替换
      4. 都有子节点,则调用 updateChildren 方法
        1. 头和头比,尾和尾比,头和尾比,尾和头比
          1. 若元素相同,再调用 patchVnode 方法
          2. 不相同,头依次和后面的元素比较
  2. 3.0 采用的位运算先判断 vnode 类型

vue 的$nextTick 什么原理

  1. 由来
    1. vue 驱动视图更新是异步的,即修改数据之后,试图不会立刻更新,而是等同一时间循环中的所有数据变化完成之后再统一进行试图更新
  2. 触发时机
    1. 在视图更新后基于新的视图进行操作
    2. 同一时间循环中的代码执行完毕-》DOM 更新->nextTick callback 触发
  3. 作用
    1. 可以避免 DOM 频繁变动,避免因此造成的浏览器卡顿,大幅提升性能

computed/watch 区别

  1. 能使用 computed 优先使用 computed,但是 computed 不能写异步方法
  2. 多个数据影响一个数据的时候,我们考虑使用 computed
  3. 一个数据影响多个数据的时候,考虑使用 watch

watcher中的deep和immediate

deep

  1. 是否深度监听,当data为obj时如果deep设置为true,

immediate

  1. watcher中变量绑定的方法是否立即执行

什么是 mvvm

  1. 进行数据劫持
  2. 模板编译
  3. 观察者模式 watcher 把两条线连接起来
  4. 每当数据更新的时候通知 watcher,让 watcher 监听到的地方更新
  5. 利用 Object.defineProperty

vue 如何检测数组变化

  1. 对数组原型链上的方法进行重写

Data 方法为什么是函数

  1. 一个组件被多次复用,会创建多个实例。本质上用的同一个构造函数,如果 data 是对象,属于引用类型,会影响所有的实例,为了保证不同实例间 data 不冲突,data 必须是一个函数

vue 模板编译原理

  1. 生成一个虚拟 dom 树,用正则对模板进行解析
  2. 优化,深度遍历 dom 树,按照条件对节点进行标记,被标记过的可以跳过对他们的对比
  3. 将优化后的 dom 树转换为可执行的代码

computed 和 watcher

  1. 能使用 computed 优先使用 computed,但是 computed 不能写异步方法
  2. 多个数据影响一个数据的时候,我们考虑使用 computed
    1. 本质是一个具备缓存的 watcher,依赖的属性发生变化就会更新视图,适用于比较消耗性能的场景
  3. 一个数据影响多个数据的时候,考虑使用 watcher
    1. 没有缓存性,更多的是观察作用,可以监听某些数据执行回调,需要深度监听时,打开 deep:true,优化可以使用字符串形式监听。

keep-alive

  1. 可以实现组件缓存,当组件切换时不会对当前组件进行卸载
  2. 常用属性 include/exclude,允许有条件的进行缓存
  3. 两个生命周期 activated/deactivated,用来得知当前组件是否处于活跃状态

Vue 中组件生命周期调用顺序

  1. 调用顺序都是先父后子,渲染完成是先子后父
  2. 组件销毁都是先父后子,销毁完成是先子后父

vue 组件通信方式

  1. 父子
    1. 父-子 props,子-父 on/on/emit
    2. 获取父子组件实例 parent/parent/children
    3. ref
    4. provide/inject
  2. 兄弟
    1. event bus vue.prototype.$bus
    2. vuex
  3. 跨级
    1. vuex
    2. attrs/attrs/listeners
    3. provide/inject

做过哪些 vue 性能优化

  1. 尽量减少 data 中的数据,data 中的数据会增加 getter 和 setter,回收机对应的 watcher
  2. spa 页面采用 keep-alive 缓存组件
  3. 按需使用 v-if 和 v-show
  4. key 保证唯一
  5. 使用路由懒加载
  6. 第三方模块按需导入
  7. 图片懒加载
  8. 长列表滚动到可视区域动态加载
  9. cdn 加载第三方模块

vue 的 mvvm 和 react 的 mvc

  1. 都是操作数据来影响视图,告别了传统操作 DOM 的时代
    1. Model 控制 View 层
      1. vue 基于数据劫持,拦截到最近的数据,从而重新渲染视图
      2. React 是提供了对应的 API,通过操作 API,让最新数据渲染视图
    2. 都一定存在 DOM 的差异化渲染(DOM DIFF)
      1. 每一次数据更改,只把需要改变的试图部门进行重新渲染
    3. React 默认只实现了单项控制(只有数据影响视图),而 Vue 基于 v-model 实现了双向控制(即也包含了视图影响数据)
      1. 不论 vue 还是 react,在实现视图影响数据的方式上,也都是基于 change/input 事件,监听表单元素内容的改变,从而去修改数据,达到数据的更新

其他

混合式开发

  1. jsBridge:前端注入到 webview 的一个全局变量
  2. schema 协议:url 拦截,前端页面与 App 的通信协议,app 内部页面跳转协议

https 与http

  1. http 和 https 的区别
    1. https 更加安全,对搜索引擎更友好,利于 seo,谷歌百度优先索引 https 网页
    2. https 需要用到 ssl 证书,http 不用
    3. https 端口 443,http 端口 80
    4. https 基于传输层,http 基于应用层
    5. https 在浏览器显示绿色安全锁,http 没有显示
    6. https 是在 http 上建立 ssl 加密层,并对传输数据进行加密,是 http 协议的安全版
  2. 作用
    1. 对数据进行加密,并建立一个信息安全通道,来保证传输过程中的数据安全
    2. 对网站服务器进行真实身份认证
  3. http 缺点
    1. 通信使用明文,内容可能被窃听
    2. 无法证明报文的完整性,所以可能遭篡改(没有办法确认发出的请求/响应和接收到的请求/响应是前后相同的)
    3. 不验证通信方的身份,因此有可能遭遇伪装(http 协议中的请求和响应不会对通信方进行确认)
  4. Https 如何解决了上述缺点
    1. http 直接和 tcp 通信,当使用 ssl 是则变成先和 ssl 通信,再由 ssl 和 tcp 通信,所谓 https 就是身披 ssl 协议的 http
    2. tls/ssl 主要依赖于三类基本算法:散列函数,对称加密和非对称加密
      1. 非对称加密实现身份认证和密钥协商
      2. 对称加密采用协商的秘钥对数据加密
      3. 基于散列函数验证信息的完成性
    3. 解决内容可能被窃听的问题
      1. 对称加密
        1. 加密和解密同用一个密钥,加密和解密都会用到密钥,没有密钥就无法解密,任何人只要持有密钥就能解密
      2. 非对称加密
        1. 私有密钥不能被其他任何人知道,二公开密钥可以随意发布
        2. 发送密文的以防使用对方的公开密钥进行加密处理,对方收到被加密的信息后,再使用自己的私有密钥进行解密
          1. 信息传输一对多,服务器只需要维持一个私钥就能够和多个客户端进行加密通信
          2. 缺点
            1. 公钥是公开的,针对私钥加密的信息,黑客可以使用公钥进行解密
            2. 公钥不包含服务器信息,使用非对称加密算法无法确保服务器身份的合法性,服务器发送给客户端的公钥可能在传送过程中被中间人截获并篡改
            3. 在数据加密解密过程需要消耗一定时间,降低了数据传输效率
      3. 对称加密+非对称加密
        1. 在交换密钥环节使用非对称加密,之后建立通信交换报文阶段使用对称加密
        2. 具体做法:发送密文的一方使用对方的公钥进行加密处理“对称的密钥”,然后对方用自己的私钥解密拿来’对称的密钥’,这样可以确保交换的密钥是安全的全体下,使用对称加密方式进行通信
  5. 解决报文可能遭篡改问题-数字签名
    1. 功效
      1. 能去人多消息确实是有发送方签名并发出来的,因为别人假冒不了发送方的签名
      2. 数字签名能确定消息的完整性,证明数据是否未被篡改过
    2. 生成方式:讲一段文本先用 hash 函数生成消息摘要,然后用发送者的私钥加密生成数字签名,与原文一起传送给接受者
  6. 解决通信方身份可能被篡改的问题-数字证书

https 工作流程

  1. client 发起一个 https 请求
  2. server 把事先配置好的公钥证书返回给客户端
  3. client 验证公钥证书
  4. client 使用伪随机数生成器生成加密所使用的对称密钥
  5. server 使用自己的私钥解密这个消息,得到对称密钥,此时 client 和 server 双方否持有相同的对称密钥
  6. server 使用对称密钥加密“a”发送给 client
  7. client 使用对称密钥解密相应的密文,得到’a”
  8. client 再次发起 https 的请求,使用对称你要加密请求的’b’,然后 server 使用对称密钥解密密文,得到’b'

HTTP1.0和HTTP1.1的一些区别

  1. 缓存处理,HTTP1.0中主要使用 Last-Modified,Expires 来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略:ETag,Cache-Control…
  2. 带宽优化及网络连接的使用,HTTP1.1支持断点续传,即返回码是206(Partial Content)
  3. 错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除…
  4. Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)
  5. 长连接,HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点

HTTP2.0和HTTP1.X相比的新特性

  1. 新的二进制格式(Binary Format),HTTP1.x的解析是基于文本,基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合,基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮
  2. header压缩,HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小
  3. 服务端推送(server push),例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了
// 通过在应用生成HTTP响应头信息中设置Link命令
Link: </styles.css>; rel=preload; as=style, </example.png>; rel=preload; as=image
  1. 多路复用(MultiPlexing)
- HTTP/1.0  每次请求响应,建立一个TCP连接,用完关闭
- HTTP/1.1 「长连接」 若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,毫无办法,也就是人们常说的线头阻塞;
- HTTP/2.0 「多路复用」多个请求可同时在一个连接上并行执行,某个请求任务耗时严重,不会影响到其它连接的正常执行;

一个 url 从输入到展现

url 解析

  1. 地址解析
    • 协议:客户端和服务器之间的数据通信,传输协议就是负责运送这些信息的 http/https/ftp
      • http:超文本传输协议
      • https:安全,ssl 加密(产品涉及支付)
      • ftp:文件上传下载(本地代码上传到服务器)
    • 域名:服务器地址
      • 顶级域名 qq.com
      • 一级域名 www.qq.com
      • 二级域名 sports.qq.com
      • 三级域名 kbs.sports.qq.com
    • 端口号:用来区分服务器上的不同服务 0-64435 之间
    • 请求资源的文件路径
    • 查询字符串:问号参数
    • 片段标识符:HASH 值
  2. 编码
    1. encodeURI /decodeURI:编码整个 url(处理 url 中的中文)
    2. encodeURIComponent/decodeURIComponent:编码 url 中问号传递的信息

缓存检查

  • 缓存位置:内存缓存(memory cache)/硬盘缓存(disk cache)
    1. 打开网页:查找硬盘缓存(disk cache)中是否有匹配,如果有则使用,没有则发送网络请求
    2. 普通刷新 F5:因 tab 没有关闭,内存缓存是可用的,会被优先使用,其次才是硬盘缓存
    3. 强制刷新:浏览器不使用缓存,发送的请求头部均带有 cache-control:no-cache,服务器直接返回 200 和最新内容
    • 资源文件缓存方式:强缓存/协商缓存(服务端做时间标识的存储,客户端不需要做处理)
      1. 强缓存:服务器直接设置 Expires 和 Cache-Control
        • Expires:缓存过期时间,用来指定资源到期时间(http1.0)是具体时间
        • Cache-Control:max-age=2592000 秒,第一次拿到资源后的 30 天内,再次发送请求,读取缓存中的信息(http1.1)
        • 两者同时存在的话,Cache-Control 优先级高于 Expires
        • 过程
          1. 检测缓存信息和缓存标识 Expires 和 Cache-Contro
          2. 有且未过期,客户端直接读取渲染缓存信息
          3. 没有或者已过期,客户端重新向服务器发送新的 http 请求
          4. 返回该请求结果和缓存标识 Expires 和 Cache-Contro
          5. 把请求结果和缓存标识存储到浏览器中
        • html 页面一般不设置强缓存(放置服务器更新文件后,客户端获取的还是本地缓存中的页面,这样页面不能及时更新)
        • css/js/图片设置强缓存
          1. 如果服务器有更新,页面会更新,只要每次有更新 css、js 等,我们在请求的 css、js 后面设置一个时间戳
          2. 基于 webpack,只要资源文件有更新,生成不同文件名字的资源:hash 值
      2. 协商缓存:Last-Modified(1.0)/ETag(1.1),强缓存失效或者没有时,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,html做协商缓存
        • 第一次请求的时候不传递
        • 服务器端返回资源,同时在请求头里返回 last-modified(服务器端当前资源文件最后一次的修改时间)/ETag(根据当前资源的修改时间生成一个标识)
        • 第二次发送请求 if-modified-since(上一次服务器返回的时间)/if-none-match(上一次返回的标识)
        • 服务区端把获取的标识/时间与最新资源的标识/时间作对比,
          1. 如果一样,返回 304,通知客户端读取缓存信息
          2. 不一样,直接缓存最新的资源和标识
      3. 区别
        • 强缓存:本地没有缓存,从服务器拿,本地有缓存,和服务器没有关系
        • 协商缓存:不管有没有缓存,都先像服务器发请求,校验服务器上的资源是否更新,如果没有更新则返回 304,从缓存中获取信息,如果有更新,返回 200,并且把最近的结果和标识缓存到本地
      4. 应用:html不做强缓存缓存,其他的文件类型都是强缓存和协商缓存都做
    • 数据缓存(需要客户端手动处理)
      1. 基于ajax从服务器获取最新的数据信息,并将数据存到本地(localstorage/vuex/redux)
      2. 检测本地缓存的数据信息
        • 如果有,对比当前时间和过期时间,如果在未过期则直接从缓存中拿,
        • 如果过期了或者没有缓存信息,则重新发起ajax请求

DNS解析

  • 第一次请求,本地没有DNS缓存,需要一个完整的DNS解析20-120ms
    • 迭代查询
      • 客户端-本地DNS服务器-根域名服务器-本地DNS服务器-顶级域名服务器-本地DNS服务器-全为域名服务器-本地DNS服务器-客户端
  • 第二次请求,基本是直接用上一次DNS解析缓存记录即可
    • 递归查询
      • 客户端-浏览器缓存-本地hosts文件-本地DNS解析器缓存-本地DNS服务器
  • 减少DNS请求次数
    • 一个项目中,尽可能只访问相同的服务器,不要访问过多的服务器和域名,但不现实
      • 服务器拆分优势
        • 资源的合理利用
        • 抗压能力加强
        • 提高Http并发
    • DNS预获取(DNS Prefetch)预获取时可以同时做页面渲染等,以节省时间

TCP三次握手

  • 既要保证数据可靠传输,又要提高传输效率
  • UDP比较快,但是不做校验,不可靠(消息,视频流会用)

数据传输

TCP四次挥手

  • 为什么连接的时候是三次握手,关闭的时候却是四次握手? 服务器端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文
  • 但关闭连接时,当服务器端收到FIN报文时,很可能并不会立即关闭链接,所以只能先回复一个ACK报文,告诉客户端:”你发的FIN报文我收到了”,只有等到服务器端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送,故需要四步握手。

页面渲染

浏览器缓存

  1. DNS:每次访问 url 会去查询 DNS
  2. CDN:服务器缓存,将资源放在各地 CDN 节点上,用户访问 url,会去 CDN 专用 DNS 解析,然后根据用户 url 分析最近节点资源及负载情况,返回给浏览器

LocalStorage/sessionStorage/cookie

ssr

  1. 也就是从服务端渲染,将 vue 在客户端把标签渲染成 html 的工作由服务端来完成,然后再把 html 直接返回给客户端
  2. ssr 有这更好的 seo,并且首屏加载速度更快,但是开发条件会受到限制,服务端渲染只支持 beforeCreate 和 created 两个钩子

Babel 运行原理

  1. 解析:接受代码并输出 AST(抽象语法树)
    1. 词法解析
      1. 把字符串形式的代码转换为令牌流(扁平的语法片段数组)
    2. 语法解析
      1. 把令牌流转换成 AST 形式,这个阶段会使用令牌中的信息把他们转换成一个 AST 的表述结构
    3. code(字符串形式代码) -> tokens(令牌流) -> AST(抽象语法树)
  2. 转换
    1. 接收 AST 并对其进行遍历,在此过程中对节点进行添加,更新,移除等操作
    2. babel 提供了 traverse 方法维护 AST 树的整体状态,参数是原始 AST 和定义的转换规则,返回结果是转换后的 AST
  3. 生成
    1. 把最终的 AST 转换成字符串形式 的代码,同时会创建源码映射
    2. 深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串
    3. 使用 generator 将修改后的 AST 转换成代码

http 网络层前端性能优化

利用缓存

  1. 对于静态资源文件实现强缓存和协商缓存
    1. 文件有更新,如何保证及时刷新?
      • 文件后添加时间戳
  2. 对于不经常更新的接口数据采用本地存储做数据缓存
    1. cookie / localStorage / vuex|redux 区别?
      1. cookie文件小,需要服务端参与
      2. localStorage永久保存
      3. vuex|redux页面关闭缓存消失

DNS优化

  1. 分服务器部署,增加HTTP并发性
  2. DNS Prefetch

TCP的三次握手和四次挥手

  1. Connection:keep-alive

数据传输

  1. 减少数据传输的大小
    1. 内容或者数据压缩(webpack等)
    2. 服务器端一定要开启GZIP压缩(一般能压缩60%左右)
    3. 大批量数据分批次请求(例如:下拉刷新或者分页,保证首次加载请求数据少)
  2. 减少HTTP请求的次数
    1. 资源文件合并处理
    2. 字体图标
    3. 雪碧图 CSS-Sprit
    4. 图片的BASE64

CDN服务器“地域分布式”

采用HTTP2.0

网络安全相关XSS和SCRF

怎么在一个域名下把很多跨平台项目整合起来

webpack 新版本

loader和plugin的区别

遇到的问题

  1. 之前做混合 app。在子页 input focus 在输入东西 直接点的前端写的左侧返回按钮 安卓机的键盘手不起来 我们是让安卓写了一个方法 我们子页面销毁的时候 调取一下 就把键盘收起来了
  2. 裁剪图片 blob 上传问题,裁剪完成按要求尺寸生成的土片转为 bolb 对象上传 服务端查询不到 filename,查原因 blob 不能指定 filename 所以转给 File 对象即可设置 filename
  3. mouesedown mouseup 比 click 先执行