2022.3.1记录前端面试题

325 阅读32分钟

一、html

1.你对盒子模型理解多少?

a.什么是盒模型? css基础框盒模型是CSS规范的一个模块,它定义了一种长方形的盒子
b.盒模型的分类:盒模型从标准定义上分为标准盒模型和替代(IE)盒模型,从元素类型上分为块级盒子和内联盒子
c.盒模型的切换:
1.通过box-sizing属性进行切换;
2.ie盒模型:box-sizing:border-box;定义width,height代表元素边框盒(包含border,padding,content)的宽和高
3.标准盒模型:box-sizing: content-box;定义width,height代表content的宽和高
d.盒模型属性设置
margin和paddin值的设置;
1个值的情况:如10px4个方向都为10px;
2个值的情况:如10px 20px 上下10px 左右20px
3个值的情况:如10px 20px 30px10px左右20px30px
4个值的情况:如10px 20px 30px 40px10px20px30px40px(顺时针)
border值:border:1px solid red 三个值分别代表border-width, border-style,
border-color可设置一个或多个属性的值

2.谈谈你对BFC的理解?

a.BEF:是Block Formatting Context,块格式化上下文的缩写,简单的说BFC是一个完全独立的空间,
      这个空间里子元素的渲染不会影响到外面的布局。
b.触发BFC的条件: display: table-cell, display: flex, display: inline-block, 
overflow: hidden, position: absolute, position: fixed
c.BFC解决的问题: 1.垂直方向margin重叠的问题 2.使用float脱离文档流,父元素高度塌陷问题

3.谈谈你对css选择器的理解?

a.css选择器:通俗的讲CSS选择器用来对选定的页面元素进行样式修改。
b.选择器(11种):标签选择器h1{},通配选择器*{},类选择器.box{},ID选择器#box{},
              标签属性选择器a[title]{},伪类选择器p:first-child{}
              伪元素选择器p::first-line{},后代选择器.box p,子代选择器.box > p, 
              相邻兄弟选择器 h1+p,通用兄弟选择器 h1~p

4.谈谈你对css选择器优先级的理解?

a.css选择器优先级是基于不同种类选择器组成的匹配规则
b.css选择器的优先级:!important>行内样式>ID选择器>类,伪类,属性选择器>
                  标签,伪元素选择器>通配符,子类选择器,兄弟选择器

5.css实现三栏布局有哪些方式?

1float布局是现在用的比较多的布局很多门户网站目前使用这个布局方式,使用的时候只需要注意一定要清除浮动。
2Position布局只是根据定位属性去直接设置元素位置,个人感觉不太适合用做页面布局
3table布局使用起来方便,兼容性也不存在问题,不利于搜索引擎抓取信息
4flex布局比较强大,但是还是存在IE上兼容性问题,只能支持到IE9以上
5grid布局很强大,但是兼容性很差。

6.css中哪些属性是可以继承的?

a.字体font,font-family,font-size,font-style,font-variant,font-weight
b.字母间距 letter-spacing
c.可见性 visibility
d.文字展示 line-height, text-align, text-indent, text-transform,
f.字间距 word-spacing

7.如何区分px/em/rem/vw/vh?

px:就是pixel像素的缩写,可以简单理解为网页开发的基本长度单位
em: em是一个相对长度单位,相对于当前元素内文本的字体尺寸
rem: rem是CSS3新增的一个相对单位,基于html元素的字体大小来决定,通常配合媒体查询用于解决移动端适配问题
vw和vh:vw和vh是相对于视口的长度单位,1vw即值为视口宽度的1%,1vh意味着值为视口高度的1%
a.px:相对单位,网页开发基本长度单位
b.em:相对单位,相对当前盒子字体大小进行计算
c.rem:相对单位,相对根元素html字体大小进行计算
d.vw+vh:相对单位,相对当前网页视口宽度和高度进行计算

8.如何实现左边定宽右边自适应?

a.非严格意义方案:1.float+calc 2.inline-block-calc 3.position+padding
b.严格意义方案: 1.flex布局 2.table布局 3.grid布局

9.如何实现绝对集中?

a.定宽高 1.绝对定位+负margin2.绝对定位+margin auto 
b.不定宽高 1.table-cell 2.flex布局 3.绝对定位+transform

10.清除浮动有哪些方法,各有什么优缺点?

a.父元素固定宽高 
1.优点:简单,代码量少,没有兼容问题 
2.缺点:内部元素高度不确定的情况下无法使用
b.添加新元素
1.优点:简单,代码量少,没有兼容问题 
2.缺点:需要添加无语义的html元素,代码不够优雅,不便于后期的维护
c.使用伪元素
1.优点:仅用css实现,不容易出现怪问题 
2.缺点:仅支持IE8以上和非IE浏览器
d.触发父元素BEC 
1.优点:仅用css实现,代码少,浏览器支持好
2.缺点:用overflow:hidden触发BFC的情况下,可能会使内部本应正常显示的元素被裁剪

11.如何用css画一个三角形

border: 10px solid transparent; border-left: 10px solid red;

12.如果要做优化,css提高性能的方法有哪些?

a.属性设置使用简写 目的:减少生产包体积
b.用CSS替换图片  目的:减少http请求节约带宽
c.删除不必要的零和单位  目的:减少生产包体积
d.用css精灵图替代单个文件加载 目的:减少http请求节约带宽

13.介绍下最新的布局方式

1.普通/文档流 布局
2.Float 布局 float: left/right
3.绝对布局 position: absolute,position: fixed
4.Grid 网格布局: display:grid
5.css3 Flex 弹性盒子布局: display:flex
6.响应式布局(BootStrap)

14.Transform,Transition,Animation三个区别

1.transform:描述了元素的静态样式,本身不会呈现动画效果,可以对元素进行 旋转rotate、扭曲skew、缩放scale和移动translate以及矩阵变形
div { transform:scale(0.5) }
2.transition样式过渡,从一种效果逐渐改变为另一种效果(从左到右分别是:css属性、过渡效果花费时间、速度曲线、过渡开始的延迟时间)
div { width: 100px; height: 100px; transition: transform 2s} div:hover{ transform: rotate(180deg)}
transition通常和hover等事件配合使用,需要由事件来触发过渡
3.animation动画 由@keyframes来描述每一帧的样式
div{ animation:myAnimation 5s infinite }
@keyframes myAnimation { 0%{left:0;transform:rotate(0);} 100%{left:200px;transform:rotate(180deg);}}
区别:
1.transform仅描述元素的静态样式,常常配合transition和animation使用
2.transition通常和hover等事件配合使用,animation是自发的,立即播放
3.animation可设置循环次数
4.animation可设置每一帧的样式和时间,transition只能设置头尾
5.transition可与js配合使用,js设定要变化的样式,transition负责动画效果

15.伪类与伪元素的特性及其区别

1.伪类本质上是为了弥补常规CSS选择器的不足,以便获取到更多信息;
2.伪元素本质上是创建了一个有内容的虚拟容器;
3.CSS3中伪类和伪元素的语法不同;
4.可以同时使用多个伪类,而只能同时使用一个伪元素

二、javascript

1.介绍下原型和原型链

a.原型:在javascript中,函数可以有属性。每个函数都有一个特殊的属性叫作原型(prototype)
b.原型链:就是当我们访问对象的某个属性或方法时,如果在当时对象中找不到定义,会继续在当前
对象的原型对象中查找,如果原型对象中依然没有找到,会继续在原型对象的原型中查找(原型也
是对象,也有自己的原型)如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,
就结束查找,返回undefined。可以看出,这个查找过程是一个链式的查找,每个对象都有一个
到它自身原型对象的链接,这些链接组件的整个链条就是原型链。
c.原型和原型链存在的意义:使得是咧对象可以共享构造函数原型属性和方法,
节省内存。构造函数原型上的属性和方法越多,节省内存越大

prototype
函数有原型,函数有一个属性叫prototype,函数的这个原型指向一个对象,这个对象叫原型对象。
这个原型对象有一个constructor属性,指向这个函数本身。
一个实例化对象,没有prototype属性。
function fn(){
    console.log(1);
}
console.log(fn.prototype.constructor == fn)
//  true

__proto__
JavaScript中,万物皆对象,任何东西都有__proto__属性。这个叫隐式原型。
第一种情况:
var obj = {
    name:'chenlin'
}
obj.__proto__ == Object.prototype;
// true

第二种情况:
 function Person(){}
 var person1 = new Person();
 console.log(person1.__proto__==Person.prototype);
 // true
 
 第三种情况:
 function Foo() {

};
console.log(Foo.__proto__==Function.prototype);
//  true

第四种情况:
console.log(String.prototype.__proto__==Object.prototype);
//  true


如何判断一个属性存在于实例中,还是存在于原型中?
1.原生js没提供直接判断属性是否存在于原型对象的方法,但同时使用hasOwnProperty() 和in操作符,
能实现这个功能!
obj.hasOwnProperty(attribute):可以检测一个属性是否存在于一个实例对象中,还是存在于
原型对象中,若存在于实例对象中,则返回true,示例:
function Person() {
    // this.name="overwrite"; //构造函数中重写属性
}
Person.prototype = {
    name: "Nike",
    sayName: function () {
        console.log(this.name);
    }
}
var person1 = new Person();
// person1.name="overwrite";//实例中重写属性
console.log(person1.hasOwnProperty("name"));

2.介绍下闭包

1.闭包:能够访问其他函数内部变量的函数
2.场景:
a.事件函数的封装
b.用闭包模拟私有方法
c.在循环中给页面元素绑定事件响应函数
3.存在什么问题:闭包本身会造成内部变量常驻内存

3.bind和call和apply的区别?

a.bind,call,apply都是用来改变this指向的
b.相同点 
  1.bind,call,apply都可以改变this指向
c.不同点 
  1.call,apply的传参方式不同 2.bind,call,apply的返回值不同
d.使用场景: 
  1.call 判断数据类型,类数据转数组 2.apply 获取数组最大值 3.bind react类组件事件响应函数的绑定
e.代码案列
const Ten = {
      name: 'xiaolin',
      company: 'xiaolin',
      time:'2022-2-19',
      address:'shenzhen',
      say(company) {
          console.log(`my name is`, this.name);
          console.log(`my company is`, company)
      }
}
const Al = {
    name: 'tom',
    time: '2022-1-19',
    address: 'shenzhen',
    say(company) {
          console.log(`my name is`, this.name);
          console.log(`my company is`, company)
      }
}
Ten.say('Ten')
Ten.say.call(Al, 'Al')
Ten.say.apply(Al, ['Al'])
const res = Ten.say.bind(Al, 'Al')
res()

f.call 判断数据类型,类数据转数组
const array = [1,2,3,4]
const type = Object.prototype.toString.call(array)
console.log(type, 'type 1')
const arrayLike = {
    0:'name',
    1:'age',
    2:'gender',
    length: 3
}
const res = Array.prototype.slice.call(arrayLike)
console.log(res, 'res 2')
g.apply 获取数组最大值/最小值
const array = [1,2,3,4,5]
const max = Math.max.apply(null, array);
const min = Math.min.apply(null, array);
console.log(max,min, 'max 1')
h.bind react类组件事件响应函数的绑定
class App extends React.Component {
    constructor(props) {
        super(props);
        /* 初始化阶段 */
        this.state = {
            name: 'xiaolin',
        };
        this.handle = this.handle.bind(this); // 这里绑定
    }
    handle() {
        console.log(this.name);

    }
    render() {
        return <div>
            <button onClick={this.handle}>点击</button>
        </div>
    }
};

4.如何理解作用域和作用域链?

1.作用域是在运行代码中的某些特定部分中变量,函数和对象的可访问性,作用域决定了代码区块中变量和其他资源的可见性
2.作用域存在的最大意义就是变量隔离即:不同作用域下同名变量不会有冲突
3.作用域链:当我们在某个函数的内部作用域中查找某个变量时,如果没有找到就会到他的父级作用域中查找,
如果父级也没找到就会接着一层一层的向上寻找,直到找到全局作用域还是没找到的话,就宣布放弃。
这种一层一层的作用域嵌套关系

5.多种方式实现数组去重?

一、普通数组去重 
   1.indexOf方法去重 2.相临元素排序 3.set与解构赋值去重 4.set与Array.from去重
二、对象数组去重 
   1.临时对象缓存数组项key值 2.reduce方法+缓存对象
a.indexOf 查找项的下标 -1
b.filter []
c.sort []
d.reduce []
e.push length
const array = [1,2,3,4,5,1,2,3]

1.filter indexOf
function unique(array){
    if(!Array.isArray(array)) {
        return false
    }
    return array.filter((item, index) => {
        return array.indexOf(item) === index
    })
}
console.log(unique(array), '222')

2.相邻元素排序
function unique(array) {
    if(!Array.isArray(array)) {
        return false
    }
    array = array.sort()
    let res = []
    for (let i = 0;i < array.length; i++) {
        if(array[i] !== array[i-1]){
            res.push(array[i])
        }
    }
    return res
}

3.Set解构赋值
function unique() {
    if(!Array.isArray(array)) {
        return false
    }
    return [...new Set(array)]
}

4.set Array.from
function unique() {
    if(!Array.isArray(array)) {
        return false
    }
    return Array.from(new Set(array))
}
console.log(unique(array), '222312')
const array = [{name: 'xiaolin', age:20},{ name:'tom', age:20}]

1.临时对象缓存数组项key值
function unique(array,key) {
    if(!Array.isArray(array)) {
        return false
    }
    let result = []
    let template = {}
    for (let i = 0; i < array.length; i++) {
        var keyName = array[i][key]
        if (template[keyName]) {
            continue
        }
        template[keyName] = true
        result.push(array[i])
    }
    return result
}

2.reduce
function unique(array, key) {
    if(!Array.isArray(array)) {
        return false
    }
    var obj = {}
    return array.reduce((prve, current) => {
        obj[current[key]]?"":obj[current[key]]=true&&prve.push(current)
        return prve
    },[])
}
  console.log(unique(array, 'age'), '2223121')

6.给定数组求最大值?

const array = [1,2,3,7,8,9]
1.Math.max
const res = Math.max(...array)
2.Math.max.apply
const res = Math.max.apply(null, array)
3.reduce函数
function getMax(array) {
    return array.reduce((prev, current) => {
        return current > prev ? current : prev
    })
}
4.sort
function getMax(array) {
    const result = array.sort();
    return result[result.length-1]
}
console.log(getMax(array), 'res1')

7.js中判断类型的方式有哪些?

一、数据类型
a.基本数据类型StringNumberBooleanSymbolundefinedNull
console.log(typeof '111') // 'string'
console.log(typeof 111) // 'number'
console.log(typeof true) // 'boolean'
console.log(typeof Symbol('symbol')) // 'symbol'
console.log(typeof undefined) // 'undefined'
console.log(typeof null) // 'object'  js计算机bug

console.log(typeof([])) // 'object'
console.log(typeof(new Date())) // 'object'
console.log(typeof({})) // 'object'

console.log(typeof(function(){})) // 'function'
console.log(typeof(Array)) // 'function'
// 注意
Object[does not implement[[Call]]] // 'object'
Object[implements[[Call]]] // 'function'

// 面试题01
const str = 'xiaolin'
console.log(str) // 'xiaolin'
console.log(typeof(str)) // 'string'

const str1 = new String('xiaolin') // 实例化后的对象
console.log(str1) // String {'xiaolin'}
console.log(typeof(str1)) // 'object'


b.引用数据类型ObjectArrayFunctionDateFormDataSetMap等等
// instanceof检测 boolean true false A instanceof B
console.log([] instanceof Array) // true
console.log({} instanceof Object) // true
console.log(new Date() instanceof Date) // true
function Person() {}
console.log(new Person() instanceof Person) // true

// instanceof 原型链 A instanceof B, B instanceof C
console.log([] instanceof Object) // true
console.log(new Date() instanceof Object) // true
console.log(new Person instanceof Object) // true

二、判断数据类型的方式
a.typeof 
优点:使用简单 
缺点:功能残缺,只能用来判断6种数据类型(StringNumberBooleanSymbolundefinedNull)
b.Object.prototype.toString.call 
console.log(Object.prototype.toString.call('111')) // [object String]
console.log(Object.prototype.toString.call([])) // [object Array]
优点:适用于判断所有数据类型 
缺点:使用上相对typeof而言比较繁琐

c.instanceof(排除):instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上

8.实现函数防抖

一、函数防抖:事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时
二、场景:搜索框(电商网站商品搜索,后台管理系统数据查询)
const search = document.getElementById('search')
const debounce = (fn, inital) => {
    let timer = null
    return () => {
        clearTimeout(timer)
        timer = setTimeout(fn, inital)
    }
}
search.oninput = debounce(function(){
    console.log('xiaolin')
}, 2000)

9.实现函数节流

一、函数节流:规定在一个单位时间内,事件响应函数只能被触发一次。如果这个单位时间内触发多少函数,只有一次生效
二、场景:a.window.onresize事件 b.mousemove事件
function throttle(fn, interval) {
    var timer;
    return (event) => {
        if (timer) {
            return false
        }
        timer = setTimeout(() => {
            clearTimeout(timer)
            timer = null
            fn(event)
        }, interval)
    }
}
window.onresize = throttle(function(event) {
    console.log(1)
}, 2000)

10.多种方式实现数组拍平

一、数组拍平:也叫数组扁平化、数组拉平、数组降维。指的是把多维数组变成一维数组
二、如何实现数组拍平
a.reduce函数
b.es6自带的flat函数
c.用while循环加扩展运算符
const array = [1,2,3,4,[5,6,[7,8]]]
reduce函数
function flatten(arr) {
   return arr.reduce(function(prev, current) {
       return prev.concat(Array.isArray(current)?flatten(current):current)
   },[])
}
es6自带的flat函数
function flatten(arr){
    return arr.flat(Infinity)
}
用while循环加扩展运算符
function flatten(arr) {
    while(arr.some(Array.isArray)) {
       arr = [].concat(...arr)
    }
    return arr
}
console.log(flatten(array), '1222')

11.使如下判断成立

let value = 0
Object.defineProperty(window, 'a', {
    get() {
        return value += 1
    }
})
if (a === 1 && a === 2 && a === 3){
    console.log('object')
}

12.如何实现new操作符

1.实例化对象 
2.返回值的问题:构造函数中如果有值返回,那实例化后的对象就是这个返回值
const TMap = function(options) {
    this.name = options.name
    this.address = options.address
    // return {
    //     name: 'xiaolin1',
    //     address:'shenzhen'
    // }
}
const map = new TMap({
    name: 'xiaolin',
    address:'shenzhen'
})
console.log(map, 'map')

const ObejectFactory = (...args) => {
    // 1.创建空对象
    const obj = {}
    // 2.获取构造函数
    const Constructor = [].shift.call(args)
    // 3.对象的__proto__指向Constructor.prototype
    obj.__proto__ = Constructor.prototype
    // 4.用apply的方式把构造函数Constructor的this指向obj执行Constructor
    const ret = Constructor.apply(obj,args)
    // 5.根据ret的执行结果判断返回构造函数的返回对象还是新创建的空对象
    return typeof ret === 'object' ? ret : obj
}
console.log(ObejectFactory(TMap, {name: '11', address: '22'}), '22')

13.如何实现一个bind函数

Function.prototype.bindFn = function() {
    // 1.获取源函数
    const fn = this
    // 2.获取目标对象
    const obj = arguments[0]
    // 3.获取源函数参数列表
    const args = [].slice.call(arguments, 1)
    // 4.返回函数
    return function() {
        // 5.获取返回函数的参数列表
        const returnArgs = [].slice.call(arguments)
        // 6.执行源函数
        fn.apply(obj, args.concat(returnArgs))
    }
}
原理
a.bind 函数改变this指向
b.bind 函数是Function.prototype上的方法
c.bind 函数的返回值也是函数
d.bind 函数调用之后返回的函数的参数同样也接收处理

14.如何实现call和apply函数

一.call原理
1.改变this指针
2.返回函数调用
3.数组方式传参
二.apply原理
1.改变this指针
2.返回函数调用
3.参数一个一个依次传递

Function.prototype.callFn = function(context, ...args) {
    args = args || [];
    // 给context新增一个Symbol属性以免覆盖原有属性
    const key = Symbol();
    context[key] = this
    // 通过隐式绑定的方式调用函数
    const result = context[key](...args)
    // 删除添加的属性
    delete context[key]
    // 返回函数调用的返回值
    return result
}

Function.prototype.applyFn = function(context, args) {
    args = args || [];
    // 给context新增一个Symbol属性以免覆盖原有属性
    const key = Symbol();
    context[key] = this
    // 通过隐式绑定的方式调用函数
    const result = context[key](...args)
    // 删除添加的属性
    delete context[key]
    // 返回函数调用的返回值
    return result
}

15.如何实现instanceof

instanceof原理
1.获取实例对象的隐式原型
2.获取构造函数的prototype属性
3.while 循环 -> 在原型链上不断向上查找
4.在原型链上不断查找 构造函数的显式原型
5.直到implicitPrototype = null 都没有找到,返回false
6.构造函数的prototype属性出现在实例对象的原型链上返回true

定义:instanceof运算符用于检测构造函数的prototype属性是否出现某个实例对象的原型链上
function instance_of(Obj, Constructor) {
    let implicitPrototype = Obj.__proto__ // 获取实例对象的隐式原型
    let displayPrototype = Constructor.prototype // 获取构造函数的prototype属性
    // while 循环 -> 在原型链上不断向上查找
    while(true) {
        // 直到implicitPrototype = null 都没有找到,返回false
        if (implicitPrototype === null) {
            return false
            // 构造函数的prototype属性出现在实例对象的原型链上返回true
        } else if (implicitPrototype === displayPrototype) {
            return true
        }
        // 在原型链上不断查找 构造函数的显式原型
        implicitPrototype = implicitPrototype.__proto__
    }
}

16.javascript数组的方法有哪些

一、javascript数组的方法有哪些
1.concat 连接2个或更多数组,并返回结果
const firstArray = [1,2,3,4,5];
const secondArray = [6,7,8,9];
const result = firstArray.concat(secondArray)

2.every 对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true
const array = [{name: 'freemen', age: 18},{name: 'vinko', age: 18}]
const result = array.every((item) => {
    return item.age === 18
})

3.filter 对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组
const array = [{name: 'freemen', age: 18},{name: 'vinko', age: 18}]
const result = array.filter((item) => {
    return item.name === 'freemen'
})

4.forEach 对数组中的每一项运行给定函数,这个方法没有返回值
const array = [{name: 'freemen', age: 18},{name: 'vinko', age: 18}]
array.forEach((item, index) => {
    console.log(item, index)
})

5.join 请所有的数组元素连接成一个字符串
const array = [1,2,3,4,5]
const result = array.join(';')
console.log(result)

6.indexOf 返回第一个与给定参数相等的数组元素的索引,没有找到则返回-1
const array = [1,2,3,4]
const result = array.indexOf(2)

7.lastIndexOf 返回在数组中搜索到的与给定参数相等的元素的索引里最大的值
const array = [1,2,3,3,3,3,4,5,6]
const result = array.lastIndexOf(3)

8.map 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
const array = [{name: 'freemen', age: 18},{name: 'vinko', age: 18}]
const result = array.map(item => {
    return item.name
})

9.reverse 颠倒数组中元素的顺序,原先第一个元素现在变成最后一个,同样原先的最后一个元素变成现在的第一个
const array = [1,2,3,4,5,6]
const result = array.reverse()

10.slice 传入索引值,将数组里对应索引范围内的元素作为新数组返回
const array = [1,2,3,4,5,6,7]
const result = array.slice(0, 3)

11.some 对数组中的每一项运行给定函数,如果任一项返回true,则返回true
const array = [1,2,3,4,5]
const result = array.some(item => {
    return item > 0
})

12.sort 按照字母顺序对数组排序,支持传入指定排序方法的函数作为参数
const array = [1,2,3,4,5,]
const result = array.sort((a, b) => {
    return b - a
})

13.toString 将数组作为字符串返回
const array = [1,2,3,4,5]
const result = array.toString()

14.如何去除数组中指定的元素
const array = [{name: 'freemen', age: 18},{name: 'vinko', age: 18}]
function cutArray(sourceArray, target) {
    sourceArray.forEach((item, index) => {
        if(item.name === target){
            sourceArray.splice(1, 1)
        }
    })
    return sourceArray
}
const result = cutArray(array, 'freemen')

17.为何0.1+0.2!==0.3

原因:小数可能无法用二进制准确表达 
解决方法:
1.math.js
2.parseFloat((0.1 + 0.2).toFixed(10))

备注:
parseFloat可解析一个小数点 
toFixed把数字转换为字符串,结果的小数点后有指定位数的数字        

18.this指向

es5 this指向分四种情况 
情况一:纯粹的函数调用
这是函数的最通常用法,属于全局性调用,因此this就代表全局对象。
var x = 1;
function test() {
   console.log(this.x);
}
test();  // 1

情况二:作为对象方法的调用
函数还可以作为某个对象的方法调用,这时this就指这个上级对象。
function test() {
  console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;

// 类似这种写法
 var obj = {
    x:"1",
    m: function() {
        console.log(this.x);
    }
}
console.log(obj.m())

obj.m(); // 1

情况三 作为构造函数调用
所谓构造函数,就是通过这个函数,可以生成一个新对象。这时,this就指这个新对象。
function test() {
  this.x = 1;
}

var obj = new test();
obj.x // 1

运行结果为1。为了表明这时this不是全局对象,我们对代码做一些改变:
var x = 2;
function test() {
  this.x = 1;
}

var obj = new test();
obj.x  // 1
运行结果为1,表明全局变量x的值根本没变。

情况四 apply,call,bind 调用 
apply
var a = {
    name : "Cherry",
    func1: function () {
        console.log(this.name)
    },
    func2: function () {
        setTimeout(  function () {
            this.func1()
        }.apply(a),100);
    }
};

a.func2()            // Cherry

call
var a = {
    name : "Cherry",
    func1: function () {
        console.log(this.name)
    },
    func2: function () {
        setTimeout(function () {
            this.func1()
        }.call(a),100);
    }
};

a.func2()            // Cherry


bind
var a = {
    name : "Cherry",
    func1: function () {
        console.log(this.name)
    },
    func2: function () {
        setTimeout(  function () {
            this.func1()
        }.bind(a)(),100);
    }
};

a.func2()            // Cherry

箭头函数和普通函数区别
* 没有 this,不能改变 this 绑定
* 不能通过 new 调用,当然也没有原型(prototype)
* 没有 arguments 对象,不能有相同命名参数
* 箭头函数虽然没有 this ,但是还是可以在内部使用 this 的
* this 的绑定取决于定义函数时的上下文环境
* 一旦函数调用,任何改变 this 的方法都无效

练习题
let x = 11111
let a = {
    x: 1,
    init() {
        // 箭头函数的 this 取决于 init,所以可以打印出 1
        document.addEventListener('click', () => console.log(this.x))
    },
    allowInit: () => {
        // allowInit 直接是个箭头函数,所以这时的 this 变成了 window
        // 但是并不会打印出 11111,使用 let 时变量也会被提升至块级作用域的顶部,但是只提升声明,不提升初始化
        console.log(this.x)
    },
    otherInit() {
        // 普通函数的 this 取决于调用函数的位置,this 指向 document
        // 如果想打印出 x,可以使用 bind
        document.addEventListener('click', function() {
            console.log(this.x)
        })
    }
}
a.init() // -> 1
a.allowInit() // -> undefined
a.otherInit() // -> undefined

19.[]运算符

1.如果是对象,就通过 `toPrimitive` 转换对象
2.如果是字符串,就通过 `unicode` 字符索引来比较

// [] 转成 true,然后取反变成 false
[] == false
// 根据第 8 条得出
[] == ToNumber(false)
[] == 0
// 根据第 10 条得出
ToPrimitive([]) == 0
// [].toString() -> ''
'' == 0
// 根据第 6 条得出
0 == 0 // -> true

20.深浅拷贝浅拷贝

问题:如果给一个变量赋值一个对象,那么两者的值会是同一个引用,其中一方改变,另一方也会相应改变
let a = { age: 1 }
let b = a
a.age = 2
console.log(b.age) // 2

浅拷贝
1.Object.assign
let a = { age: 1 }
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
2.展开运算符(…)来解决
let a = { age: 1 }
let b = { ...a }
a.age = 2
console.log(b.age)
浅拷贝缺点:
只解决了第一层的问题,如果接下去的值中还有对象的话,
那么就又回到刚开始的话题了,两者享有相同的引用。
要解决这个问题,我们需要引入深拷贝。
let a = {
    age: 1,
    jobs: {
        first: 'FE'
    }
}
let b = { ...a }
a.jobs.first = 'native'
console.log(b.jobs.first) // native


深拷贝
1.JSON.parse(JSON.stringify(object))
let a = {
    age: 1,
    jobs: {
        first: 'FE'
    }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
有局限性的:
1.会忽略 undefined
2.会忽略 symbol
3.不能序列化函数
4.不能解决循环引用的对象
let obj = {
    a: 1,
    b: {
        c: 2,
        d: 3,
    },
}
obj.c = obj.b
obj.e = obj.a
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj) // Converting circular structure to JSON
2.深拷贝函数
function deeCopy(obj){
    var result = Array.isArray(obj) ? [] : {};
    for (var key in obj) {
        if(obj.hasOwnProperty(key)) {
            if(typeof obj[key] === "object") {
                result[key] = deeCopy(obj[key]);
            } else {
                result[key] = obj[key];
            }
        }
    } 
    return result;
}
console.log(deeCopy(obj))

三、ES6

1.你知道let const 和var的区别吗?

a.不存在变量提升
b.块级作用域
c.暂时性死区
d.不可重复声明
e.不会挂在window对象下面
f.const声明之后必须马上赋值,否则会报错
g.const声明的简单类型不可更改,复杂类型内部数据可以更改 

2.你了解箭头函数和普通函数的区别吗?

a.箭头函数this指向父级作用域的this
b.call(),apply(),bind()无法改变箭头函数中this的指向
c.不可以被当作构造函数
d.不可以使用arguments对象
e.箭头函数不支持new.target

3.你了解forEach、for in、for of三者区别吗?

一.forEach是数组的方法
1.便利的时候更加简洁,效率和for循环相同,不用关系集合下标的问题,减少了出错的概率
2.没有返回值
3.不能使用break中断循环,不能使用return返回到外层函数
二.for in用于循环遍历数组或对象属性(大部分用于对象) 对象遍历key, 数组和复杂数组遍历是索引
三.for of一个数据结构只有部署了Symbol.iterator属性,就被视为具有iterator接口,
   就可以用for of循环遍历它的成员,数组遍历是值复杂对象遍历是当前对象值
   可用for of遍历的成员包括:数组,Set和Map结构,类数组对象 但是不能遍历对象,因为没有迭代器对象

4.es6中哪个方法可以实现数组去重?

a.Array.from(new Set())

5.你对es6中对象新增的方法有了解吗?

a.Object.is() // 用于判断两个值是否全相等 
 1.不仅可以对值类型进行正常处理而且对象类型的值也可以判断
 2.对于特殊的值NaN也可以进行正常的处理
b.Object.assign() // 用于合并对象
c.Object.keys() // 目标对象的所有可遍历属性的键名
d.Object.values() // 目标对象的所有可遍历属性的键值
e.Object.entries() // 目标对象的所有可遍历属性的键值对数组

6.你对class和function的区别了解多少?

a.相同点:都可以用作构造函数
b.不同点:class不可以使用call apply bind的方式来改变他的执行上下文

7.你对Promise了解多少?

1.定义:Promise是异步编程的一种解决方案,比传统的解决方案--回调函数和事件--更合理和更强大。
2.Promise

8.你知道扩展运算符...的实现原理吗?

9.设计一个对象,键名的类型只是包含一个symbol类型,并且实现遍历所以key

四、vue

1.说一下生命周期

1.创建阶段
beforeCreate 实例刚在内存中创建出来,还没有初始化data和methods
created 实例已经在内存中创建完成,此时data和methods已经创建完成
beforeMount 此时已经完成模板的编译,只是还没有渲染到界面中去
mounted 模板已经渲染到了浏览器,创建阶段结束,即将进入运行阶段

2.运行阶段
beforeUpdate 数据更新时调用,这里适合在更新之前访问现有的DOM
updated 页面重新渲染完毕,页面中的数据和data保存一致

3.销毁阶段
beforeDestroy 实例销毁之前调用,在这一步,实例仍然完全可用
destroyed 实例销毁后调用,该钩子被调用后,对应vue实例的所有指令都被解绑,
所有的事件监听器被移除,所有的子实例也都被销毁

WeChatb757f9bef8cf0db9d87f41b4b424181f.png

2.vue中在哪个生命周期内调用异步请求

1.created,beforeMount,mounted 
2.推荐在created钩子函数中调用异步请求
a.相对beforeMount、mounted能更快获取到服务端数据,减少页面loading时间
b.ssr不支持beforeMount,mounted钩子函数,所以放在created中有助于一致性

3.vue父子组件生命周期调用顺序

1.加载渲染过程
  父beforeCreate->父created->父beforeMount->
  子beforeCreate->子created->子beforeMount->子mouted->父mouted
2.子组件更新过程
  父beforeUpdate->子beforeUpdate->子updated->父updated
3.父组件更新过程
  父beforeUpdate->父updated
4.销毁过程
  父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

4.keep-alive 组件的了解

a.是什么:缓存组件 
b.场景:性能优化
c.初次进入时:created > mounted > activated;退出后触发deactivated
  再次进入:会触发activated;事件挂载的方法等,只执行一次的放在mounted中去执行的方法放在activated中
d.原理:在 created 钩子函数调用时将需要缓存的 VNode 节点保存在 this.cache 中/在 render(页面渲
染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 this.cache 
中取出之前缓存的 VNode实例进行渲染。
f.参数(Props)
   1. include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
   2. exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
   3. max - 数字。最多可以缓存多少组件实例

5.v-ifv-show 区别

共同点:v-if和v-show都能实现元素的显示隐藏
区别:
1.v-show只是简单的控制元素的display属性而v-if才是条件渲染(条件为真,元素将会被渲染,条件为假,元素会被销毁
2.v-show有更高的首次渲染开销,而v-if的首次渲染开销要小的多
3.v-if有更高的切换开销v-show切换开销小
4.v-if有配套的v-else-if和v-else而v-show没有
5.v-if可以搭配template使用,而v-show不行

6. v-model 的实现原理

v-model 呢其实是
<input type="text" :value="testMessage" @input="testMessage = $event.target.value">
的语法糖,它实际上做了两件事情:
1,绑定数据(在 <input type=“text” :value=“testMessage” )
2,触发input的输入事件(也就是监听) 
(<input type=“text” :value=“testMessage” @input=“testMessage = $event.target.value”>)
<template>
    <div class="test-model-essure">
        <p>这里是input输入框输入时响应的文本</p>
        <p>{{ testMessage }}</p>
        <input
        type="text"
        placeholder="这是input输入框"
        :value="testMessage"
        @input="emitInput"
        />
    </div>
</template>
<script>
export default {
    data() {
        return {
          testMessage: "",
        };
    },
    methods: {
        emitInput($event) {
          this.testMessage = $event.target.value;
        },
    },
};
</script>

6.vuex是什么?怎么使用

vuex是一个专为vue.js应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的
规则保证状态以一种可预测的方式发生变化
state:用于保存我们应用的状态 
mapState: 处理顶层状态到组件的映射关系
Getters: 对状态进行公共的处理
mapGetters: 处理Getter到组件的映射关系
mutations: 业务组件通过$store.commit同步修改state 
Actions: 子组件中通过dispatch action异步修改应用状态
mapActions: 简化action的调用
Modules: 对store进行模板化管理

7.说一下使用jQuery和使用框架的区别

1.数据和视图的分离,解耦
2.以数据驱动视图,只关心数据变化,DOM操作被封装

8.说一下对MVVM的理解

1.MVC View视图、界面 Model数据 Controller控制器,处理逻辑
2.MVVM Model数据 View视图 ViewModel

9.vue的整个实现流程

1.解析模板成render函数
    a.with的用法
    b.模板中的所有信息都被render函数包含
    c.模板中用到的data中的属性,都变成了JS变量
    d.模板中的v-model v-for v-on 都变成了JS逻辑
    f.render函数返回vnode
2.响应式开始监听
    a.Object.defineProperty
    b.将data的属性代理到vm上
3.首次渲染,显示页面,且绑定依赖
    a.初次渲染,执行updateComponen,执行vm._render()
    b.执行render函数,会访问到vm.list和vm.title
    c.会被响应式的get方法监听到
    d.执行updateComponen,会走到vdom的patch方法
    f.patch将vnode渲染成DOM,初次渲染完成
4.data属性变化,触发rerender
    a.修改属性,被响应式的set监听到
    b.set中执行updateComponent
    c.updateComponent重新执行vm._render()
    d.生成的vnode和prevVnode,通过patch进行对比
    d.渲染到html

10.为何要监听get,直接监听set不行吗?

1.data中有很多属性,有些被用到,有些可能不被用到
2.被用到的会走到get,不被用到的不会走到get
3.未走到get中的属性,set的时候我们也无需关系
4.避免不必要的重复渲染

11.vdom是什么?为何会存在vdom?

1.virtual dom,虚拟DOM
2.用JS模拟DOM结构
3.DOM变化的对比,放在JS层来做
4.提高重绘性能
5.DOM操作非常昂
<ul id="list">
    <li class="item">Item 1</li>
    <li class="item">Item 2</li>
</ul>

{
    tag: 'ul',
    attrs: {
        id: 'list'
    },
    children: [
        {
            tag: 'li',
            attrs: { className: 'item' },
            children: ['Item 1']
        },
        {
            tag: 'li',
            attrs: { className: 'item' },
            children: ['Item 2']
        }
    ]
}

12. v-ifv-for 哪个优先级高

1.显然v-for优先于v-if被解析;
2.如果同时出现,每次渲染都会先执行循环再判断条件,无论如何循环都不可避免,浪费了性能;
3.要避免出现这种情况,则在外层嵌套template,在这一层进行v-if判断,然后在内部进行v-for循环;

13.vue组件data为什么必须是个函数而vue的根实例则没有此限制

1.vue组件可能存在多个实例,如果使用对象形式定义data,则会导致它们共用一个data对象,那么状态
变更将会影响所有组件实例,这是不合理的;采用实例函数形式定义,在initData时会将其作为工厂函数
返回全新data对象,有效规避多实例之间状态污染问题。而在vue根实例创建过程中则不存在该限制,也是
因为根实例只能有一个,不需要担心这种情况。

14.vue中key的作用和原理

1.key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精确判断两个节点
是否是同一个,从而避免频繁更新不同元素,使用整个patch过程更加高效,减少DOM操作量,提高性能;
2.另外,如果不设置key还可能在列表更新时引发一些隐藏的bug;
3.vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了vue可以区分它们,
否则vue只会替换其内部属性而不会触发过渡效果;

15. vue中的diff算法

简述diff算法过程
1.patch(elem, vnode)和patch(vnode, newVnode)
2.patchVnode和addVnodes和removeVnodes
3.updateChildren(key的重要性)
4.vue 的diff算法是个深度优先算法

思路:
当组件创建和更新的时候, vue 会执行内部的 update 函数,该函数使用  render 函数生成虚拟的 dom 树,
找到差异点,最终更新到真实dom
将新旧对比差异的过程叫 diff, vue 在内部通过一个叫做 patch 的函数来完成该过程。

在对比的过程,vue 采用深度优先,同级比较的方式进行比较,同级比较就是说它不会跨越结构进行比较,
在判断两个节点是否相同的时候,是根据虚拟节点的 key 和 tag 来进行判断的。

具体来说,首先对根节点进行对比,如果相同则将旧节点关联的真实dom的引用挂到新节点上,然后根据需要
更新属性到真实dom,然后再对比其子节点数组;如果不相同,则按照新节点的信息递归创建所有真实dom,
同时挂到对应虚拟节点上,然后移除掉旧的dom。
在对比其子节点数组时,vue对每个子节点数组使用了两个指针,分别指向头尾,然后不断向中间靠拢来进行对比,
这样做的目的是尽量复用真实dom,尽量少的销毁和创建真实dom。如果发现相同,则进入和根节点一样的对比流程,
如果发现不同,则移动真实dom到合适的位置。
这样一直递归的遍历下去,直到整棵树完成对比。

Vue2 Vue3 React三者diff算法有何区别?
1.只比较同一层级,不跨级比较
2.tag不同则删除重建(不再去比较内部的细节)
3.子节点通过key区分
Vue2: 双端比较
Vue3:最长递增子序列
React:仅右移

16.vue组件化

1.组件是独立和可复用的代码组织单元,组件系统是vue核心特性之一,它使开发者使用小型、独立和通常
可复用的组件构建大型应用;
2.组件化开发能大幅提高应用开发效率、测试性、通用组件;
3.组件使用按分类有:页面组件、业务组件、通用组件;
4.vue的组件是基于配置的,我们通常编写的组件是组件配置而非组件,框架后续会生成其构造函数,它们
基于VueComponent,扩展与vue;
5.vue中常见组件化技术有:属性prop,自定义事件,插槽等,它们主要用于组件通信,扩展等;
6.合理的划分组件,有助于提升应用性能;
7.组件应该是高内聚,低耦合的;
8.遵循单向数据流的原则;

17.vue组件之间的通信

1.props
2.$emit/$on
3.vuex
4.$parent/$children
5.$attrs/$listeners
6.provide/inject
7.事件总线

18. vue优化

1.路由懒加载
2.keep-alive缓存页面
3.使用v-show复用DOM
4.v-for遍历避免同时使用v-if
5.如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容
6.事件的销毁(Vue组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件)
7.图片懒加载(对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先
不做加载,等到滚动到可视区域后再去加载)
8.第三方插件按需引入(element-ui)
9.无状态的组件标记为函数式组件
10.子组件分割

19. watchcomputed 的区别以及怎么选用

1.定义/语义区别
2.功能区别
a.watch更通用,computed派生功能都能实现,计算属性底层来自于watch,但做了更多,例如缓存
3.用法区别
a.computed更简单/更高效,优先使用,有些必须watch,比如值变化要和后端交互
4.使用场景
a.watch需要在数据变化时执行异步或开销较大的操作时使用,简单讲,当一条数据影响多条数据的时候,
例如搜索数据
b.computed对于任何复杂或一个数据属性在它所依赖的属性发生变化时,也要发生变化,简单讲,当一个
属性受多个属性影响的时候,例如购物车商品结算时

computed

实质是一个惰性的watcher,在取值操作时根据自身标记 dirty属性返回上一次计算结果/重新计算值 
在创建时就进行一次取值操作,收集依赖变动的对象/属性(将自身压入dep中) 在依赖的对象/属性变动时,
仅将自身标记dirty致为true

20.nextTick 的原理

定义:
1.在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
使用场景:
1.created()钩子函数进行的DOM操作一定要放在Vue.$nextTick()的回调函数中,因为vue生命周期created()
钩子函数执行时DOM其实并未进行任何渲染故此时进行DOM操作无异于徒劳。与之对应的是mounted钩子函数,
该钩子函数执行时所有的DOM挂载已经完成。
2.项目中想在改变DOM元素的数据后基于新的DOM做点什么时,对新的DOM一系列的js操作都要放进Vue.nextTick()
的回调函数中(即更改数据后当你想立即使用js操作新的视图的时候需要使用它,来实现DOM数据更新后延迟执行后续代码)
3.在使用某个第三方插件时,希望vue生成的某些DOM动态发生变化时重新应用该插件,也会用到该方法,
此时需在$nextTick的回调函数中执行重新应用插件的方法。
原理:
Vue是异步执行DOM更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环(event loop)
中观察到的数据变化的watcher推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。
这种缓冲行为可以有效去掉重复数据造成不必要的计算和DOM操作。而在下一个事件循环时,Vue会清空队列,并进行
必要的DOM更新。当你设置vm.somedata = 'new value'时,DOM并不会立马更新,而是在异步队列被清除,也就
是下一个事件循环开始时执行更新时才会进行必要的DOM更新。若此时你想要根据更新的DOM状态去做某些事情,就会
出现问题。为了在数据变化后等待Vue完成更新DOM,可在数据变化后立即使用Vue.nextTick(callback),这样回
调函数在DOM更新完成后就会调用。

21.什么是递归组件

组件是可以在它们自己的模板中调用自身的。递归组件,一定要有一个结束的条件,否则就会使组件循环引用,
最终出现的错误,我们可以使用v-if="false"作为递归组件的结束条件,当遇到v-if为false时,组件将
不会再进行渲染。

23.Vue中 Object.defineProperty() 缺点和 Proxy 优势

Object.defineProperty() 的问题主要有三个:
1.不能监听数组的变化
2.必须遍历对象的每个属性
3.必须深层遍历嵌套的对象

1.数据规模是否庞大。创建Vue实例的时候,一旦对象是一个深层的引用(老千层饼了),递归进行Observer的
创建显然会花很多时间;
2.对所有属性的变化进行监听,也需要消耗不小的内存;
3.新增/删除属性的时候,怎么调用/卸载defineProperty;
4.vue2的官方文档,对开发者说明了defineProperty的一些限制,比如说数组在两种情况下是无法监听的:
5.利用索引直接设置一个数组项时,例如:arr[indexOfItem] = newValue;
6.修改数组的长度时,例如:arr.length = newLength;
ProxyES2015 规范中被正式加入,它有以下几个优势点
1.针对对象:针对整个对象,而不是对象的某个属性,所以也就不需要对 keys 进行遍历。这解决了上述
Object.defineProperty() 第二个问题
2.支持数组:Proxy 不需要对数组的方法进行重载,省去了众多 hack,减少代码量等于减少了维护成本,而且
标准的就是最好的。
3.Proxy 的第二个参数可以有 13 种拦截方法,这比起 Object.defineProperty() 要更加丰富
4.Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,相比之下 Object.defineProperty() 是一个
已有的老方法,可以享受新版本红利

24. axios 简单的封装

axios简单的封装 唉是色哦思
一.创建axios实例
1.配置好baseUrl、headers信息、是否跨站点访问控制请求、时间、请求数据转换、相应数据转换
二.请求拦截器
1.让每个请求携带自定义token处理错误抛到业务代码
三.响应拦截器
1.接收接口返回参数状态根据参数状态来判断返回结果

// request.js
import axios from 'axios'

// 创建 axios 实例
let baseUrl = '/'
if(process.env.NODE_ENV === 'production'){
  baseUrl = 'localhost:5000'
}else if(process.env.NODE_ENV === 'development'){
  baseUrl = 'localhost:5000'
}
const service = axios.create({
  baseURL: baseUrl,
  headers: {
    get: {
      'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
    },
    post: {
      'Content-Type': 'application/json;charset=utf-8'
    }
  },
  // 是否跨站点访问控制请求
  withCredentials: false,
  timeout: 30000,
  //请求数据转换
  transformRequest: [(data) => {
    return JSON.stringify(data)
  }],
  //相应数据转换
  transformResponse: [(data) => {
    if (typeof data === 'string' && data.startsWith('{')) {
      data = JSON.parse(data)
    }
    return data
  }]
})

// 请求拦截器
service.interceptors.request.use((config) => {
  // 让每个请求携带自定义 token
   config.headers['Authorization'] = 'token'
  return config
}, (error) => {
  // 错误抛到业务代码
  error.data = {}
  error.data.msg = '服务器异常,请联系管理员!'
  return Promise.resolve(error)
})

// 响应拦截器
service.interceptors.response.use((response) => {
  const status = response.status
  let msg = ''
  if (status < 200 || status >= 300) {
    // 处理http错误,抛到业务代码
    msg = showStatus(status)
    if (typeof response.data === 'string') {
      response.data = {msg}
    } else {
      response.data.msg = msg
    }
    //拦截异常(通知)
    Notification({
      title: '提示',
      message: msg,
      type: 'error'
    })
  }
  return response
}, (error) => {
  // 错误抛到业务代码
  error.data = {}
  error.data.msg = '请求超时或服务器异常,请检查网络或联系管理员!'
  return Promise.resolve(error)
})

const showStatus = (status) => {
  let message = ''
  switch (status) {
    case 400:
      message = '请求错误(400)'
      break
    case 401:
      message = '未授权,请重新登录(401)'
      break
    case 403:
      message = '拒绝访问(403)'
      break
    case 404:
      message = '请求出错(404)'
      break
    case 408:
      message = '请求超时(408)'
      break
    case 500:
      message = '服务器错误(500)'
      break
    case 501:
      message = '服务未实现(501)'
      break
    case 502:
      message = '网络错误(502)'
      break
    case 503:
      message = '服务不可用(503)'
      break
    case 504:
      message = '网络超时(504)'
      break
    case 505:
      message = 'HTTP版本不受支持(505)'
      break
    default:
      message = `连接出错(${status})!`
  }
  return `${message},请检查网络或联系管理员!`
}

export {service as axios}

25.vue2响应式的原理

vue2响应式的原理它主要指的这个状态改变以后,你的视图要去自动去更新
vue是通过两个步骤来实现的第一步我叫数据劫持,数据劫持也叫做数据拦截
通过Object.defineProperty来把对象的每一个属性都把它转成这个setter,getter
在修改对应的属性的时候他就能去触发这个setter,这样就可以知道哪个属性被修改了然后
第二个是这个依赖收集就是我们在渲染视图的时候要将这个观察者(Watcher)和具体的属性把它结合起来
通过这个发布订阅的模式以后啊,将数据的改变就能够更加精准的去更新这个视图

五、react

1.react的生命周期你了解多少?

1.分阶段(挂载,更新,卸载)讲清楚react的生命周期
挂载:constructor, static getDerivedStateFromProps, render, componentDidMount
更新:static getDerivedStateFromProps, shouldComponentUpdate, 
render, getSnapshotBeforeUpdate, componentDidUpdate
卸载:componentWillUnmount
错误处理:static getDerivedStateFromError(), componentDidCatch

屏幕快照 2022-03-20 下午8.01.36.png

2.react组件之间如何实现通信

1.父组件向子组件通讯:props
2.子组件向父组件通讯:利用回调函数
3.兄弟组件通信:利用共同的父节点
4.跨层级通信:Context设计目的是为了共享那些对于一个组件树,而言是全局的数据,例如当前认证的用户、主题
或首选语言,对于跨越多层的全局数据通过Context通信再适合不过
5.全局状态管理工具:借助Redux等全局状态管理工具进行通信,这种工具会维护一个全局状态中心Store,并根据不同的事件产生新的状态

3.babel

先将ES6语法转为抽象语法树,再将抽象语法树,转为es5

六、浏览器

1.HTTPS详解及常见面试题

1.HTTP缺点:
无连接: HTTP/1.1之前每次请求都要通过TCP建立连接断开连接,HTTP/1.1实现了持久连接
无状态: Cookie技术和持久连接解决
明文传输: 不安全

HTTPS:简单讲是HTTP的安全版,在HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
2.HTTPS特点:
内容加密:采用混合加密技术,中间者无法直接查看明文内容
验证身份:通过证书认证客户端访问的是自己的服务器
保护数据完整性:防止传输的内容被中间人冒充或者篡改

1.png

加密算法 --- RSA(混合加密机制)
(1)共享密匙加密: 公开密匙加密解密,但是无法保证安全发送密匙
(2)公开密匙加密: 公开密匙加密,私有密匙解密

HTTPS:混合加密机制: 结合对称加密和非对称加密技术
用对称加密生成的密钥对通信数据加密
用非对称加密生成的公钥对密钥加密
这样在保证密钥的安全性下,提高了效率

认证 --- 防止"中间人"攻击,并验证服务器身份
证书: 认证机构(CA)信息,公钥,域名,有效期,指纹(对证书进行hash运算,即证书摘要),指纹算法,数字签名
(CA私钥加密的指纹)等

通信过程:
1.客户端发起 HTTPS 请求,服务端返回证书
2.客户端对证书进行验证,验证通过后本地生成用于改造对称加密算法的随机数。
3.通过证书中的公钥对随机数进行加密传输到服务端,服务端接收后通过私钥解密得到随机数,之后的数据
交互通过对称加密算法进行加解密。

验证过程:
1. 证书是否过期
2.CA是否可靠(查询信任的本地根证书)
3.证书是否被篡改(用户使用CA根公钥解密签名得到原始指纹,再对证书使用指纹算法得到新指纹,两指纹若不一样,则被篡改)
4.服务器域名和证书上的域名是否匹配
助于理解在知乎上down的HTTPS通信流程:

2.png

常见面试题 :
Q:HTTPHTTPS区别?
A: 1.HTTPS需要申请购买CA证书, HTTP不需要
   2.HTTP是明文传输,不安全, HTTPS是在HTTP基础上加了SSL层,更安全
   3.HTTPS效率低,HTTP效率高
Q:HTTPS传输过程?
A:客户端发起 HTTPS 请求,服务端返回证书,客户端对证书验证,验证通过后本地生成用于改造对称加密算法的
随机数,通过证书中的公钥对随机数进行加密传输到服务端,服务端接收后通过私钥解密得到随机数,之后的
数据交互通过对称加密算法进行加解密。

Q:为什么需要证书?
A:防止中间人攻击,验证服务器身份

Q:怎么防止的篡改?
A:证书是公开的,虽然中间人可以拿到证书,但私钥无法获取,公钥无法推断出私钥,所以篡改后不能用私钥加密,
强行加密客户也无法解密,强行修改内容,会导致证书内容与签名中的指纹不匹配

2.Event loop

微任务包括 `process.nextTick`,`promise`,`Object.observe`,`MutationObserver`
宏任务包括 `script`,`setTimeout`,`setInterval`,`setImmediate`,`I/O`,`UI rendering`

Event loop 顺序是这样的
1.执行同步代码,这属于宏任务
2.执行栈为空,查询是否有微任务需要执行
3.执行所有微任务
4.必要的话渲染 UI
5.然后开始下一轮 Event loop,执行宏任务中的异步代码

3.webpack常见性能优化

1.拆分大文件 splitChunksPlugin
2.对js和css进行哈希缓存 [name].[hash]
3.js,css,html压缩 plugins
4.小图片打包成64   loader:"url-loader",
5.抽离公共代码optimization common: { name: 'common', // chunk 名称 priority: 0, // 优先级 
minSize: 0,  // 公共模块的大小限制minChunks: 2  // 公共模块最少复用过几次}
6.排除依赖包,或者指定文件查找resolve.
7.tree-Shaking mode: 'production',
8.引入cdn externals

4.淘宝做法fiexible.js 设备像素比 = 物理像素 / 设备独立像素

1.js首先会获取设备型号,然后根据不同设备添加不同的data-dpr值,比如说12或者3,实现布局视口大小
2.动态计算htmlfont-size