常见的面试题合集,

87 阅读11分钟

深拷贝和浅拷贝

深拷贝和浅拷贝都是针对于对象(引用数据类型)的,因为对基本数据类型进行拷贝,可以直接将值赋给新变量,这时不管修改哪一个变量都不会对另外一个有影响。而对于对象来说进行拷贝时,如果将对象赋值给一个新变量,他俩的内存地址是同一个,所以只要有一个修改了,另外一个也会跟着改变。

如果对象的属性只有一层,就可以用浅拷贝,如果对象属性有多层(对象的属性是引用数据类行),就需要用深拷贝

  1. 浅拷贝 可以用 扩展运算符{...obj} Object.assign({},obj) 数组用 slice()
var obj = {
    name:'zs',
    age: 15,
}
var a = {...obj}
console.log(a);
// 修改 obj的属性
a.name = 'cc'
console.log(obj);
  1. 深拷贝 可以用 JSON.parse(JSON.stringify(Obj)) 或 递归 使用JSON.parse(JSON.stringify(Obj))的缺点 --- 会造成数据丢失和数据异常 --- function、undefined 直接丢失 --- NaN、Infinity 和-Infinity 变成 null --- RegExp、Error对象只得到空对象;
var obj = {
a: 1,
b: 2,
c: {
  c1: 1,
  c2: 2
}
}
var str = JSON.stringify(obj)
var obj1 = JSON.parse(str)
// 修改 obj的属性
obj.c.c1 = 20
console.log(obj1)
var obj = {
      a: 1,
      b: {
        sing: function () {
          console.log('唱歌')
        }
      }
    }
    var str = JSON.stringify(obj)
    var obj1 = JSON.parse(str)
    // 修改 obj的属性
    console.log(obj1); 

substr与substring的区别

首先 substr()与substring() 都是用来做字符串截取 substr是从指定位置开始截取指定长度的字符串 (包括头跟尾) substring是从指定位置开始截取指定长度的字符串 (包括头不包括尾)

slice和splice的区别

  1. slice用于从指定值开始截取并返回新数组,不会改变原数组 slice(起始索引,结束索引) 接受两个参数 (包括头不包括尾) --- start为起始索引 从该索引提取原数组元素,如果是负数,则从末尾开始 --- end,结束索引,在该索引结束提取原数组元素,如果end被省略,slice会一直到末尾;如果end大于数组的长度,也会提取到数组末尾。
  2. splice会改变原数组,他通过删除或者替换现有元素或者原地添加新的元素来修改数组,并且以数组形式返回被修改的内容。 splice(起始索引,删除的个数,替换或者添加的元素) 有三个参数 第一个参数,指定修改的开始位置,如果超出数组的长度,则从数组末尾开始添加;如果是负值,从数组末尾开始第几位;如果负数也大于数组的长度,则开始位置为0。 第二个参数 表述要移除的数组元素的个数。 (可选) 第三个参数 表示要添加进数组的元素。(可选)

事件循环Event Loop — 宏任务和微任务

浏览器的事件循环分为同步任务和异步任务;所有同步任务都在主线程上执行,形成一个函数调用栈(执行栈),而异步则先放到任务队列(task queue)里,任务队列又分为宏任务(macro-task)与微任务(micro-task)。“执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。只要主线程空了,就会去读取”任务队列”,这就是JavaScript的运行机制。这个过程会循环反复。

微任务和宏任务都是异步任务 微任务比宏任务先执行 同步任务->微任务->宏任务

watch和计算属性

computed 计算属性:依赖其它属性值,并且computed的值有缓存,只有它依赖的属性值发生改变,下一次获取computed的值时才会重新计算computed的值。 watch 侦听器:没有缓存,每当监听的数据变化时都会执行

js数据类型和数据类型检测

  1. 数据类型从大的方向来说分为二种
  • 基本数据类型 字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol (nbs) Symbol 表示独一无二的值,避免了属性名的冲突
  • 复杂数据类型 对象(Object)、数组(Array)、函数(Function)
  1. 检测数据类型 typeof 检测 存在的问题:null 或者数组打印出来也是 object instanceof (只能检测复杂数据类型) 返回值是 true 或者 false Object.prototype.toString.call(要检测的数据值)

生命周期,父子组件生命周期

  1. 什么是生命周期 组件实例从创建到销毁的过程就是生命周期

  2. vue有四个阶段 创建 挂载 更新 销毁

  3. 创建 有二个钩子 beforeCreate created 在工作一般用的是created 拿ajax数据

  4. 挂载 有二个钩子 beforeMount mounted 在工作中可以在mounted 里面去操作DOM

  5. 更新阶段 beforeUpdate updated 在工作的时候可以在updated里面 拿到最新的DOM效果

  6. 销毁阶段 beforeDestroy destroyed 我们可以在beforeDestroy中 去清理当前组件中有的定时器和DOM监听的事件

  7. 如果面试官继续问,就需要答keep-alive相关的钩子二个 activated 激活,deactivated 失活

  8. 父子组件生命周期 341 121 121. 永远是调用的时候先父后子 完成的时候先子后父

父组件 beforeCreate 父组件 created 父组件 beforeMount 子组件 beforeCreate 子组件 created 子组件 beforeMount 子组件 mountd 父组件 mountd

父组件 beforeUpdate 子组件 beforeUpdate 子组件 updated 父组件 updated

父组件 beforeDestroy 子组件 beforeDestroy 子组件 destroyed 父组件 destroyed

new的过程 new 在执行时会做四件事情

  1. 在内存中创建一个新的空对象。
  2. 让this指向这个对象。
  3. 执行构造函数里面的代码,给这个新对象添加属性和方法。
  4. 返回这个新对象(构造函数不需要return)

原型链

JS中每个函数都存在有一个原型对象属性prototype。并且所有函数的默认原型都是Object的实例 简单理解就是原型组成的链,原型对象也是一个对象,也有__proto__属性,指向原型对象的原型 就是Object 在往上就是 null

如何实现三角形

  1. 利用高宽为零的容器和透明的border
  2. 利用线性渐变linear-gradient
  3. 使用“transform:rotate”配合“overflow:hidden”
  4. 利用“&#9660”、“&#9650”等字符绘制。
<div class="box"></div>
<div class='bottom'></div>
.box{
        width: 0;
        height: 0;
        border-left: 3.125rem solid red;
        border-right: 3.125rem solid black;
        border-top: 3.125rem solid green;
        border-bottom: 3.125rem solid gold;
      }
.bottom {

  border: 3.125rem solid transparent;

  border-top: 3.125rem solid deeppink;

}

v-model底层原理

v-model就是 v-bind:val 和 v-on:input 事件 的语法糖 (简写)

<input v-model="msg" />
<input :value="msg" @input="msg=$event.target.value" /> 
data () {
   return {
       msg: 'hello'
   }
}

computed和 watch哪个可以异步

computed 需要 retrun 不支持异步, 而 watch 不需要 支持异步操作 当computed内有异步操作时是无法监听数据变化的;

观察者模式和发布订阅模式的理解

  1. 观察者模式是 一对多的关系 主体有两个 分别是 被观察者Dep 和观察者 watcher 这种方式是两个主体直接关联,缺点就是耦合性很强
  2. 发布订阅模式 主体有三个 分别是 发布者 调度中心(事件通道) 和订阅者 其中发布者和订阅者互相没有关系的 是通过调度中心关联的 是解耦的 这两种模式的区别就在于 发布订阅模式 比 观察者模式 多了一个调度中心 从而实现解耦

let const var区别

let和const是ES6新增的声明变量的关键词,之前声明变量的关键词是var。

  1. -let
  2. var定义的变量,可以预解析提前调用的结果是undefined,let定义的变量不能预解析,提前调用的结果是 报错。
  3. var定义的变量,变量名称可以重复,效果是重复赋值,let定义的变量不能重复,否则执行报错。
  4. var定义的变量作用域是全局/局部作用域。let定义的变量如果在{}中只能在{}中调用。
  5. 在循环语句中var定义的循环变量和使用let定义的循环变量。执行原理和执行效果不同。 2. -const
  6. var定义的变量,可以预解析提前调用的结果是undefined,const定义的变量不能预解析,提前调用的结果是 报错。
  7. var定义的变量,变量名称可以重复,效果是重复赋值,const定义的变量不能重复,否则执行报错。
  8. var定义的变量作用域是全局/局部作用域。let定义的变量如果在{}中只能在{}中调用。
  9. const 定义的变量存储的数据数值不能改变,也就是const定义的变量,不能重复赋值。
    1. 代码演示
    1. 提前调用报错
        // 提前调用 预解析
        console.log( int1 );
        // // 提前调用 结果是报错
        console.log( int2 );
 
        // var 定义的变量 
        var int1 = 100 ;
        let int2 = 200 ;.
    2. constlet 定义的变量不能重复
            // var 定义的变量 
        var int1 = 100 ;
        let int2 = 200 ;
 
        // 变量名称重复 重复赋值效果
        var int1 = '北京' ;
        console.log( int1 );
 
        // 变量名称重复 结果是报错
        let int2 = '上海' ;
    3. constlet定义的变量如果在{}中只能在{}中调用
            // 在 {} 中 使用 let 定义变量 只能在 { } 中 使用
        // 如果需要在 { } 中 对 let 定义的变量 进行操作 
        // 提前定义 变量 在 {} 中 进行赋值操作
        if( true ){
            var a = 300 ;
            let b = 400 ;
            // let 声明的变量 在 { } 中可以调用
            // 对 {} 外定义的变量 进行赋值操作
            console.log( b );
        }
 
        console.log( a ) ; 
 
        // let 声明的变量 在 { } 外 不能调用 
        console.log( b );
    4. const定义的变量不能重复赋值。 
            // const 定义的变量 不能重复赋值
 
        // const c = 100 ;
        // c = 200 ;
        // 结果是报错
 
        const arr = [1,2,3,4,5] ;
        // 只是修改引用数据类型中,数据单元存储的数据
        // 没有修改 arr变量中 存储的引用数据类型的内存地址
        arr[0] = '北京' ;
        console.log( arr );

路由传值的方法,各自异同

  1. 父向子 props 子组件通过props接收父组件传过来的值
  2. 子向父 父组件: @自定义事件名="父methods函数" 子组件:this.$emit("自定义事件名", 传值) - 执行父methods里函数代码
  3. 跨组件传值 事件总线(event-bus) EventBus/index.js- 定义事件总线bus对象 组件A eventBus.emit(事件,值)组件BeventBus.emit('事件名',值) 组件B eventBus.on('事件名',函数体)
  4. vuex
  5. v-slot
  6. provide/inject
  7. refs/refs/panent/children/children/root
  8. attrs/attrs/listener

组件之间嵌套传值最顶层和最底层怎么传 vue provide ineject(依赖注入)

有时一个页面,其实由许多组件构成,并且这些组件,层层嵌套,层次可能很深。这时问题就来了,假如有一些参数,从顶层组件就开始设置或提供,然后最底层的组件又需要,层层传递,不仅繁琐、容易出错,反而不利于代码的可维护和可读。

有没有一种方法,可以直接由顶层组件传播到最底层呢?或者说,可以让最底层能直接接收到顶层的参数呢? 有的,provide + inject

。顶层组件provide,底层组件inject。但是,这只能保证一次成功,以后参数改变了,底层并不能感知。这时又要加上computed和watch,并且参数是函数类型,各种方法一齐上,才能奏效。

// 父级组件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}

// 子组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
}

flex1 的理解

  1. flex-grow 定义项目的的放大比例; 默认值为0 即使存在剩余空间,也不会放大 flex-grow 当父级盒子大于子级盒子的宽度 子级盒子可以平分父级盒子 剩余的宽度
  2. flex-shrink 定义项目的缩小比例 默认值为1 如果空间不足 该项目将缩小 flex-shrink 当父级盒子小于子级盒子的宽度 子级盒子缩小 子级盒子超出父级盒子的宽度
  3. fles-basis 定义在分配多余空间之前,项目占据的主轴空间(main size),浏览器根据此属性计算主轴是否有多余空间。相当于设置初始值 同时设置width 又设置fles-basis 当fles-basis大于自身内容宽度是,不管width是否设置,fles-basis优先级高 当fles-basis和width都小于自身内容宽度时 fles-basis和width哪个值大,宽度就是哪个 当fles-basis设置值小于自身内容宽度,而width大于自身宽度时, 则宽度为自身内容宽度

img标签 alt和title的区别

  1. title:通常当鼠标滑动到元素上的时候显示
  2. alt:alt是img标签的特有属性,是图片内容的等价描述,用于图片无法正常加载时显示

link和@import到底有什么区别?

  1. link属于html标签,而@import是css提供的。
  2. 页面被加载时,link会同时被加载,而@import引用的css会等到页面被加载完再加载的。
  3. 兼容性问题:@import只在IE5以上才能识别,而link是html标签,无兼容性问题。
  4. 权重问题: @import的权重要高于link。
  5. DOM操作: DOM可以操作link中的样式,而不可以操作@import中的样式。

防抖和节流

  1. 防抖 防抖的意思是,在连续的操作中,无论进行了多长时间,只有某一次的操作后 在指定的时间内没有再操作,这一次才会被判定有效。 具体应用场景:可以用于搜索框输入关键字过程中实时 请求服务器匹配搜索结构 如果不进行处理 那么就是输入框的内容一直在变化,导致一直发送请求, 如果惊醒防抖处理,结果就是当我们输入内容完成后,一定时间内(比如1秒)没有再输入,这是才会触发请求

  2. 节流 节流的意思是,规定时间内,只能触发一次。比如我们设定3秒,在这个时间内 无论点击了按钮多少次,他都只会触发一次。 具体应用场景:一般用于抢购的时候,由于有很多人在快速点击按钮,如果每次点击都发送请求,就会给服务器造成巨大的压力,但是进行节流后,就会大大减少请求次数

    搜索框输入关键字的时候 应该选择使用防抖 防止表单按钮呗多次触发 我们应该选择使用节流 而不是防抖

重绘于回流

  1. 重绘: 当渲染树中的一些元素需要更新属性,而这些属性只是影响元素的外观、风格,而不会影响布局的操作,比如 background-color,我们将这样的操作称为重绘。

  2. 回流:当渲染树中的一部分(或全部)因为元素的规模尺寸、布局、隐藏等改变而需要重新构建的操作,会影响到布局的操作,这样的操作我们称为回流。

  3. 常见引起回流属性和方法:

任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发回流。 (1)添加或者删除可见的 DOM 元素;

(2)元素尺寸改变——边距、填充、边框、宽度和高度

(3)内容变化,比如用户在 input 框中输入文字

(4)浏览器窗口尺寸改变——resize事件发生时

(5)计算 offsetWidth 和 offsetHeight 属性

(6)设置 style 属性的值

(7)当你修改网页的默认字体时。

怎么实现右边固定,左边自适应 (2种以上 bfc,或 flex实现)

  1. 利用flex布局,首先设置display: flex,让2个盒子一行显示,设置右边的固定宽度px,然后设置左边flex:1,自动填满剩余空间。
  2. 利用定位和overflow。首先设置右边盒子position:absolute(或者fixed),设置右边盒子固定宽度,由于这种定位形式会脱离标准流,那么设置左边盒子overflow:hidden
  3. 利用浮动float和margin 首先设置右边盒子float:right,设置右边盒子固定宽度,然后设置左边margin-right 为右边盒子的宽度

你有在请求拦截器里面操作过什么

1.在进行鉴权的时候;我们每个接口都需要携带token; 这时候就可以用拦截器来使token自动增加 请求拦截就是在你请求的时候会进行触发!只要是你发送一个 axios 请求就会触发!所以我们主要用它做我们的loading 加载和数据的权限验证,包括我们所有的数据预加载也可以实现。

数组去重方法

  1. 双重for循环 设置一个变量为没有重复的状态,如果有相同的值则跳过,不相同就push进数组
var arr = [1,1,2,2,3,3,4,4,5,5] 
var arr1 = []
var flag = true  // 状态为没有重复的
for(var i=0; i<arr.length; i++){
  flag = true
  for(var j=0; j<arr1.length; j++){
    if(arr1[j] == arr[i]) {
        flag = false
    }
    if(flag) {
        arr1.push(arr[i])
    }
  }
}
console.log(arr1)
  1. 遍历数组,建立新数组,利用indexOf判断是否存在于新数组中,不存在就push到新数组中
var arr = [1,1,2,2,3,3,4,5,6,4]
var arr1 = []
for(var i=0; i<arr.length; i++){
    if(arr1.indexOf(arr[i]) == -1){
        arr1.push(arr[i])
    }
}
console.log(arr1)
  1. 用lastIndexOf和indexOf查找
  var arr = [1,1,2,2,3,3,4,4,5,5] 
  var arr1 = []
for(var i=0; i<arr.length; i++){
  if(arr1.lastIndexOf(arr[i]) == -1){
    arr1.push(arr[i])
  } else{
    if(arr1.indexOf(arr[i]) == -1){
        arr1.push(arr[i])
    }
  }
}
console.log(arr1)
  1. 利用set成员唯一性进行去重
  var arr = [1,1,2,2,3,3,4,4,5,5] 
  var arr1 = Array.from(new Set(arr)) // Array from 将类数组转换成数组
  console.log(arr1);
  1. 利用对象属性不能重复进行数组去重
var arr = [1,1,2,2,3,3,4,4,5,5] 
var arr1 = []
var obj = {}
for(var i=0; i<arr.length; i++){
    obj[arr[i]] = '1'
}
for(key in obj){
    arr1.push(Nunber(key))
}
console.log(arr1);

router和route的区别

  1. router 用来做路由间的跳转 router.push()router.push() --- router.go() --- $router.back()
  2. route 用来获取获取动态路由参数 this.route.paramsthis.route.params ---- this.route.query

promise的3种状态 pending、fulfilled、rejected(未决定,履行,拒绝)

  1. pending 初始状态。创建Promise对象时,且没有调用resolve或者是reject方法,相当于是初始状态。这个初始状态会随着你调用resolve,或者是reject函数而切换到另一种状态。
  2. fulfilled 成功状态 pending -> resolve
  3. rejected 失败状态 pending -> rejected