面试汇总

131 阅读48分钟

1-- HTML

2-- CSS

1.scoped组件css作用域

默认情况下, 如果子组件和父组件有相同的选择器样式,优先加载父组件css样式 scoped : 1.属性作用: 让子组件加载自己的样式(添加组件css作用域) 2.属性原理: 给组件添加一个自定义属性 data-v-xxxx , 本质通过css属性选择器增加权重

2.浮动

为什么出现浮动 浮动定位将元素排除在普通流之外,即元素将脱离文档流,不占据空间。浮动元素碰到包含它的边框或者浮动元素的边框停留. 为什么需要清除浮动 1、父元素的高度无法被撑开,影响与父元素同级的元素; 2、与浮动元素同级的非浮动元素(内联元素)会跟随其后; 3、若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构 清除浮动的方式 1.给父级设置双伪元素 2.给父级元素设置 overflow:hidden;或 overflow:auto;本质是构建一个BFC

3.重绘和回流

重绘 : 当元素的一部分属性发生改变, 如外观, 背景, 颜色等不会引起布局的变化, 只需要游览器根据元素的新属性重新绘制 回流:元素的字体大小边距,页面的结构发生变化,就会引发回流 重绘不一定回流 , 回流一定重绘

4.em和rem的区别?

em是相对于元素自身字体大小的一个单位(可以继承) , 而rem是相对于根元素(html)字体大小的一个单位

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      html {
        font-size: 100px;
      }
      .box1 {
        font-size: 50px;
      }
      .box2 {
        font-size: 20px;
        /* em相对于元素自身字体大小的一个单位 em会继承父盒子字体大小*/
        width: 2em; /* 40px */
        height: 2em; /* 40px */
        background-color: green;
      }
      .box3 {
        /* rem相对于html根元素字体大小的一个单位 */
        width: 3rem; /* 300px */
        height: 3rem; /* 300px */
        background-color: orange;
      }
    </style>
  </head>
  <body>
    <div class="box1">
      <div class="box2"></div>
      <div class="box3"></div>
    </div>
  </body>
</html>

5.rem适配的原理是什么?

利用媒体查询或者js动态检测/获取设备的宽度 , 不同的宽度设置相应的根元素字体大小,当设备宽度变 =>根元素字体大小变 =>所有使用rem做单位的那些元素就跟着变了

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script>
        // 动态的获取屏幕的宽度除以10+px赋给根元素字体大小
      document.documentElement.style.fontSize =
        document.documentElement.clientWidth / 10 + "px"
    </script>
    <style>
      /* 清除所有边距 */
      * {
        margin: 0;
        padding: 0;
      }
      .box {
        /* 占屏幕尺寸的一半 */
        width: 5rem;
        height: 100px;
        background-color: green;
      }
    </style>
  </head>
  <body>
    <div class="box"></div>
  </body>
</html>

6.如何实现一个盒子水平垂直居中?

方法1 父元素设置display:flex ; 然后通过进行 justify-content: center;水平居中 , 和 align-items: center;垂直居中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            width: 500px;
            height: 500px;
            background-color: green;
            display: flex;
            justify-content: center;  /* 水平居中 */
            align-items: center;  /* 垂直居中 */
        } 麦包包菜                                                                                                          

        .inner {
            width: 100px;
            height: 100px;
            background-color: deeppink;
        }
    </style>
</head>
<body>
    <div class="box">
        <div class="inner"></div>
    </div>
</body>
</html>

方法2 父相子绝 父元素设置相对定位 position: relative , 子元素设置绝对定位 position: absolute

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .box {
        width: 500px;
        height: 500px;
        background-color: green;
        position: relative;
      }

      .inner {
        position: absolute;
        width: 100px;
        height: 100px;
        top: 50%;
        left: 50%;
        margin-top: -50px;
        margin-left: -50px;
        background-color: deeppink;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="inner"></div>
    </div>
  </body>
</html>

注意点:这儿的 margin 往往不要用百分比,因为 margin 使用百分比是相对于其父盒子的宽度的,CSS 百分比都是相对于谁

方法3 上面的方案 : margin进行位移的缺点是 : 需要明确知道自身元素的宽高 有时候在不知道盒子自身宽高的情况下推荐使用 transform: translate(-50%, -50%) 进行位移,因为 translate 的百分比是相对于盒子自身的.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .box {
        width: 500px;
        height: 500px;
        background-color: green;
        position: relative;
      }

      .inner {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 55%;
        height: 33%;
        background-color: deeppink;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="inner"></div>
    </div>
  </body>
</html>

7.说一说你对BFC的理解

1.BFC是什么 块级格式上下文, 是一种特性 , 拥有自己的一套渲染规则 , 它决定了其子元素将如何布局,以及和其他元素的相互关系和作用 2.如何触发BFC特性

  1. 根元素(html)
  2. 浮动元素 (元素的float不是none)
  3. 绝对定位元素 (元素的 position为 absolute 或 fixed )
  4. 行内块元素 (元素的display 为 inline - block)
  5. 表格单元格 ( 元素的 display 为 table-cell , HTML表格单元默认为该值)
  6. overflow不等于 visible

3.BFC 特性有什么作用 1.可以包含内部浮动元素 2.可以排除外部浮动带来的影响 3.阻止外边距重叠 4.解决margin塌陷

3-- JS

1.JavaScript 中什么情况下会返回 undefined

访问声明,但是没有初始化的变量 访问不存在的属性 访问函数的参数没有被显式的传递值 访问任何被设置为 undefined 值的变量 没有定义 return 的函数隐式返回 函数 return 没有显式的返回任何内容

2.数组扁平化

1.递归实现 : 创建一个空变量arr1接收 用for循环数组arr 用Array.isArray(arr[i])判断内层是否是函数 如果是 ,则用arr1.concatnewArr(arr[i]))合并两个数组给空变量arr 不是则直接 arr.push() 最后返回arr1

export const newArr = (arr) => {
  let arr1 = [];
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      arr1 = arr1.concat(newArr(arr[i]));
    } else {
      arr1.push(arr[i]);
    }
  }
  return arr1
};

2.reduce实现数组扁平化 用reduce 第一个参数是累加和 用a.concat()合并数组 判断 第二个参数当前遍历的元素是否是数组 是的话再次调用函数, 不是的话就是当前项的元素合并

// reduce实现数组扁平化
export const newArr1 = (arr) => {
  return arr.reduce((a,b) => {
    return a.concat(Array.isArray(b) ? newArr1(b) : b)
  },[])
};

3.flat实现多维数组扁平化

// Array.prototype.flat是es6新增的数组方法
export const newArr2 = (arr) => {
  return arr.flat(Infinity)
}

3.如何遍历对象的属性

**1.**Object.keys() 方法 - 遍历自身可枚举的属性(可枚举,非继承属性) **2.**Object.getOwnPropertyNames()方法 - 遍历自身的所有属性(可枚举,不可枚举,非继承属性) 3.for in 遍历对象的属性 - 遍历可枚举的自身属性和继承属性 (可枚举,可继承的属性

4.什么是伪数组,如何转化为真数组

伪数组 : 可以使用索引对数据进行操作;具有length(长度)属性;但是不能使用数组的方法,如push,pop. **如何转化为真数组 : ** 1.利用es6 提供的Array.from( )方法 2.遍历数组, 创建一个空数组,将遍历的数据逐一放在空数组中 3.常用 arr.push.apply(arr,伪数组) 4.拓展运算符 **5.**Array.prototype.slice.call() 6.arr.slice.call()

5.判断两个对象相等

1.判断两个对象相等,我们要判断他们两个对象的引用地址是否一致

let obj1={
  a:1
}
let obj2={
  a:1
}
console.log(Object.is(obj1, obj2)) // false
let obj3 = obj1
console.log(Object.is(obj1, obj3)) // true
console.log(Object.is(obj2, obj3)) // false

2.当需求是比较两个对象内容是否一致时就没用了 想要比较两个对象内容是否一致,思路是要遍历对象的所有键名和键值是否都一致 (1)判断两个对象是否指向一内存 (2)使用Object.getOwnPropertyNames获取对象所有键名数组 (3)判断两个对象的键名数组是否相等 (4)遍历键名,判断键值是否都相等

let obj1 = {a: 1,b: {c: 2}}
  let obj2 = {b: {c: 3},a: 1}
  function isObjectValueEqual(a, b) {
  // 判断两个对象是否指向同一内存,指向同一内存返回 true
  if (a === b) return true
  // 获取两个对象键值数组
  let aProps = Object.getOwnPropertyNames(a)
  let bProps = Object.getOwnPropertyNames(b)
  // 判断两个对象键值数组长度是否一致,不一致返回 false
  if (aProps.length !== bProps.length) return false
  // 遍历对象的键值
  for (let prop in a) {
  // 判断 a 的键值,在 b 中是否存在,不存在,返回 false
  if (b.hasOwnProperty(prop)) {
  // 判断 a 的键值是否为对象,是则递归,不是对象直接判断键值是否相等,不相等返回 false
  if (typeof a[prop] === 'object') {
  if (!isObjectValueEqual(a[prop], b[prop])) return false
  } else if (a[prop] !== b[prop]) {
  return false
  }
  } else {
  return false
  }
  }
  return true
  }
  console.log(isObjectValueEqual(obj1, obj2)) // false

6.this的指向

  • this : 谁调用我,我就指向谁
  • 普通函数   函数名()   : window
  • 对象方法 对象名.方法名() : 对象
  • 构造函数 new 函数名()  :  new创建的实例对象
  • 箭头函数 取决于外部父环境

7.JS中8大数据类型 ?

JS类型由原始值和对象组成 原始值 : Boolen ,
Null ,
Undefined , Number ,
String ,
BigInt(大数字) , 它的目的是比Number数据类型支持的范围更大的整数值 symbol(符号) 符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。 **对象 : **Object

8.forEch和map的区别

forEach没有返回值 , map有返回值 foreach会修改原数组 , map会得到一个新的数组并返回 foreach在并不打算改变数据的时候用 map适用于要改变数据值的时候

9.说一说对事件委托的理解

是什么 : 事件委托 , 就是把子元素绑定的事件 , 统一委托(绑定)到祖先元素身上 原理 : 事件冒泡 为什么要用事件委托(好处是什么?) 1.性能高 2.对后续新增的元素同样具有事件绑定的效果 如何按需触发 ? 自定义属性

10.说一下new的执行过程?

1.在内存中开辟一个空间 2.this指向这个内存空间 3.执行构造函数中的代码 4.返回this实例 在构造函数中 , 如果手动返回了简单数据类型, 会忽略 , new出来的结果还是实例

function Person(name, age) {
  this.name = name
  this.age = age
  // 如果手动返回了简单数据类型,会忽略
  return 'hello world'
}

const p1 = new Person('ifer', 18)

console.log(p1) // Person { name: 'ifer', age: 18 }

如果手动返回复杂数据类型 , 则new出来的结果就是这个返回的复杂数据类型

function Person(name, age) {
  this.name = name
  this.age = age

  // new 出来的结果就是这个返回的复杂数据类型
  return { name: 'elser', age: 19 }
}

const p1 = new Person('ifer', 18)

console.log(p1) // { name: 'elser', age: 19 }

11.说一下call , apply , bind的异同

**相同点 : **第一个参数都是用来改变函数内部this指向的 , 都可以利用后续参数传参 不同点 : 1.call 和 apply 会直接调用函数 , bind会返回一个新的函数 2.call 和 bind 可以传递任意多个参数 , 而apply只能传递两个参数(第二个参数是数组或者是伪数组)

    <script>
        function test (a,b,c) {
            console.log(a+b+c);
        }

        // 直接调用
        test.call({name:'xxx'},1 , 2 , 3)  // 6
        test.apply({name:'xxx'},[4,5,6])  // 15

        // 返回新的函数
        const newFh = test.bind()
        newFh(10,20,30)  //  60
    </script>

12.防抖和节流的理解

防抖就是持续触发(事件)不执行 , 不触发一段时间后才执行 节流就是持续触发也执行 , 只不过执行的频率变低了 **在哪用 ** 防抖 : 根据用户输入的内容发送请求
节流 : 获取窗口滚动的位置 怎么做 一般可以结合定时器封装方法来实现 , 或者使用lodash提供的 debounce 和throttle方法

// 在data中定义  延时器的 timerId
				timerId: null,

          
input(value) {
				// 用户每次输入,清除上次输入启动的延时器
				clearTimeout(this.timerId)

				// 每次输入,在清除了上次延时器后,再重新启动一个 500 毫秒的延时器
				// 如果 500 毫秒内容用户没有再次输入,则执行搜索
				// 如果 500 毫秒内用户再次输入了,取消上次的延时器,再重新启动一个 500 毫秒的延时器
				// 防抖处理
				this.timerId = setTimeout(() => {
					// 如果 500 毫秒内,没有触发新的输入事件,则为搜索关键词赋值
					this.kw = value
					// 根据关键词,查询搜索建议列表
					this.getSearchResults()
				}, 500)
			},
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #oBox {
      width: 500px;
      height: 260px;
      background-color: pink;
      margin: 100px auto;
    }
  </style>
  <script src="./node_modules/lodash/lodash.min.js"></script>
</head>

<body>
  <div id="oBox"></div>
  <script>
    oBox.onmousemove = _.debounce(function (e) {
      const left = e.pageX - this.offsetLeft
      const top = e.pageY - this.offsetTop
      this.innerHTML = `left: ${left}, top: ${top}`;
    }, 500);
  </script>
</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #oBox {
      width: 500px;
      height: 260px;
      background-color: pink;
      margin: 100px auto;
    }
  </style>
  <script src="./node_modules/lodash/lodash.min.js"></script>
</head>

<body>
  <div id="oBox"></div>
  <script>
    oBox.onmousemove = _.throttle(function (e) {
      const left = e.pageX - this.offsetLeft
      const top = e.pageY - this.offsetTop
      this.innerHTML = `left: ${left}, top: ${top}`;
    }, 500);
  </script>
</body>

</html>

13.说一下你对EventLoop的理解

EventLoop又叫事件循环 , 是单线程语言JS在运行代码时不被阻塞的一种机制 JS代码的执行分为同步代码和异步代码 , 当碰到同步代码时直接在执行栈中执行 当碰到异步代码并且时机符合时(例如定时器时间到了) , 就会把异步代码添加到任务队列当中 当执行栈中的同步代码执行完毕后 , 就去任务队列中把异步代码拿到执行栈中执行 这种反复去任务队列中取异步代码并拿到执行栈中执行的操作 , 就是EventLoop EventLoop

console.log('react')

setTimeout(() => {
  console.log('vue')
}, 1000)

console.log('angular')


// 执行
react --> angular --> 同步执行完并且定时器时间到了 --> vue

怎么保证定时器那个时间是准确的 ? web worker => 开启多线程 => 把耗时的操作放到另一个线程

14.for...in 与for...of的区别

**for...in : **可以遍历对象 , 遍历数组的下标 , 可以遍历原型的属性 **for...of :**不能遍历对象 , 遍历数组的元素 , 不可以遍历原型的属性

    <script>
      // for...of 不能遍历对象 , 遍历数组的元素
      let arr = [1, 2]
      let obj = { a: "1" }
      // for (let i of obj) {
      //   console.log(i)
      // }
      // for (let i of arr) {
      //   console.log(i)
      // }

    // for... in 可以遍历对象 , 遍历数组的下标
    for(let i in obj){
      console.log(i);
    }
    // 遍历数组的下标
    for(let i in arr) {
      console.log(i);
    }
    </script>

15.原型与原型链

构造函数通过new创建实例对象 实例对象(对象)身上的一个属性,叫 __proto__就是对象原型 原型对象是构造函数上的一个属性prototype 只有函数有prototype ,对象没有

对象原型有什么作用? 对象原型(proto) 默认指向了 原型对象(prototype) , 导致实例对象可以调用公共方法 proto 就是实例对象和原型对象之间的一个桥梁

**constructor ** 概念:是原型对象(prototype) 和 对象原型(proto)身上的一个属性 作用: constructor会记录当前对象属于哪个构造函数 IMG_0213.PNG

原型链图.png

16.数据类型的判断

**判断原始数据类型typeof:区分不出null , **除了 function,区分不出复杂数据类型(都是 'object')

typeof undefined   // "undefined"
typeof null   // "object" 
typeof 1   // "number" 
typeof "1"   // "string" 
typeof Symbol()   // "symbol" 

**区分null : ** 用全等 === 判断 或者 Object.prototype.toString 方法 **引用数据类型intanceof:**返回的是布尔值 A instanceof B,是判断 A 是否能够通过原型链(proto) 找到 B 的 prototype。

17.判断是否为一个对象

**1.Object.prototype.toString.call **(数据) 判断对象

Object.prototype.toString.call(obj) === '[object Object]'

2.constructor

obj.constructor === Object

3.**instanceof **需要注意的是由于数组也是对象,因此用 arr instanceof Object 也为true。

obj instanceof Object

18.判断对象为空

第一种 : JSON.stringify( ) 通过JSON.stringify()将json对象转化为json字符串,再判断该字符串是否为”{}”,这就直接可以得出来这个对象是否为空。

let data = {};
let a = (JSON.stringify(this.projectData) === "{}"); //把data转换为字符串a
if(a === true)){  //如果data为空,返回为true
    return;   
}

第二种 : for…in 循环判断 通过for…in 循环来判断对象**(obj)**是否为空

let data = {};
let a = function() {
  for(let key in data) {
    return false;
  }
  return true;
}
alert(a());  //为true,说明data为空对象

第三种 : Object.keys( ) 通过Object.keys()来进行对象**(obj)**是否为空的判断,该方法也是ES6新增的方法, 返回值是对象中属性名组成的数组。

let data = {};
let array = Object.keys(data);
if(array.length === 0){     //如果数组array的长度为0 ,则返回为true,说明data对象为空
    return ;   
}

还有两种不常用的 : ** ** jquery的isEmptyObject方法
**Object.getOwnPropertyNames( )**方法。

19.深浅拷贝

浅拷贝 : 如果是基本数据类型, 拷贝的就是基本类型的值 ; 如果是引用类型, 拷贝的是引用地址 ; 即浅拷贝拷贝的第一层 1.浅拷贝 1.扩展运算符 既不是深拷贝,也不是浅拷贝。一半一半,他只能深拷贝第一层。第二层的拷贝还是浅拷贝 2.Object.assign() 语法 : Object.assign(target, ...sources) 注意事项 : 1.不会拷贝对象的继承属性; 2.不会拷贝对象的不可枚举的属性; 3.可以拷贝 Symbol 类型的属性。 **3. Array.prototype.concat()拷贝数组 ** 浅拷贝,适用于基本类型值的数组 4.Array.prototype.slice()拷贝数组 浅拷贝,适用于基本类型值的数组 2.深拷贝 1:JSON.stringify( ) JSON.stringfy( ) 其实就是将一个 JavaScript 对象或值转换为 JSON 字符串,最后再用 JSON.parse( ) 的方法将JSON字符串生成一个新的对象。(JSON.stringfy()JSON.parse()2.递归实现

20.slice splice split的区别

slice 截取 : 可操作数组 和 字符串 slice中存在2个参数,slice(start,end),start表示数组索引,end是数字位置,若只存在一个参数则显示参数位置到最后

let a=[1,2,3,4,5]; a.slice(0,3)=[1,2,3]; a.slice(3)=[4,5]; 
# 可以看第二个参数减去第一个参数的值,为几一般就是显示几个数字
let a="this is a test"; a.slice(0,6)="this i";

splice 删除替换 : 可操作数组 和 字符串 splice(start,deletecount,item) start 起始位置索引 deletecount 删除位数, 替换的item 返回值为被删除的字符串

let a={'a','b','c'}; 
let b=a.splice(1,1,'e','f'); 
# 结果
let a={'a','e','f','c'}; 

split 分割 : 字符串变数组

let a="01234"; 
a.split("",3)=["0","1","234"];

21.hash和history路由的区别?

1.hash 路由:监听 url 中 hash 的变化,然后渲染不同的内容,这种路由不向服务器发送请求,不需要服务端的支持; 2.history 路由:监听 url 中的路径变化,需要客户端和服务端共同的支持; **原理 ** hash 模式是通过监听 onhashchange 事件做的处理。 history 模式是利用 H5 新增的 History 相关 API 实现的,例如 onpopstate 事件、pushState、replaceState 等方法。 对于后端的表现 刷新页面时,hash 地址也就是(也就是 # 号后面的内容),不会作为资源发送到服务端,后端拿到的都是 / 这个请求地址,它只需要返回 index.html 页面就好啦。 刷新页面时,history 地址对于服务端来说是一个新的请求,后端拿到的是不同的请求地址,也就意味着需要服务端对这些请求做处理,否则会 404。 hash 不存在兼容性问题

22.跨域的理解

一、说一说, 你对跨域的理解 ? 1.跨域的概念 : 两个协议(http,https), 地址, 端口号有一个不相同, 就是跨域的 2.跨域的优缺点 : 优点就是安全 : DOM/cookie/ajax无法正常执行 ; 缺点 : 无法正常处理数据 3.解决方案 : 1.cors模块 解决方案 : 后端配置(后端通过设置一个响应头Access-Control-Allow-Origin) , 前端正常调用 2.代理服务器 , 如果是Vue-CLI创建的项目, 可以再vue-config.js文件中配置devServer的proxy 3.jsonp 要求前后端配合完成 ; 前端通过script标签发送请求 , 然后把全局函数名称发送给后端 , 后端接受到函数名称 , 会把函数放入到参数中 , 作为字符串响应给前端

23.null 和undefined的区别

null 表示一个"无"的对象(空对象指针) , 转为数值时为0 undefined 是一个表示"无"的原始值 , 转为数值时为NaN

typeof null // 'object'
typeof undefined // 'undefined'
null + 4 // 4
undefined + 7 // NaN

// Number(null) // 0
// Number(undefined) // NaN
JSON.stringify({ foo: undefined, bar: null }) // '{bar: null}'

24.JS继承

1.ES6的class继承 2.原型链继承

Child.prototype = New Parent()

3.借用构造函数继承

Parent.call(this)

4.组合式继承 : 用构造函数继承 + 原型链继承

4-- Es6

1..async /await

await 表示强制等待的意思,await关键字的后面要跟一个promise对象,它总是等到该promise对象resolve成功之后执行,并且会返回resolve的结果 async里面是异步函数 , await后面是同步函数 async / await 用同步的方式 去写异步 promise可以通过catch捕获,async / await捕获异常要通过 try / catch

2.catch 之后再写 then 还会触发吗

catch 之后再写 then 还会触发吗, 正常是会的,因为这儿也有一个默认的返回

3.var、let、const之间的区别

var声明变量可以重复声明,而let不可以重复声明 var是不受限于块级的,而let是受限于块级 const声明之后必须赋值,否则会报错 const定义不可变的量,改变了就会报错 const和let一样不会与window相映射、支持块级作用域、在声明的上面访问变量会报错

4.箭头函数与普通函数的区别

1.箭头函数没有原型对象prototype,不能作为构造函数使用(不能被new) 2.箭头函数没有arguments(类似数组但不是数组的对象), 可以使用 ... 拿到所有实参的集合数组 3.箭头函数中的 this 在定义时就已经确定,取决于父级的环境 4.箭头函数不能通过call ,apply , bind方法修改它的this指向(会忽略第一个参数 , 其他功能可以正常使用) 5.箭头函数不能用作Generator函数 , 不能使用yeild关键字(function*)

5.localStorage与sessionStorage和cooike区别

生命周期和存储不一样 localStorage : 硬盘存储 , 永久存储 , 除非手动删除 存储大小4.98M , **sessionStorage **: 内存存储 , 关闭当前页后会自动清除 存储大小4.98M **cookie **: 默认关闭游览器后失效, 如果设置了过期时间,则达到过期时间后失效 存储大小4KB 生效范围不一样 localStorage : 同域可以共享 **sessionStorage : **只有当前标签页才能访问 **cookie : **同域且path匹配的情况下才能访问 操作主体和请求是否会携带 localStorage / sessionStorage:只有客户端才能设置 , 请求时不会自动携带。 cookie:客户端和服务端都可以设置。每次请求都会随着请求头带到后端 什么是 path 匹配? 例如 www.baidu.com:3000/ 下创建的 cookie 或者设置 cookie 的时候加了 path=/,同域下的任意路径都能访问,因为任意路径都属于 / 的子路径,又称为和 / 是匹配的。 例如 www.baidu.com:3000/a/b 下创建的 cookie 或者设置 cookie 的时候加了 path=/a/b,同域下和 /a 匹配的任意路径都能访问,例如 /a、/a/b、/a/c、/a/b/c 等都称为是和 /a 匹配,或者称为是 /a 的子路径。

6.对promise的理解

Promise是ES6新增的函数 自身是同步的 一般作为构造函数使用 , 需要new一下创建一个promise实例 , 它有三种状态 : pending( 进行中) , fulfilled( 已成功) , rejected( 已失败) ; 成功会触发 then , 失败会触发 catch ; 还有一个finally是永远都会被触发的 Promise 相关的几个静态方法?

  1. Promise.race():接收多个 Promise 实例,可以得到最先处理完毕的结果(可能是成功,也可能是失败)。
Promise.race([p1, p2, p3])
  1. Promise.all():接收多个 Promise 实例,都成功了会触发 then,有一个失败就会触发 catch。发送多个ajax
Promise.all([p1, p2, p3]).then(r => console.log(r))
// [p1 的结果, p2 的结果, p3 的结果]
  1. Promise.any():接收多个 Promise 实例,可以得到最先处理成功的结果,都失败才会触发 catch。
Promise.any([p1, p2, p3])

它解决了回调地狱的问题
虽然解决了回调地狱的问题但是没有简化代码 , 所以工作中我们会配合async / await 来使用

7.对闭包的理解

一个函数 使用了其 外部函数 中的局部变量 , 使用变量的地方我们称为发生了 闭包现象 变量定义所在的函数我们称为 闭包函数 **特性 : **普通函数调用完毕, 内部局部变量马上销毁 ; 闭包函数调用完毕,会使内部形成这个【闭包函数】的变量(age)常驻内存,所以滥用闭包会造成内存浪费。

8.const能否修改?

const 定义的简单数据类型,绝对不能改 const 定义的复杂数据类型,引用地址绝对不能改,内容可以改

// const 定义的简单数据类型,绝对不能改
const num = 8
// const 定义的复杂数据类型,引用地址绝对不能改,内容可以改
const arr[] = []
arr.push(8) // 这就叫内容可以改
arr = [] // 这就叫引用不能改

9.++i 和 i++

++i : +在前先自增在运算 i++ : + 先运算在自增

let i = 8
// 先赋值(先把 i 给了 n),再自加(i 加了 1)
let n = i++

console.log(n) // 8
console.log(i) // 9

10.Set 和Map

Set 和 Map 主要的应用场景在于 数据重组 和 数据储存。 Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构。 Set:

  • 成员唯一、无序且不重复。
  • [value, value],键值与键名是一致的(或者说只有键值,没有键名)。
  • 可以遍历,方法有:add、delete、has。

Map:

  • 本质上是键值对的集合,类似集合。
  • 可以遍历,方法很多可以跟各种数据格式转换。

**set : ** Set 是ES6 新增的一种新的数据结构 , 类似于数组,成员唯一(内部元素没有重复的值) Set 本身是一种构造函数

const s = new Set()
[1, 2, 3, 4, 3, 2, 1].forEach(x => s.add(x))
for (let i of s) {
    console.log(i)	// 1 2 3 4
}
// 去重数组的重复对象
let arr = [1, 2, 3, 2, 1, 1]
[... new Set(arr)]	// [1, 2, 3]

操作方法:

  • add(value):新增,相当于 array里的push。
  • delete(value):存在即删除集合中value。
  • has(value):判断集合中是否存在 value。
  • clear():清空集合。

便利方法:

  • keys():返回一个包含集合中所有键的迭代器。
  • values():返回一个包含集合中所有值得迭代器。
  • entries():返回一个包含Set对象中所有元素得键值对迭代器。
  • forEach(callbackFn, thisArg):用于对集合成员执行callbackFn操作,如果提供了 thisArg 参数,回调中的this会是这个参数,没有返回值。

Map :

const m = new Map()
const o = {p: 'haha'}
m.set(o, 'content')
m.get(o)	// content

m.has(o)	// true
m.delete(o)	// true
m.has(o)	// false

操作方法:

  • set(key, value):向字典中添加新元素。
  • get(key):通过键查找特定的数值并返回。
  • has(key):判断字典中是否存在键key。
  • delete(key):通过键 key 从字典中移除对应的数据。
  • clear():将这个字典中的所有元素删除。

遍历方法:

  • Keys():将字典中包含的所有键名以迭代器形式返回。
  • values():将字典中包含的所有数值以迭代器形式返回。
  • entries():返回所有成员的迭代器。
  • forEach():遍历字典的所有成员。

5-- webpack

1.你对webpack了解哪些东西

**是什么 : **Webpack是一个打包构建工具 , 可以基于loader处理各种各样类型文件 怎么用 : 一般使用的时候会在 wenpack.config.js中做相关配置 , 例如entry , output , module , plugins , mode(development 或 production) 别名 deServer的Proxy 不过,实际开发中我更喜欢用基于 Webpack 的 Vue CLI,它也可以再 vue.config.js 中对 Webpack 进行二次配置。

2.webpack热更新过程

依赖了 hot-module-replacement 插件

  1. 使用 WDS(webpack-dev-server) 托管静态资源,同时注入 HMR(hot module replacement) 代码。
  2. 浏览器加载页面后,与 WDS 建立 WebSocket(双向数据通信的协议) 连接。
  3. Webpack 监听到文件变化后,增量构建发生变更的模块,并通过 WebSocket 发送 hash 事件。
  4. 浏览器接收到 hash 事件后,请求 manifest 资源文件,确认增量变更范围。
  5. 浏览器加载发生变更的增量模块。
  6. Webpack 触发变更模块的 module.hot.accept 回调,执行代码变更逻辑

6-- git

7-- http

1.get与post请求区别

1.get请求一般是去取获取数据 ; post请求一般是去提交数据。 2.get因为参数会放在url中,安全性较差,请求的数据长度有限制(不大于2kb) ; post请求没有长度限制,请求数据放在body中 3.get请求刷新服务器或者回退没有影响,post请求回退时会重新提交数据请求。 4.get请求可以被缓存,post请求不会被缓存。 5.get请求会被保存在浏览器历史记录当中,post不会。 get 请求发送的是  查询参数(query参数) ,通过params传递 post 请求发送的是 请求体(post)参数 ,通过data传递

2.ajax与axios的区别

axios是一个基于Promise的HTTP库 , 而ajax是对原生XHR的封装 ajax技术实现了局部数据的刷新, 而axios实现了对原生ajax的封装 **ajax原理 : **由客户端请求ajax引擎,ajax请求服务器 , 服务器响应返回给ajax引擎 , 由ajax决定将这个结果写到客户端什么位置, 实现无刷新更新数据 核心对象 HMLHttpRequest 简单来说 xioas有的ajax都有 , ajax有的axios不一定有

3.一个页面从输入URL到页面加载显示完成,这个过程中都发生了什么

三大步骤:

  • 网络请求
    • DNS 查询(得到 IP),建立 TCP 连接(三次握手)
    • 浏览器发送 HTTP 请求
    • 收到请求响应,得到 HTML(解析 HTML 过程中,遇到静态资源还会继续发起网络请求)
  • 解析(字符串 -> 结构化数据)
    • HTML 构建 DOM 树
    • CSS 构建 CSSOM 树
    • 两者结合形成 render tree
  • 渲染
    • layout 计算布局:(文档流, 盒模型, 计算各个元素的大小、位置)
    • paint 绘制:(将边框颜色、文字颜色、阴影等都画出来)
    • composite 合成:根据层叠关系显示画面
    • 遇到 JS 执行
    • 异步 CSS,图片加载,可能会触发重新渲染

4.什么是TCP连接的三次握手

TCP是因特网中的传输层协议,建立一个TCP连接时需要客户端和服务端发送三个数据包的过程。进行三次握手是为了确定双方的接收能力和发送能力是否正常。 **第一次握手 : **客户端给服务端发送一个 SYN 报文,并指定客户端的初始化序列号 ISN ,此时客户端处于 SYN_SEND 状态。 **第二次握手 : **服务端受到客户端的SYN报文后,会以自己的SYN报文作为应答,并且也是指定了自己的初始化序列号ISN。同时会把客户端的ISN+1作为ACK的值,表示自己已经收到了客户端的SYN,此时服务器处于SYN_REVD状态。 **第三次握手 : **客户端受到 SYN 报文后,会发送一个 ACK 报文,把服务器的 ISN + 1 作为自己Acknowledgment number的值,表明自己已经收到了 SYN 报文,此时客户端处于ESTABLISHED状态,服务器收到 ACK 报文后,也处于 ESTABLISHED状态。此时双方已经建立链接

5.什么是四次挥手?

(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。 (2)服务器B收到这个FIN,它发回一个ACK(确认号),确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。 (3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。 (4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。

8-- Vue2

1.组件data为什么是一个函数

因为组件是需要在多个地方使用的

  • 如果data是一个对象,对象是引用类型。 一旦某一个地方修改,就会全部修改
  • data是一个函数,每一次复用组件的时候就会从这个函数返回一个新的对象。 这样组件在复用的时候就可以做到数据互不干扰。

2.Vuex的actions和mutations有什么区别?

1.actions主要用于响应组件中的动作,通过 commit( )来触发 mutation 中函数的调用, 间接更新 state,不是必须存在的; 2.actions可以进行异步操作,可用于向后台提交数据或者接受后台的数据; mutations中是同步操作不能写异步代码、只能单纯的操作 state ,用于将数据信息写在全局数据状态中缓存,不能异步操作

3.mapState 和 mapGetters的区别

**mapState : **是将state中的数据映射到组件的计算属性当中

computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])

**mapgetters : **用于将getter(store的计算属性)中的计算属性映射到组件的计算属性当中

...mapGetters({
  // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
  doneCount: 'doneTodosCount'
})

www.jianshu.com/p/eef5213c1…

4.mutation 里面为什么建议只写同步代码去修改 state?写异步会怎样?

写同步代码可以保证,mutation 执行完毕后可以得到一个确定的状态,方便 devtools 打个快照存下来,实现 time-travel 调试(在调试工具中能根据触发 mutation 的时间来进行切换和调试)。 如果 mutation 里面写异步代码,严格模式下会有警告,因为不好追踪状态是何时变更的,给调试带来困难。

5.v-if与v-show的区别

v-if : 条件渲染。JS控制元素的创建与销毁 如果不满足条件,则该元素不会添加到DOM树中 ; 只有满足才会添加到dom树中 (底层原理: appendChild 和 removeChild) , 有较高切换开销 应用场景: 不需要频繁切换,初始为false则直接不添加到dom中 v-show: CSS控制显示与隐藏。 只是修改元素的display属性值 (修改css样式) , 有较高初始渲染开销 应用场景: 需要频繁切换

6.v-for和v-if为什么不建议放一行

**Vue2 : **因为Vue2中v-for的优先级比v-if高,Vue会先循环出所有的元素 , 再把不符合条件的销毁 , 多了一些没必要的创建和销毁的操作, 性能浪费 **Vue3 : **Vue3中v-if比v-for的优先级高 , 更不能放一行 , 因为v-if判断的时候需要依赖循环项进行 , 这个时候循环项还没有,所以会直接报错 **如何解决 : **解决方式就是通过计算属性 , 先计算出需要循环的那些元素就好 需求:数组中的 angular 不需要渲染。

<template>
  <div id="app">
    <ul>
      <li v-for="(item, index) in arr" :key="index" v-if="item !== 'angular'">{{ item }}</li>
    </ul>
  </div>
</template>

<script>
  export default {
    name: 'App',
    data() {
      return {
        arr: ['vue', 'react', 'angular'],
      }
    },
  }
</script>

解决

<template>
  <div id="app">
    <ul>
      <li v-for="(item, index) in renderArr" :key="index">{{ item }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      arr: ['vue', 'react', 'angular'],
    }
  },
  computed: {
    renderArr() {
      return this.arr.filter((item) => item !== 'angular')
    },
  },
}
</script>

7.生命周期

image.png 钩子 : 一种回调函数 , 4个阶段,8个周期 1.beforeCreate(){} el和data还没有创建 vue实例创建了,但是el和data还没有创建 (准备创建data) 底层(初始化vue实例,初始化钩子函数,初始化一些事件和侦听器配置项) 2.created() {} data创建 , el还没创建 data数据创建了,但是el挂载点还没有创建 (准备创建el) 底层:初始化data中的数据和methods中的方法 3.beforeMount() {} el创建 , data还没渲染 el挂载点创建了,但是data数据还没有渲染(准备渲染中) 底层:创建el挂载点,并且生成虚拟DOM 4.mounted() {} data第一次渲染完 data数据 第一次 渲染完毕 (完成初始渲染) 底层:将虚拟dom渲染成真实DOM 5.beforeUpdate() {} data变化还没渲染 检测到data数据变化,但是还没有开始重新渲染 (data变了,准备重新渲染中) 会执行多次 6.updated() {} data变化渲染完 变化后的data数据 ,完成渲染到页面 (完成重新渲染)会执行多次 7.beforeDestroy() {} 解除data与el的关联 vue实例销毁即将销毁(解除data与el的关联),之后修改data,页面不会被渲染 底层 : 解除 事件绑定、侦听器、组件 8.destroyed() {} vue实例已经销毁(而是指解除data与el的关联) **组件钩子 : **activated和deactivated没有keep-alive的时候是不会被触发的 在存在keep-alive的时候可以将activated当作created进行使用 deactivated是组件销毁的时候触发,此时的destory是不执行的 component 动态组件 路由守卫钩子 : 路由守卫分类 【1】全局守卫:是指路由实例上直接操作的钩子函数,特点是所有路由配置的组件都会触发,直白点就是触发路由就会触发这些钩子函数 beforeEach(to,from, next) afterEach(to,from) beforeResolve(to,from, next) 【2】路由守卫: 是指在单个路由配置的时候也可以设置的钩子函数 beforeEnter(to,from, next) 【3】组件守卫:是指在组件内执行的钩子函数,类似于组件内的生命周期,相当于为配置路由的组件添加的生命周期钩子函数。 beforeRouteEnter(to,from, next) beforeRouteUpdadte(to,from, next) beforeRouteLeave(to,from, next)

8.父子生命周期顺序

初始化:父 beforeCreate、父 created、父 beforeMounte、儿子走完、父 mounted 更新:父 beforeUpdate、子走完、父 updated 销毁:父 beforeDestroy、子走完、父 destroyed

9.会在vue哪个生命周期钩子里发送请求 , 为什么

📌 首先呢,发请求的时机肯定越早越好,那么 beforeCreate 最早; 📌 但是还有一点,有时候请求前需要依赖 data 里面的数据或调用 methods 里面的方法(请求后可能也需要); 📌 而 data 和 methods 都是需要实例创建完毕后(created)才具有的,所以一般我会在 created 里面发请求; 📌 既保证了请求实际相对较早,又保证了可以使用 data 里面的数据或 methods 里面的方法。 不过有时候这个请求需要依赖 DOM 相关操作的话,我会选择在 mounted 里面进行,因为 mounted 阶段才能保证页面已经渲染完毕了,也就可以操作 DOM 啦。

10.created和mounted的区别

**created:**在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。 **mounted:**在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。

11.Vue2 哪些情况对数据的操作不是响应式的?

哪些操作不是响应式, 为什么? Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。 1.对后续新增的key(属性)进行赋值 Object.definePropert( )是递归劫持对象中的每一个属性, 后续新增的属性劫持不到 解决:Vue.set() 或 this.$set()

data() {
    return {
        obj: {}
    }
}
this.obj.age = 18

2.删除对象中的某一项 Object.definePropert( ) 不支持对数组删除操作的劫持 Vue.delete() 或 this.$delete()

data() {
    return {
        obj: {
            age: 18
        }
    }
}
delete this.obj.age

3.通过索引去删除数组的某一项 Object.definePropert( ) 并不是不支持, 而是作者出于对性能的考虑没有这样做 Vue.set() 或 this.$set() 或 arr.splice() **Vue3 呢? 1. 给对象后续新增的 key 进行赋值; Vue3 响应式的原理换成了 Proxy,可以直接代理整个对象,就无所谓这个属性是不是后续添加的了。 2. 删除对象中的某一项; Proxy 支持对删除操作的拦截。 3. 通过索引去修改数组的元素; Proxy 对数组的操作不存在性能问题。

12.$set用法

为什么要用 ? 在Vue中并不是任何时候数据都是双向绑定的 总结 : 只要值的地址没有改变,vue是检测不到数据变化的。 用法 ? 解决数据没有被双向绑定我们可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名

13.说一下你对Vue.use的理解

Vue.use( ) 作用是通过全局方法使用插件(组件) , 它接收的参数限制Function | Object 如果插件是一个对象, 它必须暴露一个install方法 如果插件本身是一个函数, 它将视为安装方法

14.说一说对自定义指令的理解

Vue有一组默认指令 , 比如v-model , v-for , 同时也允许用户注册自定义指令来扩展Vue能力 自定义指令分为, 定义 , 注册 , 使用三步 定义有两种方式: 对象形式 : 有各种钩子函数 ; 指令的钩子会传递以下几种参数:

  • el:指令绑定到的元素。这可以用于直接操作 DOM。

  • binding:一个对象,包含一些属性。

                 name,指令名,不包含v-前缀。
                 value,指令的绑定值。如v-focus=“1+1”,value即为2

函数形式 : 只会在mounted 和updateed中执行 注册 : 通过Vue.directive( )全局注册 , Vue.directive第一个参数是指令的名字(不需要写上v-前缀),第二个参数可以是对象数据,也可以是一个指令函数 使用{directives:{xxx}} 局部注册 , 局部注册通过在组件options选项中设置directive属性 使用时 : 注册名称前加上v- , 比如 v-focus 项目使用: 复制v-copy , 光标v-focus

15.说一下你对keep-alive的理解

keep-alive是vue中的内置组件 ,用来缓存组件的, 提升性能
能在组件切换过程中将状态保存到内存中, 防止重复渲染 比如 : 首页进入详情页 , 用户每次点击都是相同的, 那么详情页就没必要再次请求了, 直接缓存起来再用, 点击的不是同一个 , 就再次发送 缓存后如何获取数据?

  1. beforeRouteEnter

每次组件渲染的时候都会执行 beforeRouteEnter

beforeRouteEnter(to, from, next){
    next(vm=>{
        console.log(vm)
        // 每次进入路由执行
        vm.getData()  // 获取数据
    })
},
  1. actived

在keep-alive缓存的组件被激活的时候,都会执行actived钩子

activated(){
   this.getData() // 获取数据
},

activated和deactivated没有keep-alive的时候是不会被触发的 在存在keep-alive的时候可以将activated当作created进行使用 deactivated是组件销毁的时候触发,此时的destory是不执行的

16.父传子, 子传父,兄弟通信,跨层级通信你了解的都有哪些方法?

  1. 通过 props 传递 父传子 父亲通过自定义属性传递, 儿子通过props接收
  2. 通过 $emit 子传父 触发自定义事件
  3. 使用 ref 父组件使用子组件 通过ref来获取子组件的实例,调用子组件的方法来传递
  4. EventBus 兄弟组件传值
  5. parentparent 或root 共同祖辈兄弟传值
  6. attrsattrs 与 listeners 祖先传递给子孙
  7. Provide 与 Inject 祖先传递给子孙
  8. Vuex 复杂关系的组件数据传递 , 是一个存储共享变量的容器

17.虚拟dom和真实dom

虚拟DOM和真实DOM (1)真实DOM : document.querySelector('选择器') 特点: 内存中的属性很多,有上百个 (2)vue虚拟DOM : 本质是一个js对象 特点: 只存储少部分有用的属性 虚拟DOM好处 (1)提高了更新DOM的性能(不用把页面全删除重新渲染) (2)存储更少的数据(节省内存) vue渲染虚拟DOM流程 (0)根据真实DOM 生成 新的虚拟DOM 把真实DOM上百个属性提取核心属性出来,生成虚拟 VNode (1)使用 Object.defineProperty() 监听data变化 (2)新旧 虚拟DOM对比, 找不同的地方 底层:使用 diff算法来找新旧 VNode 不同点 (3)更新虚拟DOM (4)将虚拟DOM渲染到真实DOM

18.vue2统一功能的数据和业务逻辑如何复用

可以使用 mixins 复用 缺点 : 数据来源不清楚 ; 命名冲突

19.侦听器 计算属性 methods 区别

面试点 : 侦听器与计算属性区别 开启深度侦听 deep:true (1)计算属性有缓存, 侦听器没有缓存 (2)计算属性不支持异步操作 , 侦听器支持异步操作 (3)侦听器只能检测一个数据变化, 计算属性可以检测多个数据变化 (4)侦听器只能侦听data里面有的数据, 计算属性可以给vue实例新增属性 面试点 : 计算属性和methods的区别(计算属性有缓存机制) (1)第一次使用计算属性的时候, vue会执行一次这个函数,然后把返回值放入缓存中 (2)之后使用计算属性, vue不会执行函数,而是从缓存读取 (3)只有当计算属性内部所 ‘依赖的数据’ 发生改变,vue才会重新执行函数然后将返回值缓存

20.说说对vuex的理解?

Vuex是一个全局的状态管理库 配置项 一般使用的时候它里面有 state、mutation、action、getters、modules、plugins 等配置项。 触发流程 视图 => dispatch => action => 异步请求并拿到结果 => commit => mutation => 修改 state => 视图改变。 解决了什么? 解决了非关系型组件间数据的传递和共享的问题 有时候组件关系明确的时候, 我们会使用 props , ref , EvenBus , Provide 等方法

21.router和route的区别

  • routes : 数组。 路由匹配规则
  • router : 对象。 路由对象
  • $router : VueRouter的实例,相当于一个全局的路由器对象对象。 用于跳转路由 和 传递参数

两种传参方式 : 分别是编程式导航 和 声明式导航 编程式导航写在js函数中,通过this.$router.push(xxx)来触发路径 声明式导航是写在组件的template中,通过router-link来触发

  • $route :路由信息对象。 用于接收路由跳转参数 跳转方式 : path,name ; 传参方式 : params, query

22.query和params之间的区别是什么?

1、query要用path来引入,params要用name来引入 2、接收参数时,分别是this.route.query.namethis.route.query.name和 this.route.params.name(注意:是route而不是route而不是router)

23.如何并发多个请求, 按请求的书写顺序拿到结果?

// # 能保证顺序,但是不是并发,串行
await axios.get('/news')
await axios.get('/sports')
// # 并发,不能保证顺序
axios.get('/news')
axios.get('/sports')
const p1 = axios.get('/news')
const p2 = axios.get('/sports')
Promise.all([ p1, p2 ]).then(r => {
    r // [p1 的结果, p2 的结果]
}).catch(e => {
	// 如果中间有一个出错,就会到 catch
    // catch 之后再写 then 还会触发吗,正常是会的,因为这儿也有一个默认的返回
    // return Promise.resolve(undefined)
}).then(r => {
    
});

24.Vue传递过来的数据可以修改吗?

传递过来的如果是简单数据类型,不能改,改了会有警告(传递过来的确实会变,外部还是原来的)。 传递过来的如果是复杂数据类型,引用地址不能改,但是内容可以改,即便内容可以改,也不建议改,因为单项数据流思想。

25.$nextTick

为了解决 : 修改数据的时候, 页面dom不会立即更新 **用法 : ** 如果想要在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick() vm.$nextTick() , 等待当前dom异步更新之后立即执行 原理是 : , vue 通过异步队列控制 DOM 更新 , 当数据发生变化,Vue将开启一个异步队列,视图需要等队列中所有数据变化完成之后,再统一进行更新 nextTick底层是promise,所以是微任务

26.Vue响应式怎么实现的

①首先,vue的响应式指的是当组件data发生变化的时候,会立即触发视图的更新。 ②从原理层面来讲,它是采用数据劫持结合发布订阅的模式来实现数据的响应。具体来讲就是对data中的所有属性进行劫持,然后在getter中收集依赖,在setter中触发依赖。也就是说,把用到该属性的组件所对应的watcher对象都收集到属性订阅器dep里面,之后当属性值发生变化的时候,dep就会循环遍历所有的watcher对象并通知它们调用update方法,从而使得跟这个属性相关联的组件都得到更新。 ③刚刚提到了数据劫持这个概念,那么 vue2和vue3采用的数据劫持方法也有所不同,vue2使用的是object.defineProperty,vue3使用的是proxy, vue3之所以不再沿用vue2的方法主要是由于使用object.defineProperty存在一些缺陷,比如它不能监听对象的新增属性和删除属性,同时也无法监听到通过索引改变数组元素的操作。

27.发布订阅模式和观察者模式

发布订阅模式 : 发布订阅模式中有三个角色,发布者 Publisher ,事件调度中心 Event Channel ,订阅者 Subscriber观察者模式 : 在观察者模式中,只有两种主体:目标对象 (Object) 和 观察者 (Observer)。 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。

28.Vue双向绑定的原理

Vue 数据双向绑定原理是通过 数据劫持 + 发布者-订阅者模式 的方式来实现的,首先是通过 ES5 提供的 Object.defineProperty() 方法来劫持(监听)各属性的 getter、setter,在getter中收集依赖,在setter中触发依赖 , 并在当监听的属性发生变动时通知订阅者,是否需要更新,若更新就会执行对应的更新函数。 什么是数据劫持 数据劫持比较好理解,通常我们利用Object.defineProperty劫持对象的访问器,在属性值发生变化时我们可以获取变化,从而进行进一步操作。 发布者模式 / 订阅者模式 在软件架构中,发布订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。 这里很明显了,区别就在于,不同于观察者和被观察者,发布者和订阅者是互相不知道对方的存在的,发布者只需要把消息发送到订阅器里面,订阅者只管接受自己需要订阅的内容

29.如何监听子组件的某个钩子,这个触发后父亲做一些事情?

第一种 : 调用父亲的方法

// 第一种方法
// 子
export default {
    created() {
        # 调用父亲的方法
    }
}

第二种 : @hook

// 第二种方法
// 父
// 监听儿子的 created 钩子触发后,指定父亲的 handleFn
<Child @hook:created="handleFn"/>

30.VUE Router导航钩子

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫(2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

31.sync

.sync与v-model区别是

相同点:都是语法糖,都可以实现父子组件中的数据的双向通信。 区别点:格式不同: v-model=“num”, :num.sync=“num” v-model: @input + value :num.sync: @update:num 另外需要特别注意的是: v-model只能用一次;.sync可以有多个。

9-- Vue3

1.Vue3 丢失响应式

1.解构赋值 2.直接把简单数据赋值给一个变量 3.把简单数据类型赋值给对象中的某一个

2.说一下你对 Vue3 的了解?说一下 Vue3 和 Vue2 的差异?

1.性能更高了 (得益于响应式的原理换成了Proxy , VNode Diff算法进行了优化) 2.体积更小了 (删除了一些不常用的api , 例如 filter , EventBus ; Vue3中所有的api都是按需导入 , 能配合Webpack支持TreeShaking) 3.对TS的支持更好了 (源码使用TS写的) 4.Composition API(组合 API,Vue2 叫 Options API),对于大型项目更利于同一功能的数据和业务逻辑维护和复用! 5. 新特性(Fragment、Teleport、Suspense...自对应 render 渲染器)。

10-- TS

1.为什么要用ts ?

ts 有类型的校验和提示 , 是js的超集 , 对于开发大型项目很合适

2.any 和 unknown的区别

1.unknown是更加安全的any类型 , 例如当做方法直接调用或者访问某个属性其实在写 TS 的时候也能把错误报出来的,这往往正是我们期望的,所以一般我会优先使用 unknown。

let num: unknown = 88
num = 'abc'
console.log(num)
// 编译成 JS 会报错
// num() // error: 不能调用方法
// console.log(num.length) // error: 不能访问属性
  1. 任何类型可以给 any,any 也可以给任何类型;任何类型可以给 unknown,unknown 只能给 unknown 或 any 类型

11-- Vite

1.vite是什么?

是什么 : 是下一代前端开发与构建工具 , 热更新, 打包构建速度相比较比webpack更快 为什么快 : 1 . Webpack 会将所有模块提前编译 , 打包 , 不管这个模块是否被用到 , 随着项目越来越大, 打包启动速度自然越来越慢 2 . Vite 瞬间开启一个服务 , 并不会先编译所有文件 , 当游览器用到某个文件时 , Vite服务会收到请求然后编译后响应到客户端

12-- 小程序

1.如何模拟小程序中的请求拦截器?

因为 wx.request 并没有提供所谓拦截器的功能,每次携带 token 麻烦,让你去做你该怎么模拟请求拦截器,参考。 总结如下! 所谓请求拦截器的模拟,无非是提前准备一个方法(请求拦截器方法),每次请求前调用一下这个方法。 调用方法的同时,传递过去一个对象(this),方法内部接收对象并修改此对象(_this.header = {}),只不过 wx.request 的时候 header 所应用的值就是这对象(wx.request({ header: this.header }))。

<script>
  // 问题,每次携带 Authorization 玛法,怎么解决。
  /* wx.request({
    header: {
      Authorization: 'Bearer xxx',
    },
  })
  wx.request({
    header: {
      Authorization: 'Bearer xxx',
    },
  }) */

  function Request() {
    // 实例对象
    this.header = {}
  }
  Request.prototype.get = function () {
    // #1 每次发请求前调用 this.beforeRequest 并把 this 实例传递到此方法中
    this.beforeRequest(this)
    this._()
  }
  Request.prototype._ = function () {
    wx.request({
      // #3 后续通过 this.header 就能生效了呀
      header: this.header,
      success: () => {
        this.afterRequest()
      },
    })
  }
  const $http = new Request()

  // 希望这样写以后,后面每次请求都能携带 header
  $http.beforeRequest = function (_this) {
    // #2 此方法往 _this.header 上挂到内容
    _this.header = {
      Authorization: 'Bearer xxx',
    }
  }
  $http.afterRequest = function () {
    console.log(1)
  }
  $http.get()
  $http.get()
</script>

2.小程序登录你是怎么做的?

1.通过wx.getUserProfile 获取登录接口需要的相关参数, 例如encryptedData(后端需要的) 2.通过wx.login 拿到code(登录凭证) 3.把上面两个api拿到的参数组装好调用后端接口 , 后端根据收到的信息返回token 4.前端拿到token后存储到vuex中 (注意 : 只有用uni-app开发才有vuex , 原生小程序并没有,只需要持久化到本地即可) , 并持久化到本地 5.后续有一些需要携带 token 的接口(例如支付接口),在请求头处进行统一携带即可。

3.小程序支付你是怎么做的?

1.调用后端接口生成订单编号 (请求头携带 token,需要传递收货地址、需要传递购物车中的信息) 2.预支付(根据订单编号调用接口拿到支付相关的参数)。 3.根据上面获取到的支付参数,调用 wx.requestPayment 发起微信支付。 4.再次根据订单编号调用接口查询支付状态,根据支付状态做相关的操作。 5. 如果支付成功,跳转到支付页面(包含了支付方式、金额、状态...)。

4.结算 , 登录支付之前

结算之前 : 先要进行判断 , 满足后才执行结算程序 1.是否勾选了一件商品 2. 是否选择了收货地址 3.是否登录 支付之前 : 1.把订单信息发送到服务器 , 服务器返回拿到订单编号 2.根据订单编号拿到 订单预支付对象 , 包含了订单支付相关的必要参数 3.调用api , 发起微信支付

5.做过小程序开发吗,说一下和网页开发的区别

1、运行环境不同 网页运行在浏览器环境中 小程序运行在微信环境中 2、api不同 小程序无法调用dom和bom的api,但是可以调用微信环境中提供的api.比如地理定位、扫码、支付等 3、开发模式不同 网页开发模式:浏览器+代码编辑器 小程序:申请小程序开发账号、安装小程序开发者工具、创建和配置小程序项目

13-- 移动端

1. 移动端 click 有 300ms 延迟

可以使用 fastclick.js 解决。

2. 1像素问题

1px 在 iPhone6 上占 2 个物理像素,因为 dpr(物理像素比)2,所以看起来有点粗,这并不是我们期望的,transform: scale(1, 0.5)。

3. IOS 设备表单元素其实自带一些样式的,需要手动清除。

input[type="text"] {   -webkit-appearance: none; }

4. 在H5页面给字体设置了固定的尺寸15px,在andriod手机上横竖屏字体大小不变,但是在苹果手机上,横屏字体会变大

解决如下。

-webkit-text-size-adjust: 100%;

14-- 项目

1.说一说你再开发中遇到的问题, 怎么解决的?

  • 1操作data中的数据,发现没有响应式
    • 原因: 数组中有很多方法,有的会改变数组(例如pop push),有的不会改变数组(例如slice, filter)
    • 解决方案:通过Vue.set(对象,属性,值)这种方式就可以达到,对象新添加的属性是响应式的
  • 2.在created操作dom的时候,是报错的,获取不到dom,这个时候实例vue实例没有挂载
    • 解决方案:Vue.nextTick(回调函数进行获取)

2.图片懒加载

背景 如果我们使页面包含的所有图片一次性加载完成,那用户体验很差。 这样做的好处,一是页面加载速度快(浏览器进度条和加载转圈很快就结束了,这样用户的体验也比较好),而是节省流量,因为不可能每一个用户会把页面从上到下滚动完 原理 1.存储图片的真实路径 , 真实路径绑定给以data开头的自定义属性data-url 2.设置img的默认src为一张1px*1px,很小很小的gif透明图片(所有的img都用这一张,只会发送一次请求),之所以需要是透明的,是需要透出通过background设置的背景图(一张loading.png,就是一个转圈圈的背景效果图) 3.需要一个滚动事件,判断元素是否在浏览器窗口,一旦进入视口才进行加载,当滚动加载的时候,就把这张透明的1px.gif图片替换为真正的url地址(也就是data-url里保存的值) 4.等到图片进入视口后,利用js提取data-url的真实图片地址赋值给src属性,就会去发送请求加载图片,真正实现了按需加载 三种方式 方法一:滚动监听+scrollTop+offsetTop+innerHeight

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        img {
            background: url('./img/loading.gif') no-repeat center;
            width: 250px;
            height: 250px;
            display: block;
        }
    </style>
</head>

<body>
    <img src="./img/pixel.gif" data-url="./img/1.jpeg">
    <img src="./img/pixel.gif" data-url="./img/2.jfif">
    <img src="./img/pixel.gif" data-url="./img/3.jfif">
    <img src="./img/pixel.gif" data-url="./img/4.jfif">
    <img src="./img/pixel.gif" data-url="./img/5.jfif">
    <img src="./img/pixel.gif" data-url="./img/6.webp">

    <script>

        let imgs = document.getElementsByTagName('img')
        // 1. 一上来立即执行一次
        fn()
        // 2. 监听滚动事件
        window.onscroll = lazyload(fn, true)
        function fn() {
            // 获取视口高度和内容的偏移量
            let clietH = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
            var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
            console.log(clietH, scrollTop);
            for (let i = 0; i < imgs.length; i++) {
                let x = scrollTop + clietH - imgs[i].offsetTop //当内容的偏移量+视口高度>图片距离内容顶部的偏移量时,说明图片在视口内
                if (x > 0) {
                    imgs[i].src = imgs[i].getAttribute('data-url'); //从dataurl中取出真实的图片地址赋值给url
                }
            }
        }
          // 函数节流
        function lazyload(fn, immediate) {
            let timer = null
            return function () {
                let context = this;
                if (!timer) {
                    timer = setTimeout(() => {
                        fn.apply(this)
                        timer = null
                    }, 200)
                }
            }
        }


    </script>
</body>

</html>

方法二:滚动监听+getBoundingClientRect()

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        img {
            background: url('./img/loading.gif') no-repeat center;
            width: 250px;
            height: 250px;
            display: block;
        }
    </style>
</head>

<body>
    <img src="./img/pixel.gif" data-url="./img/1.jpeg">
    <img src="./img/pixel.gif" data-url="./img/2.jfif">
    <img src="./img/pixel.gif" data-url="./img/3.jfif">
    <img src="./img/pixel.gif" data-url="./img/4.jfif">
    <img src="./img/pixel.gif" data-url="./img/5.jfif">
    <img src="./img/pixel.gif" data-url="./img/6.webp">

    <script>

        let imgs = document.getElementsByTagName('img')
        // 1. 一上来立即执行一次
        fn()
        // 2. 监听滚动事件
        window.onscroll = lazyload(fn, true)
        function fn() {
            // 获取视口高度和内容的偏移量
            let offsetHeight = window.innerHeight || document.documentElement.clientHeight
            Array.from(imgs).forEach((item, index) => {
                let oBounding = item.getBoundingClientRect() //返回一个矩形对象,包含上下左右的偏移值
                console.log(index, oBounding.top, offsetHeight);
                if (0 <= oBounding.top && oBounding.top <= offsetHeight) {
                    item.setAttribute('src', item.getAttribute('data-url'))
                }
            })
        }
        // 函数节流
        function lazyload(fn, immediate) {
            let timer = null
            return function () {
                let context = this;
                if (!timer) {
                    timer = setTimeout(() => {
                        fn.apply(this)
                        timer = null
                    }, 200)
                }
            }
        }


    </script>
</body>

</html>

方法三-intersectionObserve() 优

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        img {
            background: url('./img/loading.gif') no-repeat center;
            width: 250px;
            height: 250px;
            display: block;
        }
    </style>
</head>

<body>
    <img src="./img/pixel.gif" data-url="./img/1.jpeg">
    <img src="./img/pixel.gif" data-url="./img/2.jfif">
    <img src="./img/pixel.gif" data-url="./img/3.jfif">
    <img src="./img/pixel.gif" data-url="./img/4.jfif">
    <img src="./img/pixel.gif" data-url="./img/5.jfif">
    <img src="./img/pixel.gif" data-url="./img/6.webp">

    <script>

        let imgs = document.getElementsByTagName('img')
        // 1. 一上来立即执行一次
        let io = new IntersectionObserver(function (entires) {
            //图片进入视口时就执行回调
            entires.forEach(item => {
                // 获取目标元素
                let oImg = item.target
                // console.log(item);
                // 当图片进入视口的时候,就赋值图片的真实地址
                if (item.intersectionRatio > 0 && item.intersectionRatio <= 1) {
                    oImg.setAttribute('src', oImg.getAttribute('data-url'))
                }
            })
        })
        Array.from(imgs).forEach(element => {
            io.observe(element)  //给每一个图片设置监听
        });
    </script>
</body>

</html>

3.说一下路由权限的处理流程?

首先权限本质是后端控制的 , 每个用户登陆成功之后 , 后端会返回当前用户的标识 , 以数组的方式 前端拿到这个标识后 , 去筛选出有权限的路由 筛选出来后呢通过addrouters/addroute  添加到了路由实例(router) , 一旦添加到了路由实例 ,  当前用户就有了访问某个页面的权限 , 这是路由级别的 按钮级别的权限只需要封装一个全局的一个方法或者是自定义指令 , 这个方法只做一件事情 , 接收一个标识 , 内部判断一下这个标识在不在后端返回的功能数组里面, 在的话就返回true , 不在就返回false , 接下来只需要在做按钮控制的地方, 调用一下这个方法, 传过去当前功能标识, 根据这个方法返回的是true还是false , 对这个按钮做一个禁用或者启用, 显示或隐藏的操作 第二种方式 封装全局自定义指令 注册一个全局自定义指令  , 里面用inserted钩子(被绑定元素插入父节点时调用) , 传入el , 和bianding两个对象 再

// 注册一个全局自定义指令 `v-allow`
Vue.directive('allow', {
  inserted: function(el, binding) {
    // 从vuex中取出points,
    const points = store.state.user.userInfo.roles.points
    // 如果points有binding.value则显示
    if (points.includes(binding.value)) {
      // console.log('判断这个元素是否会显示', el, binding.value)
    } else {
      el.parentNode.removeChild(el)
      // el.style.display = 'none'
    }
  }
})

<el-button
+           v-allow="'import_employee'"
            type="warning"
            size="small"
            @click="$router.push('/import')"
          >导入excel</el-button>

// 或者
function fn(tag) {
	return [后端返回的, 'DEL_USER', 'b', 'c'].includes(tag)
}

<button v-if="fn('DEL_USER')">删除用户信息</button>

4.图片预览是怎么做的?

**第一种 : 使用 FileReader ** FileReader.prototype.readAsDataURL()方法把图片文件转成base64编码 , 然后把base64编码替换到预览图片的src属性即可

<input type="file" hidden id="oInput" />
    <img id="oImg" alt="" />
    <button id="oBtn">上传</button>
    <script>
      oBtn.onclick = function () {
        oInput.click()
      }
      oInput.onchange = function (e) {
        const f = e.target.files[0]
        const reader = new FileReader()
        // 把文件信息 f 读取为 base64 格式的
        reader.readAsDataURL(f)
        console.log(reader) // 读取后把base64保存到result中
        reader.onload = function () {
          //console.log(this.result) // base64格式地址
          oImg.src = this.result
          oImg.width = 600
        }
      }
    </script>

第二种 : 使用URL.createObjectURL URL.createObjectURL是将图片转换成blob的格式。

<input type="file" hidden id="oInput" />
<img id="oImg" alt="">
<button id="oBtn">上传</button>
<script>
  oBtn.onclick = function() {
    oInput.click()
  }
  oInput.onchange = function(e) {
    const f = e.target.files[0]
    // blob url => 临时的图片地址
    const blobUrl = URL.createObjectURL(f)
    oImg.src = blobUrl
  }
</script>

5.登录的流程

前端要收集数据 => 校验数据 => 校验通过后把数据提交到后端 => 后端根据接收到的用户信息生成 token 并返回到前端 => 前端拿到 token 之后存储到 Vuex(目的:是为了方便和响应式)和本地(目的:持久化)。 如何把本地的数据和 Vuex 保持同步?需要在初始化 Vuex 数据的时候从本地去一下就行了。

6.用户没有登录该如何不让他访问内页?

我会在全局路由前置导航守卫(beforeEach)内部做一些处理,如果有 token 就直接放行,如果没有 token,就看一下访问的页面(to.path)在不在白名单,如果在也执行 next 放行,否则拦截到登录页。

7.token 过期你是怎么处理的?

在响应拦截器内部做的处理,如果说后端返回的是 401,就表示 token 过期了,此时我会清除过期的用户信息并跳转到登录页。 token 一般一个小时过期?目的是为了安全。但是用户体验不好,因为只要用户使用系统超过了一个小时,就会被你上面的操作拦截到登录页,怎么办呢?可以用 refresh_token 换取新的 token,然后把错误的请求重新发送到后端。

8.axios封装

**怎么封装的 : **一般我们会在utils文件夹新建一个request.js文件, 创建axios实例 , 配置baseURL(一般通过process.env.VUE_APP_BASE_URL读取配置文件中的环境变量) , timeout , transfromResponse, 请求拦截器 (干了啥 : 统一携带token , token过期的主动处理 , 开启全局的进度条) , 响应拦截器 (响应数据的脱壳, token过期的被动处理(401) , 错误状态码的统一处理) 怎么使用的 : 封装好axios后 , 还会在api文件夹封装接口为请求函数 , 在组件中直接调用api文件夹中请求函数

9.和 Vue 相关的性能优化的手段都有哪些?

一般,我在写代码的时候就会可以考虑这些东西/优化。

  1. v-for 加 key(VNode Diff 的时候快速找到变化的那一个元素,按需更新)。
  2. v-if 和 v-for 不要放一行,(可以通过计算属性先把需要循环的计算出来)。
  3. 动态组件(),可以简化 if else 的判断。
  4. 异步组件。
// 平常写法,启动项目后,无论 Question 组件看到没看到,其实代码已经被打包过来了,性能不好
import Question from './Question.vue'
export default {
    name: 'App',
    component: {
        Question
    }
}
// 异步组件,只有用到/看到这个组件的时候才会去加载对应的代码
export default {
    name: 'App',
    component: {
        // 通过函数,返回 import 引入组件的形式
        Question: () => import('./Question.vue')
    }
}
  1. 路由懒加载,用到这个路由的时候才加载此路由对应的组件的代码。
  2. keep-alive,组件缓存的,一般用 keep-alive 包裹路由的出口或动态组件,可以通过 include 指定组件名来控制要缓存谁,和它相关的两个钩子是什么。
  3. 方法和计算属性区分使用场景,优先计算属性(缓存)。
  4. v-show 和 v-if 区分使用场景,例如切换频率用 v-show。

10.cdn加速

假设通过CDN加速的域名为www.a.com,接入CDN网络,开始使用加速服务后,当终端用户(北京)发起HTTP请求时,处理流程如下:

  1. 当终端用户(北京)向www.a.com下的指定资源发起请求时,首先向LDNS(本地DNS)发起域名解析请求。
  2. LDNS检查缓存中是否有www.a.com的IP地址记录。如果有,则直接返回给终端用户;如果没有,则向授权DNS查询。
  3. 当授权DNS解析www.a.com时,返回域名CNAME www.a.tbcdn.com对应IP地址。
  4. 域名解析请求发送至阿里云DNS调度系统,并为请求分配最佳节点IP地址。
  5. LDNS获取DNS返回的解析IP地址。
  6. 用户获取解析IP地址。
  7. 用户向获取的IP地址发起对该资源的访问请求。
  • 如果该IP地址对应的节点已缓存该资源,则会将数据直接返回给用户,例如,图中步骤7和8,请求结束。
  • 如果该IP地址对应的节点未缓存该资源,则节点向源站发起对该资源的请求。获取资源后,结合用户自定义配置的缓存策略,将资源缓存至节点,例如,图中的北京节点,并返回给用户,请求结束。

从这个例子可以了解到: (1)CDN的加速资源是跟域名绑定的。 (2)通过域名访问资源,首先是通过DNS分查找离用户最近的CDN节点(边缘服务器)的IP (3)通过IP访问实际资源时,如果CDN上并没有缓存资源,则会到源站请求资源,并缓存到CDN节点上,这样,用户下一次访问时,该CDN节点就会有对应资源的缓存了

11.文档碎片

1.假如有 10000 个元素需要添加到页面上,你觉得怎么操作性能最好(考察文档碎片) 当时我们给一个后管系统维护的时候,客户要新增“上传文件”需求,想要存放一些公司内部 学习的文件。我们刚开始用了elementui里面的上传文件组件。但在测试的时候,发现文件较 大时,会上传的很慢,考虑到内部学习文件一般会有很多大文件 用Fargment 把所有要构造的节点都放在文档片段中执行 , 这样可以不影响文档树 , 也就不会造成页面渲染. 当节点都构造完成后 , 再将文档片段对象添加到页面当中, 这时所有的节点都会一次性渲染出来 , 这样就能减少游览器负担 , 提升页面渲染速度

<ul id="container">
  
</ul>
// 优化前
<script>
	var container = document.getElementById('container')
	for(let i = 0; i < 10000; i++){
		let li = document.createElement('li')
		li.innerHTML = 'hello world'
		container.appendChild(li);
	}
</script>
  // 优化后
<script>
	var container = document.getElementById('container')
	var fragment = document.createDocumentFragment()
	for(let i = 0; i < 10000; i++){
		let li = document.createElement('li')
		li.innerHTML = 'hello world'
	    fragment.appendChild(li)
	}
	container.appendChild(fragment);
</script>

12.把平铺的数组结构转成树形结构

 [
 {id:"01", pid:"",   "name":"老王" },
 {id:"02", pid:"01", "name":"小张" }
 ]
 上面的结构说明: 老王是小张的上级
*/
export function tranListToTreeData(list) {
// 1. 定义两个变量
const treeList = [], map = {}

// 2. 建立一个映射关系,并给每个元素补充children属性.
// 映射关系: 目的是让我们能通过id快速找到对应的元素
// 补充children:让后边的计算更方便
list.forEach(item => {
if (!item.children) {
item.children = []
}
map[item.id] = item
})

// 循环
list.forEach(item => {
// 对于每一个元素来说,先找它的上级
//    如果能找到,说明它有上级,则要把它添加到上级的children中去
//    如果找不到,说明它没有上级,直接添加到 treeList
const parent = map[item.pid]
// 如果存在上级则表示item不是最顶层的数据
if (parent) {
parent.children.push(item)
} else {
// 如果不存在上级 则是顶层数据,直接添加
treeList.push(item)
}
})
// 返回
return treeList
}

13.长列表优化(虚拟列表优化)

1.不把长列表数据一次性全部直接显示在页面上; 2.截取长列表一部分数据用来填充屏幕容器区域; 3.长列表数据不可视部分使用使用空白占位填充; 4.监听滚动事件根据滚动位置动态改变可视列表;scrolltop 5.监听滚动事件根据滚动位置动态改变空白填充。 我们也把上面的优化行为简称为「虚拟滚动」

15-- 杂物

1.sass和less的区别

关于变量在Less和Sass中的唯一区别就是Less用@,Sass用$。 差不多 , 里面有些很小的区别 , 项目不是很大的话体现不出来 less比css比多出好些功能(如变量、嵌套、运算,混入(Mixin)、继承、颜色处理,函数等),更容易阅读 less混入

.hoverShadow () {
  transition: all 0.5s;
  &:hover {
    transform: translate3d(0, -3px, 0);
    box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2);
  }
}
<style lang="less" scoped>
// 必须导入variables.less
@import "@/assets/styles/variables.less";
@import "@/assets/styles/mixins.less";
div {
  background-color: @warnColor;
  .hoverShadow();
}
</style>

在vite.config.ts 统一注入全局变量 webpack也可以

2.路由缓存问题

当路由地址的切换匹配的是同一个 path 时,Vue 出于性能的考虑,对应的路由组件会被复用。 也就意味着,即便路由参数每次发生了变化,路由组件中相应的生命周期钩子也就只会被触发 1 次。 例如当在 setup 中需要根据路由参数的变化发请求时,会发现拿到的永远是最初的旧数据。 解决方案

  1. 给路由出口的地方加 key。
<router-view :key="$route.fullPath" />
  1. 通过 watch 监听 route 的变化,然后重新获取数据。
watch(
  () => route.params.id,
  (newId) => {
    getDetail(newId)
  }
)
  1. onBeforeRouteUpdate 中拿到最新的路由参数重新发请求。
onBeforeRouteUpdate((to) => {
  getDetail(to.params.id as string)
})

3.函数传参的差异性

简单数据类型传递的是值的拷贝,函数内部对参数的修改不会影响外部; 复杂数据类型传递的是引用地址,函数内部对参数内容的修改会影响外部,对参数引用的修改不会影响外部。 简单数据类型传参

let username = 'qsp'

function foo(username) {
  // 简单数据类型传参传递的是值的拷贝,把这个值的拷贝给了 username 局部变量
  // 所以对这个 username 局部变量的修改当然不会影响外部的
  username = 'zsf'
}
foo(username)

console.log(username) // 'qsp'

复杂数据类型传参的 2 种处理情况 修改内容 引用地址没变 所以外部也会受影响

const obj = {
  name: 'ifer',
}

function foo(obj) {
  // 赋值数据类型传参传递的是引用地址,把这个引用地址拷贝给了 obj 局部变量,而这个 obj 引用地址指向的还是曾经的那个空间
  // 所以对这个 obj 局部变量【内容的修改】当然会影响外部的
  obj.name = 'elser'
}
foo(obj)

console.log(obj) // 'elser'

修改引用地址

let obj = {
  name: 'ifer',
}

function foo(obj) {
  // 赋值数据类型传参传递的是引用地址,把这个引用地址拷贝给了 obj 局部变量,而这个 obj 引用地址指向的还是曾经的那个空间
  // 所以对这个 obj 局部变量【引用的修改】当然不会影响外部的
  obj = {
    name: 'elser',
  }
}
foo(obj)

console.log(obj) // 'ifer'

4.说一下你对 Node 的了解

Node.js...它既是开发平台,也是运行环境,也是个新的语言...它本身是基于google的 javascriptv8引擎开发的,因此在编写基于它的代码的时候使用javascript语言 他是一个javascript运行环境,依赖于ChromeV8引擎进行代码解释 特征:单线程、事件驱动、非阻塞I/O,轻量,可伸缩,适于实时数据交互应用 单进程,单线程(一个应用程序对应一个进程,一个进程下面会有多个线程,每个线程用 于处理任务..) Node无法直接渲染静态页面,提供静态服务 Node没有根目录的概念 Node必须通过路由程序指定文件才能渲染文件 Node比其他服务端性能更好,速度更快

16-- 人事

17.生疏面试题

Vue双向绑定的原理

Vue 数据双向绑定原理是通过 数据劫持 + 发布者-订阅者模式 的方式来实现的,首先是通过 ES5 提供的 Object.defineProperty() 方法来劫持(监听)各属性的 getter、setter,并在当监听的属性发生变动时通知订阅者,是否需要更新,若更新就会执行对应的更新函数。

什么是数据劫持

数据劫持比较好理解,通常我们利用Object.defineProperty劫持对象的访问器,在属性值发生变化时我们可以获取变化,从而进行进一步操作。

发布者模式 / 订阅者模式

在软件架构中,发布订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。

这里很明显了,区别就在于,不同于观察者和被观察者,发布者和订阅者是互相不知道对方的存在的,发布者只需要把消息发送到订阅器里面,订阅者只管接受自己需要订阅的内容

Object.defineProperty

Object.defineProperty( obj, prop, descriptor )

三个参数:

obj 要定义的对象

prop 要定义或修改的属性名称或 Symbol

descriptor 要定义或修改的属性描述符

属性描述符

get 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为 [**undefined**] set 属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。 默认为 [**undefined**]

v-model在vue2和vue3中的区别

v-model十个语法糖,它是通过自定义函数@input和自定义属性value进行组件之间的传值,  在vue2中v-model只能使用一次,若想使用多次,需要使用.sync修饰符通过$emit("update:value",@input)来实现  但是在vue3中v-model可以进行多次传值, 写法上也有所差别 父组件绑定v-model:name="name"   子组件利用props接收modelValue,

.sync:

1.父组件 :my-prop-name.sync 子组件@update:my-prop-name 的模式来替代事件触发,实现父子组件间的双向绑定。

2.一个组件可以多个属性用.sync修饰符,可以同时"双向绑定多个“prop”

3..sync针对更多的是各种各样的状态,是状态的互相传递,是status,是一种update操作

函数式组件

一般的组件是声明式组件,是不受控制的 函数式组件  是命令式组件  更加灵活

vue暴露的api h:  把一个组件变成一个VNode(虚拟dom) render :  将VNode渲染成一个真实Dom

函数式组件使用流程

  1. 把已经写好的组件通过 h 函数变成 VNode
  2. 准备一个渲染挂载的节点
  3. 调用render  api  把VNode 挂载到准备好的节点上  完成渲染

脚手架配置流程

第一个版本  V1

第一个版本的功能比较简单,大致为:

  1. 用户输入命令,准备创建项目。
  2. 脚手架解析用户命令,并弹出交互语句,询问用户创建项目需要哪些功能。
  3. 用户选择自己需要的功能。
  4. 脚手架根据用户的选择创建 package.json 文件,并添加对应的依赖项。
  5. 脚手架根据用户的选择渲染项目模板,生成文件(例如 index.htmlmain.jsApp.vue 等文件)。
  6. 执行 npm install 命令安装依赖。

第二个版本 v2

第二个版本在 v1 的基础上添加了一些辅助功能:

  1. 创建项目时判断该项目是否已存在,支持覆盖和合并创建。
  2. 选择功能时提供默认配置和手动选择两种模式。
  3. 如果用户的环境同时存在 yarn 和 npm,则会提示用户要使用哪个包管理器。
  4. 如果 npm 的默认源速度比较慢,则提示用户是否要切换到淘宝源。
  5. 如果用户是手动选择功能,在结束后会询问用户是否要将这次的选择保存为默认配置。

webpack优化

构建时间优化

thread-loader

多进程打包,可以大大提高构建的速度,使用方法是将thread-loader放在比较费时间的loader之前,比如babel-loader

由于启动项目和打包项目都需要加速,所以配置在webpack.base.js

开启热更新

比如你修改了项目中某一个文件,会导致整个项目刷新,这非常耗时间。如果只刷新修改的这个模块,其他保持原状,那将大大提高修改代码的重新构建时间

只用于开发中,所以配置在webpack.dev.js

exclude & include

  • exclude`:不需要处理的文件
  • include:需要处理的文件

合理设置这两个属性,可以大大提高构建速度

webpack.base.js中配置

提升webpack版本

webpack版本越新,打包的效果肯定更好

打包体积优化

构建区分环境

区分环境去构建是非常重要的,我们要明确知道,开发环境时我们需要哪些配置,不需要哪些配置;而最终打包生产环境时又需要哪些配置,不需要哪些配置:

  • 开发环境:去除代码压缩、gzip、体积分析等优化的配置,大大提高构建速度
  • 生产环境:需要代码压缩、gzip、体积分析等优化的配置,大大降低最终项目打包体积

CSS代码压缩JS代码压缩

CSS代码压缩使用css-minimizer-webpack-plugin,效果包括压缩、去重

JS代码压缩使用terser-webpack-plugin,实现打包后JS代码的压缩

用户体验优化

路由懒加载

如果不进行路由懒加载,当用户进入页面会请求所有的ajax ,  导致页面加载速度慢,用户体验差.

Gzip

开启Gzip后,大大提高用户的页面加载速度,因为gzip的体积比原文件小很多,当然需要后端的配合

小图片转base64

对于一些小图片,可以转base64,这样可以减少用户的http网络请求次数,提高用户的体验。

1、微信小程序有几个文件

  • WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。内部主要是微信自己定义的一套组件
  • WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式
  • js逻辑处理,网络请求
  • json小程序设置,如页面注册,页面标题及tabBar

2、微信小程序怎样跟事件传值

给 HTML 元素添加 data-*属性来传递我们需要的值,然后通过 e.currentTarget.datasetonloadparam参数获取。但  data -名称不能有大写字母和不可以存放对象

3、小程序的 wxss 和 css 有哪些不一样的地方?

  • wxss的图片引入需使用外链地址
  • 没有 Body;样式可直接使用 import 导入

4、小程序关联微信公众号如何确定用户的唯一性

使用  wx.getUserInfo方法  withCredentialstrue 时 可获取 <encryptedData,里面有 union_id。后端需要进行对称解密

5、微信小程序与vue区别

  • 生命周期不一样,微信小程序生命周期比较简单
  • 数据绑定也不同,微信小程序数据绑定需要使用{{}},vue 直接:就可以 显示与隐藏元素,vue中,使用 v-ifv-show
  • 控制元素的显示和隐藏,小程序中,使用wx-ifhidden 控制元素的显示和隐藏
  • 事件处理不同,小程序中,全用  bindtap(bind+event),或者  catchtap(catch+event)绑定事件,vue:使用v-on:event 绑定事件,或者使用@event绑定事件
  • 数据双向绑定也不也不一样在 vue中,只需要再表单元素上加上 v-model,然后再绑定 data中对应的一个值,当表单元素内容发生变化时,data中对应的值也会相应改变,这是 vue非常 nice 的一点。微信小程序必须获取到表单元素,改变的值,然后再把值赋给一个 data中声明的变量。

6、小程序的双向绑定和vue哪里不一样

小程序直接 this.data属性是不可以同步到视图的,必须调用:

this.setData({
    // 这里设置
})

7、简述微信小程序原理

  • 微信小程序采用  JavaScript、WXML、WXSS 三种技术进行开发,本质就是一个单页面应用,所有的页面渲染和事件处理,都在一个页面内进行,但又可以通过微信客户端调用原生的各种接口
  • 微信的架构,是数据驱动的架构模式,它的 UI 和数据是分离的,所有的页面更新,都需要通过对数据的更改来实现
  • 小程序分为两个部分 webviewappService 。其中 webview 主要用来展现UI ,appService 有来处理业务逻辑、数据及接口调用。它们在两个进程中运行,通过系统层 JSBridge 实现通信,实现 UI 的渲染、事件的处理

8、小程序的生命周期函数

  • onLoad 页面加载时触发。一个页面只会调用一次,可以在onLoad 的参数中获取打开当前页面路径中的参数
  • onShow() 页面显示/切入前台时触发
  • onReady() 页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互
  • onHide() 页面隐藏/切入后台时触发。 如 navigateTo 或底部 tab 切换到其他页面,小程序切入后台等
  • onUnload() 页面卸载时触发。如 redirectTo 或 navigateBack 到其他页面时

9、哪些方法可以用来提高微信小程序的应用速度

1、提高页面加载速度

2、用户行为预测

3、减少默认 data 的大小

4、组件化方案

10、微信小程序的优劣势

优势

即用即走,不用安装,省流量,省安装时间,不占用桌面 依托微信流量,天生推广传播优势 开发成本比 App

缺点

用户留存,即用即走是优势,也存在一些问题 入口相对传统 App 要深很多 限制较多,页面大小不能超过2M。不能打开超过10个层级的页面

11、怎么解决小程序的异步请求问题

  • 小程序支持大部分 ES6 语法
  • 在返回成功的回调里面处理逻辑Promise异步

12、如何实现下拉刷新

首先在全局 config 中的 window配置 enablePullDownRefresh ,在 Page 中定义onPullDownRefresh 钩子函数,到达下拉刷新条件后,该钩子函数执行,发起请求方法 请求返回后,调用wx.stopPullDownRefresh停止下拉刷新

13、bindtap和catchtap的区别是什么

相同点:首先他们都是作为点击事件函数,就是点击时触发。在这个作用上他们是一样的,可以不做区分

不同点:他们的不同点主要是bindtap是不会阻止冒泡事件的,catchtap是阻值冒泡的

14、小程序页面间有哪些传递数据的方法

1、使用全局变量实现数据传递。在 app.js 文件中定义全局变量 globalData, 将需要存储的信息存放在里面

2、使用  wx.navigateTo与  wx.redirectTo 的时候,可以将部分数据放在 url 里面,并在新页面onLoad的时候初始化

3、使用本地缓存Storage 相关

15、小程序wxml与标准的html的异同?

相同:
  • 都是用来描述页面的结构;
  • 都由标签、属性等构成;
不同:
  • 标签名字不一样,且小程序标签更少,单一标签更多;
  • 多了一些wx:if 这样的属性以及 {{ }}这样的表达式
  • WXML仅能在微信小程序开发者工具中预览,而HTML可以在浏览器内预览;
  • 组件封装不同, WXML对组件进行了重新封装,
  • 小程序运行在JS Core内,没有DOM树window对象,小程序中无法使用window对象和document对象。

16、小程序简单介绍下三种事件对象的属性列表?

基础事件(BaseEvent)

  • type:事件类型
  • timeStamp:事件生成时的时间戳
  • target:触发事件的组件的属性值集合
  • currentTarget:当前组件的一些属性集合

自定义事件(CustomEvent)

  • detail

触摸事件(TouchEvent)

  • touches
  • changedTouches

17、小程序对wx:if 和 hidden使用的理解?

  • wx:if有更高的切换消耗。
  • hidden 有更高的初始渲染消耗。
  • 因此,如果需要频繁切换的情景下,用 hidden 更好,如果在运行时条件不大可能改变则wx:if 较好。

18、微信小程序与H5的区别?

  • 运行环境的不同

传统的HTML5的运行环境是浏览器,包括webview,而微信小程序的运行环境并非完整的浏览器,是微信开发团队基于浏览器内核完全重构的一个内置解析器,针对小程序专门做了优化,配合自己定义的开发语言标准,提升了小程序的性能。

  • 开发成本的不同

只在微信中运行,所以不用再去顾虑浏览器兼容性,不用担心生产环境中出现不可预料的奇妙BUG

  • 获取系统级权限的不同

19、app.json 是对当前小程序的全局配置,讲述三个配置各个项的含义?

  • ``pages字段` —— 用于描述当前小程序所有页面路径,这是为了让微信客户端知道当前你的小程序页面定义在哪个目录。
  • window字段 —— 小程序所有页面的顶部背景颜色,文字颜色定义在这里的
  • tab字段—小程序全局顶部或底部tab

20、小程序onPageScroll方法的使用注意什么?

由于此方法调用频繁,不需要时,可以去掉,不要保留空方法,并且使用onPageScroll时,尽量避免使用setData(),尽量减少setData()的使用频次。

21、小程序视图渲染结束回调?

使用setData(data, callback),在callback回调方法中添加后续操作代码

22、小程序同步API和异步API使用时注意事项?

wx.setStorageSync是以Sync结尾的API为同步API,使用时使用try-catch来查看异常,如果判定API为异步,可以在其回调方法success、fail、complete中进行下一步操作。

23、简述下 wx.navigateTo(), wx.redirectTo(), wx.switchTab(), wx.navigateBack(), wx.reLaunch()的区别

  • wx.navigateTo():保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面
  • wx.redirectTo():关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面
  • wx.switchTab():跳转到 abBar 页面,并关闭其他所有非 tabBar 页面
  • wx.navigateBack():关闭当前页面,返回上一页面或多级页面。可通过getCurrentPages() 获取当前的页面栈,决定需要返回几层
  • wx.reLaunch():关闭所有页面,打开到应用内的某个页面

24、如何封装微信小程序的数据请求的?

1、将所有的接口放在统一的js文件中并导出。

2、在app.js中创建封装请求数据的方法。

3、在子页面中调用封装的方法请求数据。

25、小程序与原生App哪个好?

小程序除了拥有公众号的低开发成本、低获客成本低以及无需下载等优势,在服务请求延时与用户使用体验是都得到了较大幅度  的提升,使得其能够承载跟复杂的服务功能以及使用户获得更好的用户体验。

26、webview中的页面怎么跳回小程序中?

首先要引入最新版的jweixin-x.x.x.js,然后

wx.miniProgram.navigateTo({
url: '/pages/login/login'+'$params'
})

27、小程序关联微信公众号如何确定用户的唯一性?

使用wx.getUserInfo方法withCredentialstrue时 可获取encryptedData,里面有union_id。后端需要进行对称解密。

28、小程序调用后台接口遇到哪些问题?

1.数据的大小有限制,超过范围会直接导致整个小程序崩溃,除非重启小程序;

2.小程序不可以直接渲染文章内容页这类型的html文本内容,若需显示要借住插件,但插件渲染会导致页面加载变慢,所以最好在后台对文章内容的html进行过滤,后台直接处理批量替换p标签div标签为view标签,然后其它的标签让插件来做,减轻前端的时间。

29、webview的页面怎么跳转到小程序导航的页面?

答:小程序导航的页面可以通过switchTab,但默认情况是不会重新加载数据的。若需加载新数据,则在success属性中加入以下代码即可:

success: function (e) {
           var page = getCurrentPages().pop();
           if (page == undefined || page == null) return;
           page.onLoad();
          }

webview的页面,则通过

 wx.miniProgram.switchTab({
         url: '/pages/index/index'
       })

30、微信小程序的优劣势?

优势:
      1、无需下载,通过搜索和扫一扫就可以打开。

      2、良好的用户体验:打开速度快。

      3、开发成本要比App要低。

      4、安卓上可以添加到桌面,与原生App差不多。

      5、为用户提供良好的安全保障。小程序的发布,微信拥有一套严格的审查流程,不能通过审查的小程序是无法发布到线上的。
劣势:
     1、限制较多。页面大小不能超过1M。不能打开超过5个层级的页面。

     2、样式单一。小程序的部分组件已经是成型的了,样式不可以修改。例如:幻灯片、导航。

     3、推广面窄,不能分享朋友圈,只能通过分享给朋友,附近小程序推广。其中附近小程序也受到微信的限制。