前端面试题

1.ES5ES6的新特性

  • ES5新增的特性
    • 数组的方法
      • Array.indexOf()
      • Array.findIndex()
      • Array.find()
      • Array.includs()
      • Array.map()
      • Array.filter()
      • Array.reduce()
      • Array.reduceRight()
      • Array.some()
      • Array.ervey()
    • 对象新增了取值器和赋值器 Getter Setter
    • 增加了JSON对象,并且为对象提供了两种方法
      • JSON.parse()
      • JSON.stringify()
  • ES6新增的特性
    • 声明变量的关键字
      • let
      • const
    • 新增了箭头函数 ()=>{}
    • Promise 构造函数 用来解决回到地狱
    • async await
    • 面向对象 Class类
    • 解构语法
    • 默认参数语法

2.简述Promise怎么使用?

  • Promise是为了解决多层级回调的使用,也就是回调地狱
  • Promise有3个状态,分别是
    • pending 【未执行】
    • fulfilled 【执行成功的回调】
    • rejected 【执行失败的回调】
  • Promise 接受一个参数为回调函数,该回调函数接受接受两个参数,分别代表着成功的回调和失败的回调
  • 并且可以通过连续打点调用来执行回调返回值的值,上一次.then返回值将作为下一次.then接受的参数

3.ES5中怎么使用Promise,请简单实现。

  • 该方法适用于异步函数

  • 在回调函数内部 请只执行一次reject() 或者 resolve() 如果执行多次,reject 和 resolve都会执行

    // 因为Promise是使用 new 关键字来进行实例化,因此Promise函数应该定义为一个构造函数
      function ES5Promise(callback) {
        // 缓存this
        var _this = this;
        // 定义Promise的状态
        this.PromiseState = 'pending';
        // 定义存储Promise成功回调的数组
        this.saveResolveArray = [];
        // 定义存储Promise失败的回调函数
        this.saveRejectArray = [];
        // 定义存储数据的变量
        this.value = null;
    
        // 定义成功的回调函数 以为函数为普通函数 函数内的this指向的是window 因此要缓存this
        function resolve(resolveValue) {
          // 更改状态
          _this.PromiseState = 'fulfilled';
          // 存储一下数据
          _this.value = resolveValue;
          // 遍历执行成功状态数组内的函数
          for (var i = 0; i < _this.saveResolveArray.length; i++) {
            _this.value = _this.saveResolveArray[i](_this.value);
          }
          // 清空存储成功状态函数的数组
          _this.PromiseState = [];
        }
    
        // 定义失败时候执行的回调函数
        function reject(rejectValue) {
          console.log('失败状态开始执行');
          // 更改Promise状态
          _this.PromiseState = 'rejected';
          // 缓存输数据
          _this.value = rejectValue;
          // 执行失败回调函数数组内的所有函数
          for (var i = 0; i < _this.saveRejectArray.length; i++) {
            _this.value = _this.saveRejectArray[i](_this.value);
          }
          // 清空失败回调函数数组
          _this.saveRejectArray = [];
        }
    
        // 执行回调函数 使用try catch 监听是否有错误
        try {
          callback(resolve, reject);
        } catch (err) {
          reject(err);
        }
      }
      // 为Promise拓展.then方法
      ES5Promise.prototype.then = function (success, fail) {
        // 如果状态为未执行,那么存储回调函数
        if (this.PromiseState === 'pending') {
          success && this.saveResolveArray.push(success);
          fail && this.saveRejectArray.push(fail);
        } else if (this.PromiseState === 'fulfilled') {
          // 如果此时函数的状态为成功的状态 那么直接执行成功的回调
          // 并且传递参数
          success && success(this.value);
        } else {
          // 当函数的状态为失败的时候 执行失败的回调 并且传递参数
          fail && fail(this.value);
        }
        // 为了连续打点调用 返回this
        return this;
      };
      // // 测试
      new ES5Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('success');
        }, 100);
      })
        .then(
          (success) => {
            console.log(success, 'then输入出来的'); // success then输入出来的
            return 'first then data';
          },
          (fail) => {
            console.log(fail);
          }
        )
        .then((res) => {
          console.log('连续打点调用', res); // 连续打点调用 first then data
        });
    
    
    
    

4. JS的那些操作会造成内存泄露?

  • 造成内存泄露的原因

    • 初始化未经声明的变量,会挂载到全局window上,会造成内存泄露

    • 普通函数内使用this挂载的变量,也会挂载到全局window上

    • 定时器被开启器但是未被清除会造成内存泄露

    • 页面中的DOM被删除的时候,但是DOM身上添加的事件却未被清除的时候会造成内存泄露。

    • 闭包内声明的变量,多次使用未清除的情况下也会造成内存泄露

  • 解决办法

    • 声明的变量在使用完毕后,清除声明的变量
    • 定时器在不需要的时候,清除定时器
    • 当页面DOM事件被删除后,自身所挂载的DOM事件应该也被删除
    • 闭包内的变量在不再使用的情况下应该清空

5.请解释JSONP的工作原理,以及它为什么不是真正的AJAX

  • JSONP的原理是使用了 script 标签可以不受同源策略的影响而实现的

  • JSONP的实现原理

    • 通过document.createElement(‘script’); 创建一个script标签
  • 为创建的script标签指定src 和 type类型

    • 创建一个接受数据的函数
  • 在执行src的时候,将该函数的名称以query串的方式进行携带

    • 后端配置,执行该方法返回数据
  • JSONP前端代码:

      // 创建一个script
      // 定义一个接受变量的函数
      function acceptData(data) {
        console.log(data, '接受数据');
      }
    
      function JSONP(data) {
        // 创建一个script标签
        let script = document.createElement('script');
        // 拼接字符串
        let str = '?callback=' + data.fnName;
        for (let key in data.data) {
          str += '&' + key + '=' + data.data[key];
        }
        str = data.data ? str : str.splice(0, str.length - 1);
        // 指定script的类型
        script.type = 'text/javascript';
        // 指定src
        script.src = data.url + str;
        // 上树
        document.body.appendChild(script);
      }
      JSONP({
        url: 'http://localhost:3001/login',
        data: {
          color: 'red',
          num: 100,
        },
        fnName: 'acceptData',
      });
    
  • JSONP后端代码

    // 引入express
    const express = require('express');
    
    // 注册应用程序
    const app = express();
    
    // login端口
    app.get('/login', (req, res) => {
      // 定义返回的数据
      res.end(`${req.query.callback}(${100})`);
    });
    
    // 开启端口
    app.listen(3001, () => {
      console.log('port listen at 3001');
    });
    
  • JSONP为什么不是Ajax

    • Ajax是通过 实例化 XMLHttpRequest的 方式去获取数据,JSONP是通过script标签的方式去获取数据
    • Ajax可以发送get post请求,但是JSONP只能发送get请求

6. JS中的微任务和宏任务?

  • JS中的宏任务【异步】
    • setInterval、setTimeout()、IO(请求数据)
  • JS中的微任务【同步】
    • Promise、直接执行函数
  • 执行顺序:1.找同步, 2 微任务, 3 微任务交替执行, 4 异步

7. 对JS中this的理解?

  • 在普通模式下
    • 全局作用域中的this指向的是window
    • 普通函数内的this执行的是window
    • 定时器内的this指向的也是window
    • 箭头函数内的this指向的是定义时作用域内的this
    • 事件中的this指向的是事件的调用者
    • 构造函数内的this指向的是实例化对象
  • 严格模式下【use strict】
    • 全局作用域下的this指向的是window
    • 普通函数内的this指向的是undefined
    • 定时器内的this指向的是window
    • 箭头函数内的this指向的是定义时的作用域内的this
    • 事件中的this指向的是调用者
    • 构造函数内的this指向的是实例化对象

8. 谈谈对Vue生命周期的理解

  • Vue中声明周期函数一共分为4个阶段
  • 创建期
    • beforeCreate() 在该阶段,实例初始化完成,监听器,数据,方法等还未进行初始化
    • created() 在该阶段,监听器,方法,计算属性,数据等已经初始化完毕,可以访问到实例上的数据
  • 构建期
    • beforeMount() 该声明周期函数在Vue实例被挂载之前执行,此时还不能访问到DOM元素
    • mounted() 该声明周期在Vue实例初次渲染之前执行,此时已经能访问到DOM元素
  • 存在期
    • beforeUpdate() 该声明周期函数在页面数据发生改变后,DOM视图更新之前触发
    • updated() 该声明周期函数在页面数据更改后,页面虚拟DOM渲染之前执行
  • 销毁期
    • beforeDestroy() 在组件实例被销毁之前执行,此时还能访问到组件实例
    • destroyed() 在组件实例销毁之后执行,此时所有的数据监听器等被移除,所有的子实例被销毁
  • kepp-alive 缓存组件
    • activated() 在缓存的组件被激活的时候调用
    • deactivated() 在缓存组件被失活时调用
  • errorCaptured()在子组件发生报错时候调用

9. Vue数据双向绑定是怎么实现的?

  • Vue中数据是用v-model来实现数据双向绑定的,v-model也算是v-bind 和 v-on 的语法糖,

  • 相当于在v-bind绑定value 使用v-on绑chang事件,当输入框内的值发生改变的时候,重新为value赋值

  • 他的实现原理是,通过Es5提供特性功能,结合发布者和订阅者模式的方式,通过 Object.defineProperty()来为属性定义get和set特性,在数据发生改变的时候,将消息发布给订阅者,触发响应的监听回调,改变数据,让数据驱动视图发生变化

10. VueX的属性以及怎么使用

  • VueX内的属性有
    • state
      • state:是用来存储数据的
    • getters
      • getters相当于Vue里面的computed计算属性,乐意用来处理数据
    • actions
      • actions: 是用来订阅异步函数的,并且当异步函数成功或者失败的时候,会触发mutations内的同步函数,来提示报错 或者存储数据
      • 通过 this.$store.dispatch(‘方法名称’)来发布同步消息
      • 第一个参数为store 第二个参数为接受的数据
    • mutations
      • 订阅同步消息 第一个参数为 state数据对象 第二个参数为接受的数据
      • 在组件内通过 this.$store.commit(‘方法名称’) 来发布同步消息
    • modules
      • 用来进行模块分割,让页面代码解构清晰

11. Vue的两大核心是什么?

  • 核心1:数据双向绑定

    Vue 响应式核心就是,getter 的时候会收集依赖,setter 的时候会触发依赖更新
    
    vue将遍历data中对象的所有property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
    
    这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。
    
    每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。
    
    getter 的时候我们会收集依赖,依赖收集就是订阅数据变化watcher的收集,依赖收集的目的是当响应式数据发生变化时,能够通知相应的订阅者去处理相关的逻辑。
    
    setter 的时候会触发依赖更新,之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染
    
  • 核心2:组件系统

    1 模板(template):模板声明了数据和最终展现给用户的DOM之间的映射关系。
    
    2 初始数据(data):一个组件的初始数据状态。对于可复用的组件来说,这通常是私有的状态。
    
    3 接受的外部参数(props):组件之间通过参数来进行数据的传递和共享。
    
    4 方法(methods):对数据的改动操作一般都在组件的方法内进行。
    
    5 生命周期钩子函数(lifecycle hooks):一个组件会触发多个生命周期钩子函数,最新2.0版本对于生命周期函数名称改动很大。
    
    6 私有资源(assets):Vue.js当中将用户自定义的指令、过滤器、组件等统称为资源。一个组件可以声明自己的私有资源。私有资源只有该组件和它的子组件可以调用
    

12. Vue路由传参的方式以及如何接收传递的参数

  • 传递参数的方式
    • 通过query的方式进行传递数据
    • 通过params的方式传递数据
    • 通过动态路由的方式传递参数
  • 接收数据的方式
    • 通过query方法传递的数据可以在组件内通过$route.query.xxx 来接受
    • 通过params的方式传递的数据可以通过$router.params.xxx来接受
    • 通过动态路由的方式传递的数据,可以通过$router.params.xxx 来接受

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

  • 当一个组件内有子组件的时候,子组件的生命周期函数会在父组件的beforeMounet和 父组件的mounted生命周期函数之间执行
  • 指向顺序为 【当页面无数据改变的时候】
    • beforeCreate() 【父】
    • created() 【父】
    • beforeMounte( ) 【父】
    • beforeCreae() 【子】
    • created() 【子】
    • beforeMounte 【子】
    • mounted 【子】
    • mounted 【父】
  • 当数据发生改变的时候
    • beforeUpdate()
    • upDated()
  • 当组件被销毁的时候
    • beforeDestroy()
    • destroyed()
  • 缓存组件
    • activated()
    • deactivated()

14. 判断一个数据属于什么类型的方法

  • 对于简单数据类型
    • typeof
  • 对于引用类型有3种方式
    • instanceof 判断数据A是不是B的实例
    • constrcutor 判断数据A是不是B的构造函数
    • 最安全的效验方法
      • Object.prototype.toString.call(A)
  • 对于数组 还有 IsArray方法
  • 判断一个数据是不是NaN 用 isNaN() / Number.siNaN()
    • isNaN() 会先对数据类型做一次转换在进行对比
    • Number.isNaN不会对数据类型做转换

15. Vue中的data为什么要写为一个函数

  • 组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。

16. 简述一下BFC

  • BFC是Block FormattingContext块级格式化上下文,
  • BFC的特点
    • BFC内部的盒子会垂直排列
    • 盒子垂直方向的距离由margin决定,相邻的像个元素垂直方向的margin会发生重叠
    • 盒子内的每个盒子左边和外面最大的盒子左边紧贴
    • BFC区域不会与浮动的区域发生重叠
    • 计算BFC的高度时,浮动元素也参与计算
    • BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之也如此;
  • BFC产生的条件
    • 页面根元素
    • float属性值不为nonde
    • position属性不为absolute和 fixed
    • overflow不为hidden

17. 盒子水平垂直居中的方法

  • 在不知道内部盒子大小的情况下

     <style>
        .box {
          width: 300px;
          height: 300px;
          position: relative;
          background-color: skyblue;
        }
        /* 在不知道box1大小的情况下 */
        .box1 {
          position: absolute;
          top: 20px;
          left: 20px;
          bottom: 20px;
          right: 20px;
          background-color: pink;
        }
      </style>
      <body>
        <div class="box">
          <div class="box1"></div>
        </div>
      </body>
    
  • 在知道内部盒子大小,不知道外部盒子大小的情况下

    <style>
        /* 在不知道box大小的情况下 知道box1大小 */
        .box1 {
          width: 100px;
          height: 100px;
          background-color: pink;
        }
        .box {
          padding: 50px;
          background-color: skyblue;
          display: inline-block;
        }
      </style>
      <body>
        <div class="box">
          <div class="box1"></div>
        </div>
      </body>
    
  • 内外盒子都知道大小的情况下

    • 方法1

      <style>
          /* 内外盒子都知道大小的情况下 */
          .box {
            width: 300px;
            height: 300px;
            background-color: skyblue;
            /* 不添加overflow:hideen;父元素会发生margin塌陷 */
            overflow: hidden;
          }
          .box1 {
            width: 100px;
            height: 100px;
            background-color: pink;
            margin: 100px auto;
          }
        </style>
        <body>
          <div class="box">
            <div class="box1"></div>
          </div>
        </body>
      
    • 方法2

      <style>
          /* 内外盒子都知道大小的情况下 */
          .box {
            width: 300px;
            height: 300px;
            position: relative;
            background-color: skyblue;
          }
          .box1 {
            width: 100px;
            height: 100px;
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            background-color: pink;
          }
        </style>
        <body>
          <div class="box">
            <div class="box1"></div>
          </div>
        </body>
      
    • 方式3

       <style>
          /* 内外盒子都知道大小的情况下 */
          .box {
            width: 300px;
            height: 300px;
            background-color: skyblue;
            display: flex;
            justify-content: center;
            align-items: center;
          }
          .box1 {
            width: 100px;
            height: 100px;
            background-color: pink;
          }
        </style>
        <body>
          <div class="box">
            <div class="box1"></div>
          </div>
        </body>
      
    • 方法4

       <style>
          /* 内外盒子都知道大小的情况下 */
          .box {
            width: 300px;
            height: 300px;
            background-color: skyblue;
            text-align: center;
          }
          .box::after {
            content: '';
            height: 100%;
            display: inline-block;
            vertical-align: middle;
          }
          .box1 {
            width: 100px;
            height: 100px;
            display: inline-block;
            background-color: pink;
            vertical-align: middle;
          }
        </style>
        <body>
          <div class="box">
            <div class="box1"></div>
          </div>
        </body>
      

18. 说一下路由守卫都有哪些钩子函数?

  • 全局下的路由守卫有

    • 全局前置路由守卫:router.beforeEach()
    • 全局后置路由守卫:router.afterEach()
    • 全局解析路由守卫:router.beforeResolve
  • 路由独享导航守卫

    • beforeEnter()
  • 组件内的路由导航守卫

    • beforeRouterEnter()
    • beforeRouterUpdata()
    • beforeRouterLeave()
  • 导航守卫触发流程

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

19. Vue中路由跳转的方式

  • 使用router-link标签
  • router.push()
  • router.replace()

20. 怪异盒模型和标准盒模型

  • 怪异盒模型 【box-sizing:border-box;】
    • 怪异盒模型的盒子大小计算为:content(padding + border ) + margin
  • 标准盒模型 【box-sizing:content-box;】
    • 标准盒模型的大小计算规则为:margin+border+padding+content

21.flex的属性有哪些

  • flex-direction: 主轴的方向

    • row
    • row-reverse
    • column
    • column-reverse
  • justifly-content::主轴的对齐方式

    • flex-end
    • flex-state
    • space-between
    • space-around
    • center
  • 是否换行 flex-wrap:

    • wrap
    • nowrap
    • wrap-reverse
  • align-item 侧轴的对齐方式

    • flex-end
    • flex-state
    • stretch
    • baseline
    • center
  • 多行侧轴对其方式align-content

    • flex-end
    • flex-state
    • space-between
    • space-around
    • center
    • stretch

22. ES56深拷贝和浅拷贝

  • 浅拷贝
    • 浅拷贝是使用assign来实现,只拷贝应用类型的数据,不拷贝引用类型的地址
  • 深拷贝
    • 通过JSON.stringfiy 和JSON.parse()来实现深拷贝

23. Vue中的数据丢失

  • 数据丢失的现象是Vue里面的一个小的Bug,因为有些数据在没有通过Object.defaltProperty设置get set特性的时候,就会造成数据丢失,造成数据丢失的现象有一下几种
    • 在方法内改变data内已经初始化过数组中的某个成员
    • 为data中的对象数据添加一个新的属性值
    • 定义一个未初始化的数据
    • 为data中的数组添加新成员
  • 解决办法
    • 在改变数组中的某个成员值的时候,更新整个数组
    • 使用Vue提供的一些可以操作数组而不会造成数组数据丢失的方法
      • push()
      • pop
      • unshift
      • shilt
      • reverse
      • sort
      • splice
    • 使用this.$set()去更改数据
    • 在更改数据后重置一个不会造成数据丢失的数据

24. call apply 的区别

  • call 和 apply都可以改变this的指向
  • call方法传递数据的时候,当要传递多个数据的时候,第二个参数要写成数组 或者对象
  • apply从第二个参数后面都是传递的数据

25. Vue中父子组件通信

  • 父向子

    • 通过属性绑定的方式传递数据
    • 父组件通过this.$children.xxx获取数据
    • 通过在子组件身上绑定ref的方式触发子组件内的方法或者数据
  • 子向父

    • 父组件通过绑定属性的方法传递一个事件,并且接受数据,子组件通过调用该方法,传递数据
    • 父组件通过模拟DOM事件的方式,传递一个方法,子组件通过this.$emit()的方式触发方法,,并且传递数据
    • 子组件通过this.$parent.xxx可以访问
  • 还可以通过Vuex进行传递

  • 通过本地存储或者回话存储

  • 通过组件树的方式进行传递,数据挂载在Vue实例身上

  • 通过事件总线

    new Vue({
    	el:'#app',
    	render:h=>h(APP),
    	beforeCrate(){
    		Vue.prototype.$bus=this
    	}
    })
    

26. const let var的区别

  • const 用于定义常量,一些不会改变的量,而且必须进行初始化
  • var,let 定义的变量可以被修改,不初始化的时候,得到的是undefined
  • let 声明的变量是局部作用域,声明不会提升
  • var 声明的变量是全局作用域,声明会提升

27. new 一个构造函数的时候,都进行了那些步骤

  • 开辟一个新的内存空间,也就是创建一个新对象。
    • let fn = new Object();
  • 改变函数内的作用域,让该函数内的作用域指向这个新的对象将空对象的原型指向构造函数的原型对象
    • fn.call(this)
  • 该对象继承该函数的原型【更改原型链的指向】
  • 执行函数,将属性和方法加入到this引用对象中
  • 新创建的对象由this引用,并且最后隐式的返回this
    • 如果没有手动返回其他任何对象或返回值是基本类型(Number、String、Boolean)的值,会返回 this 指向的新对象,也就是实例,若返回值是引用类型(Object、Array、Function)的值,则实际返回值为这个引用类型。

28. 怎么获取浏览器地址中的query数据?

  • 简单实现
function getLocationQueryData() {
    let locationQuery = window.location.search.slice(1).split('&');
    // 开始对字符串做处理
    // 先对字符串进行切割
    let sendData = {};
    // 定义一个返回的变量
    locationQuery.forEach((item) => {
      let dealItem = item.split('=');
      sendData[dealItem[0]] = dealItem[1];
    });
    return sendData;
  }

29. js中对节点的操作

  • 创建节点

    • document.createElement(‘div’) // 创建元素节点
    • document.createText(‘文本节点’) // 创建文本节点
  • 添加节点

    • appendChild(node) // 先给一个父元素内部的最后面添加一个子元素元素

    • 向一个父元素内部最前面添加一个子元素

      /**
         * @封装一个像指定元素前面添加元素的方法
         * @ targetNode 目标节点
         * @ addNode 添加的节点
         */
        function appendChildInFirst(targetNode, newNode) {
          // 获取目标节点下的第一个子节点
          // .insertBefore(newNode);
          targetNode.insertBefore(newNode, targetNode.firstChild);
        }
      
  • 删除节点:

    • removeChild 删除指定父节点下的一个子节点
  • 查询节点

    • childrNodes 获取一个节点下的所有节点 包括文本节点 元素节点等
    • children 获取一个节点下的所元素子节点
    • firstChild 获取一个节点下的第一个子节点 包括元素节点 文本节点等
    • firstElementChild 获取一个节点下的第一个元素节点
    • lastChild 获取一个节点下的最后一个节点 包括 文本节点 元素节点
    • lastElementChild 获取一个节点下的最后一个元素节点
    • previousSibling 获取当前节点的前一个兄弟节点,包括文本节点 元素节点等;
    • previousElementSibling 获取当前节点前一个兄弟元素节点;
    • nextSibling 获取当前节点后的兄弟节点 包括文本节点 元素节点等
    • nextElementSibling 获取当前节点后的元素节点
  • 替换节点

    • replaceChild(newNode,oldNode) 从父节点中,用新节点替换老节点。

30. 列举VUE中常用的指令

  • v-model 数据绑定 语法糖为 @
  • v-on 绑定事件
  • v-bind 绑定数据 语法糖 :
  • v-if v-else-if v-self
  • v-show
  • v-slot 插槽 语法糖 #
  • v-for

31. VUE中双向数据绑定是如何实现的

  • Vue中是数据双向绑定是用过v-model 来实现的,v-model相当于v-bind 和 v-on 的语法糖
  • 在Vue初始化的时候,会采用ES5提供的Object.property() 方法为各个属性定义get,set特性方法,在数据发生改变的时候,发布消息给订阅者,触发响应的监听回调
  • 具体步骤为
    • 实现一个监听器:Observer,用来劫持并监听所有的数据,并且设置get和set特性,当数据发生改变的时候,就通知给订阅者
    • 实现一个订阅者:Watcher,可以接收到数据的变化并执行相应的函数,从而更新视图
    • 实现一个解析器:Compile,可以将模板中的变量替换为数据,然后初始化渲染页面视图,并将每个指令对应的节点添加更新函数,添加监听数据的订阅者,一旦有数据变化,触发视图更新函数
  • 通俗点来讲
    • Vue中是数据双向绑定是用过v-model 来实现的,v-model相当于v-bind 和 v-on 的语法糖,在Vue初始化的时候,会采用ES5提供的Object.property() 方法为各个属性定义get,set特性方法来监听数据是否改变,通过在input标签上绑定input事件,来改变数据,从而驱动数据改变视图。

32. v-if 和 v-for的优先级

  • v-for的优先级大于v-if,如果在同一个元素上使用v-for和v-if,那么可能会造成不必要的性能损耗,因此通常不会将v-for和v-if作用正在用一个标签上,一般都是通过v-for做外层循环嵌套,里面的元素使用v-if来进行判断

33. 写一个function,清除字符串前后的空格(兼容所有浏览器)

// 使用ES5的字符串替换的方法清除一个字符串内空白符的方法
function clearBlank(str) {
  return str.replace(/(^\s*|\s*$)/g, '');
}
// 使用最基础的方法进行替换,兼容所有浏览器
function clearBlank1(str) {
  let dealStr = '';
  for (var i = 0; i < str.length; i++) {
    str[i] === ' ' ? '' : (dealStr += str[i]);
  }
  return dealStr;
}

34. 第一个人10岁,第二个比第一个人大2岁,依次递推,倾听递归编写一个方法,可以计算出第8个人多大(js语言编写)

// 将循环变量放在外部执行 
let i = 1;
  // 定义一个函数
  function dealAge(initAge) {
    i++;
    let age = initAge + 2;
    if (i < 8) return dealAge(age);
    return age;
  }
  console.log(dealAge(10)); // 24

// 将循环变量放在内部执行
 function dealAge(initAge, n) {
    let age = initAge + 2;
    n++;
    if (n !== 8) {
      return dealAge(age, n);
    } else {
      return age;
    }
  }

  console.log(dealAge(10, 1));

35. 一个长度不超5位的正整数转换成对应得中文字符串,例如:20876返回 “两万零八百七十六”(js语言编写)

// 这个是比较简单的实现,并没有考虑到当有多位0的时候出现的情况 
function numberToString(num) {
    let string = ['万', '千', '百', '十', ''].slice(5 - +String(num).length);
    let chinese = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
    let str = '';
    // 简化代码
    String(num)
      .split('')
      .forEach((item, index) => {
        str +=
          item === '0'
            ? chinese[Number(item)]
            : chinese[Number(item)] + string[index];
      });
    return str;
  }
  console.log(numberToString(20876)); // 二万零八百七十六
  console.log(numberToString(25689)); // 二万五千六百八十九

// 可以实现 正常5位数的转换
 function numberToString(num) {
    let string = ['万', '千', '百', '十', ''].slice(5 - +String(num).length);
    let chinese = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
    let zero = '零';
    let str = '';
    // 简化代码
    String(num)
      .split('')
      .forEach((item, index) => {
        str +=
          item === '0'
            ? chinese[Number(item)]
            : chinese[Number(item)] + string[index];
      });
    // 因为使用的是repeact 方法,进行赋值字符串,因此应该每次清零 并且从1开始
    for (let i = 1; i < str.length + 1; i++) {
      zero = zero.repeat(i);
      if (str.indexOf(zero) >= 0) {
        zero = '零';
        if (str.length - str.indexOf(zero) === i) str = str.replace(/零+/g, '');
      } else {
        zero = '零';
      }
    }
    return str, str.replace(/零+/g, '零');
  }
  console.log(numberToString(20876)); // 二万零八百七十六
  console.log(numberToString(25689)); // 二万五千六百八十九
  console.log(numberToString(20000)); // 二万五千六百八十九
  console.log(numberToString(20001)); // 二万五千六百八十九

36. 数组如何去重?

  // 数组去重的方法
  let arr = [1, 2, 3, 4, 5, 6, 4, 5, 6];
  // 1. 使用ES6提供的new set方法  不会改变原数组
  console.log([...new Set(arr)]); // [1, 2, 3, 4, 5, 6]

  // 2. 使用循环遍历去重
  function deleteRepeat(arr) {
    let newArr = [];
    arr.forEach((item) => (newArr.includes(item) ? '' : newArr.push(item)));
    return newArr;
  }
  console.log(deleteRepeat(arr)); //[1, 2, 3, 4, 5, 6]

  // 3.使用reduce进行数组去重
  let result = arr.reduce((prev, cur) => {
    if (!prev.includes(cur)) prev.push(cur);
    return prev;
  }, []);
  console.log(result); // [1, 2, 3, 4, 5, 6]

  // 4.使用for循环进行去重
  function deleteRepeatFor(arr) {
    let newArr = [];
    for (let i = 0; i < arr.length; i++) {
      newArr.includes(arr[i]) ? '' : newArr.push(arr[i]);
    }
    return newArr;
  }
  console.log(deleteRepeatFor(arr)); // [1, 2, 3, 4, 5, 6]

37. 如何检测变量是不是数组?

 let arr = [1, 2, 3, 4, 5];
  //  1. 使用isArray来判断
  console.log(Array.isArray(arr)); // true

  // 2. 使用instanceof进行判断
  console.log(arr instanceof Array); // true

  // 3. 使用constructor 进行判断
  console.log(arr.constructor === Array); // true

  // 4. 使用Object.prototype.toString.call()
  console.log(Object.prototype.toString.call(arr) === '[object Array]'); // true

38. 什么是浅拷贝和深拷贝?

  • 浅拷贝中值类型是复制,但是引用类型只是改变了引用地址,而不是真正的复制
  • 深拷贝是不管是值类型还是引用类型都是真正的复制

39. 实现浅拷贝和深拷贝【针对复杂数据类型】

  • 浅拷贝

    • 可以通过Object.assign()方法实现对一个数据的浅拷贝

    • 可以通过直接赋值的方法,实现对一个数据的浅拷贝

    • 只用for循环的方式对一个数据实现浅拷贝,只循环最外面一层

      • 这种方式 可解构语法相似,对于复杂数据类型内的值类型数据,进行的是复制 ,对引用类型进行的是复制引用地址
    • 使用解构语法,对一个数据实现浅拷贝

      • 使用解构语法实现浅拷贝的时候,复杂数据类型内的值类型数据并不会发生改变
      // 浅拷贝
        const obj1 = {
          a: 1,
          b: {
            c: 1,
          },
          fn() {
            console.log(this);
          },
          null: null,
          undefined: undefined,
        };
      
        // 对obj实现浅拷贝
        // 方法1 通过 assign 方法实现浅拷贝
        //   let obj2 = Object.assign(obj1);
        //   console.log(obj2 === obj1); // true
        //   改变引用类型中的值类型数据数据
        //   console.log(obj1.a, obj2.a); //1 1
        //   obj2.a = 100;
        //   console.log(obj1.a, obj2.a); // 100 100
      
        // 2.使用赋值的方式实现浅拷贝
        //   let obj3 = obj1;
        //   console.log(obj3 === obj1); // true
        //   console.log(obj1.a, obj3.a); //1 1
        //   obj3.a = 100;
        //   console.log(obj1.a, obj3.a); // 100 100
      
        // 3. 使用解构语法 实现对一个数据的浅拷贝
        //   let obj4 = { ...obj1 };
        //   console.log(obj4 === obj1); // false
        //   console.log(obj1.a, obj4.a); //1 1
        //   obj4.a = 100;
        //   console.log(obj1.a, obj4.a); // 1 100
        //   obj4.b.c = 100;
        //   console.log(obj1.b.c, obj4.b.c); // 100 100
      
        // 4. 实现浅拷贝[也就是使用for循环遍历层数据]
        function shallowCopy(obj) {
          const sendObj = Array.isArray(obj) ? [] : {};
          for (let key in obj) {
            sendObj[key] = obj[key];
          }
          return sendObj;
        }
        let obj5 = shallowCopy(obj1);
        console.log(obj5.a, obj1.a); // 1 1
        obj5.a = 100;
        console.log(obj5.a, obj1.a); // 100 1
        console.log(obj5.b.c, obj1.b.c); // 1 1
        obj5.b.c = 10;
        console.log(obj5.b.c, obj1.b.c); //10
      
  • 深拷贝

    • 使用JSON.stringfiy 和 JSON.parse() 方法,实现对数据的深拷贝

      • 但是这种方法会过滤掉数据内的函数 和 undefined
    • 使用递归的方式深拷贝

        // 深拷贝
        const obj1 = {
          a: 1,
          b: {
            c: 1,
          },
          fn() {
            console.log(this);
          },
          null: null,
          undefined: undefined,
          arr: [1, 2, 3],
        };
        // 1.通过JSON.stringfly 来进行深复制
        // let obj2 = JSON.parse(JSON.stringify(obj1));
        // console.log(obj1, obj2);
        // console.log(obj1.a, obj2.a); // 1 1
        // obj2.a = 10;
        // console.log(obj1.a, obj2.a); // 1 10
      
        // 2. 使用递归函数进行深拷贝
        function deepCopy(obj) {
          // 判断复杂数据类型属于数组还是对象
          let objClone = Array.isArray(obj) ? [] : {};
          // 如果数据类型是复杂数据类型
          if (obj && typeof obj === 'object') {
            // 使用for in 遍历
            for (key in obj) {
              // 如果传递的数据下有该属性
              if (obj.hasOwnProperty(key)) {
                //判断ojb子元素是否为对象,如果是,递归复制
                if (obj[key] && typeof obj[key] === 'object') {
                  // 将递归出来得记过进行赋值
                  objClone[key] = deepCopy(obj[key]);
                } else {
                  //如果不是,简单复制
                  objClone[key] = obj[key];
                }
              }
            }
          }
          return objClone;
        }
        let obj3 = deepCopy(obj1);
        console.log(obj1.arr[0], obj3.arr[0]); // 1 100
        obj3.arr[0] = 100;
        console.log(obj1.arr[0], obj3.arr[0]); // 1 100
      

40. 怎么理解JS中的原型和原型链

  • 在JS中,对象分为普通对象和函数对象 Object Function

    • Object是最顶层的对象。
    • Function是最顶层的一个构造器
  • 在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象

    - 每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性
    - __proto__是隐式原型
    - prototype 是显示原型
    
  • 如果一个在一个原型上向下查找,最终得到的将会是null

41. 数组扁平化如何实现

  • 数组扁平化就是将一个多维数组 转为 一维数组

      let arr = [
        [1, 2, 3],
        [4, 5, 6],
        12,
        45,
        6,
        [[1, 2, 3], [4, 5, 6], 12, 45, 6],
        [[1, 2, 3], [4, 5, 6], 12, 45, 6],
      ];
    
      // 1. 使用flot方法 原数组不改变
      console.log(arr.flat(Infinity)); // [1, 2, 3, 4, 5, 6, 12, 45, 6, 1, 2, 3, 4, 5, 6, 12, 45, 6, 1, 2, 3, 4, 5, 6, 12, 45, 6]
    
      //   2. 使用递归的方式去进行处理
      function arrReduction(arr) {
        let newArr = [];
        arr.forEach((item) => {
          Array.isArray(item)
            ? (newArr = newArr.concat(arrReduction(item)))
            : newArr.push(item);
        });
        return newArr;
      }
      console.log(arrReduction(arr)); // [1, 2, 3, 4, 5, 6, 12, 45, 6, 1, 2, 3, 4, 5, 6, 12, 45, 6, 1, 2, 3, 4, 5, 6, 12, 45, 6]
    
      // 3. 使用reduce的方法,实现数组扁平化
      //   function arrReduceReduction(arr) {
      //     return arr.reduce((prev, cur) => {
      //       Array.isArray(cur)
      //         ? (prev = prev.concat(arrReduceReduction(cur)))
      //         : prev.push(cur);
      //       return prev.concat();
      //     }, []);
      //   }
      //   // 简写
      function arrReduceReduction(arr) {
        return arr.reduce((prev, cur) => {
          return prev.concat(Array.isArray(cur) ? arrReduceReduction(cur) : cur);
        }, []);
      }
      console.log(arrReduceReduction(arr)); // [1, 2, 3, 4, 5, 6, 12, 45, 6, 1, 2, 3, 4, 5, 6, 12, 45, 6, 1, 2, 3, 4, 5, 6, 12, 45, 6]
    

42. computed和watch的区别

  • computed计算属性是依赖的值改变会重新执行函数,计算属性是取返回值作为最新结果,所以里面不能异步的返回结果。不能写异步逻辑。
  • watch:watch是发生改变的时候才会触发。
  • 当数据进行初始化的时候,watch并不会被出发
  • 当你有一些数据需要随着其它数据变动而变动时,或者当需要在数据变化时执行异步或开销较大的操作时,你可以使用 watch。
    侦听属性是侦听的值改变会重新执行函数,将一个值重新赋值作为最新结果,所以赋值的时候可以进行一些异步操作

43. 简述px、em、rem、的区别

  • px、em、rem都是浏览器窗口显示的单位
  • px 代表像素
  • rem是根据根元素【html】元素的大小进行计算 当根元素的字体大小为16px的时候 16px = 16rem
  • em是相对于自身元素的字号大小,如果没有设置即参照父容器的字号大小或浏览器默认字号大小

44. 怎么让Chrome支持小于12px的文字

  • html,body{-webkit-text-size-adjust:none;}
    

45. 列举块级元素跟行内块内元素各5个

  • 块级元素
    • div
    • p
    • h1-h6
    • table
    • form
    • ul
    • li
    • ol
    • amin
    • section
    • footer
    • header
    • aslid
  • 行内元素
    • prev
    • span
    • a
    • stong
    • del
    • em
  • 行内块
    • img
    • input
    • select

46. call、apply、bind的区别

  • call apply bind 的区别都是实现了改变this指向功能

  • call方法内第一个参数是将this改变到指定的目标对象,从第二个参数开始,传递的为数据,并且会立即执

  • apply 方法内的第一个参数是将this改变到指定的目标对象,第二个参数为传递的数据 ,可以是一个对象,也可以传递arguments ,并且会立即执行

  • bind方法内的第一个参数是将this改变到指定的目标对象,从第二个开始为传递的数据,

      Function.prototype.Bind = function (context) {
        // 缓存this
        var me = this;
        var args = Array.prototype.slice.call(arguments, 1);
        // 定义返回的函数
        return function () {
          return me.apply(
            context,
            args.concat(Array.prototype.slice.call(arguments))
          );
        };
      };
      btn.onclick = demo.icktBind(obj, 100, 200);
    

47. 简述JS闭包

  • 概念
    • 闭包是在A函数内返回一个函数B;函数B访问函数A内私有的变量
  • 特点
    • 可以通过闭包的结构来间接访问函数中的私有数据
    • 保护私有变量,不会污染全局
    • 作用域空间不被销毁,延长变量的生命周期
  • 缺点:如果闭包内的函数在使用完成的情况下不进行销毁,可能会造成内存泄露

48. 渲染项目列表时 “Key” 属性的作用和重要性是什么

  • key值必须是唯一的,并且不变的
  • 特有的
  • key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
  • 总结:vue中key的值必须是唯一的,并且不可变,而且要为特有的,主要是在Vue进行虚拟DOM、DIFF算法的时候,在新旧节点上对比的时候,尽可能的减少对节点的操作以及修改,提高性能

49. 什么时候使用keep-alive元素

  • 有时候不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。而是希望组件可以缓存下来,维持当前的状态。这时候就可以用到keep-alive组件
  • 当一个组件从销毁被创建时,会执行created生命周期,重绘页面,用户输入的数据就会被重绘,当需要保持住这些数据的时候,皆可以使用keep-alive组件
  • 使用场景
    • 用户填写一些表单信息的时候
    • 用户查看详情在进入页面的时候不去重新请求数据
  • keep-alive的生命周期函数
    • actived 被激活时调用
    • deactived 失活是被调用

50. 字符串查重

  • 输入一个纯字母的字符串

  • 输出不同字母(区分大小写)的出现次数,并按次数从大到小排列,如次数相同则正常排序(小写字母在大写字母之前)

     function strRepeat(str) {
        // 将字符串分割为数组进行处理
        return str.split('').reduce((prev, cur) => {
          if (prev[cur]) {
            prev[cur]++;
          } else {
            prev[cur] = 1;
          }
          return prev;
        }, {});
      }
      console.log(strRepeat('aaAABBCC')); // {a: 2, A: 2, B: 2, C: 2}
    

51. 谈谈你对MVVM开发模式的理解

  • MVVM是Model View ViewModel的简写,MVVM模式主要是用数据来驱动视图发生改变
  • Model 数据模型,数据业务逻辑都定义在Model中
  • View 代表呈现给前端页面的UI视图,负责数据的展示
  • ViewModel,负责监听数据的变化,并且控制视图的更新,处理用户的交互操作

52. V-if和v-show有什么区别?

  • v-if 是对一个节点的删除和创建的过程
  • v-show是对一个节点设置样式,通过dispaly属性设置none来控制显隐

53. 如何进行网站性能优化

  • 减少SDN请求
  • 减少Https请求
  • 按需加载模块【异步加载】
  • 对文件进行打包压缩
  • 使用懒加载
  • 对文件进行缓存

54. 如何解决跨越

  • 使用JSONP
  • 后端配置跨域
  • 使用代理技术
  • 使用 frame框架

55. 简答题1

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1);
}
// 3 3 3 
//   相当于 因为 使用var 声明的变量会提升到全局
var i = 0;
for (i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1);
}

// 使用let 关键字声明的变量 不会进行变量提升
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 1);
  }
// 0 1 2 

56. 简答题2

  function sayHi() {
    console.log(name);
    console.log(age);
    var name = 'Lydia';
    let age = 21;
  }
  sayHi();

  // name:undefined
  // age:'报错'

  // 相当于
  // 通过var定义的变量会提升,但是通过age定义的变量不会提升
  function sayHi() {
    var name;
    console.log(name);
    console.log(age);
    name = 'Lydia';
    let age = 21;
  }
  sayHi();

57. 怎么判断一个对象是不是空对象

let obj = {};
let obj1 = { a: 1 };

// 1.使用 for in 去判断
function judgeNullObject(obj) {
  for (let key in obj) {
    return false;
  }
  return true;
}
console.log(judgeNullObject(obj)); // true
console.log(judgeNullObject(obj1)); // false

//   2.使用深拷贝判断
console.log(JSON.stringify(obj) === '{}'); // true
console.log(JSON.stringify(obj1) === '{}'); // false

// 3.使用ES6提供的迭代对象属性的方式
console.log(Object.keys(obj).length === 0); // true
console.log(Object.keys(obj1) === 0); // false

58. 怎么获取数组中的最大值

let arr = [10, 15, 0, 2, 5, 4, 1, 2, 5, 0, 5, 5, 10, 3, 5, 8, 5, 2];
// 1 .通过排序的方式 获取最后一位
console.log(arr.sort((a, b) => a - b)[arr.length - 1]); // 15
// 2. 通过数学方法实现
console.log(Math.max(...arr)); // 15

// 3. 使用for 循环进行排序
function getArrMaxNumber(arr) {
  let maxNumber = arr[0];
  for (let i = 1; i < arr.length; i++) {
    maxNumber = arr[i] > maxNumber ? arr[i] : maxNumber;
  }
  return maxNumber;
}
console.log(getArrMaxNumber(arr)); // 15

59. 用递归实现一下阶乘

  • 阶乘

    1 * 1 = 1
    1 * 2 = 2
    1 * 2 * 3 = 6
    1 * 2 * 3 * 4 = 24
    
  • 代码

     function factorial(num) {
        if (num <= 1) return 1;
        return num * factorial(num - 1);
      }
      console.log(factorial(3)); // 6
    

60. 给数组中的数字排序

 // 给数组中的数字排序
  let arr = [1, 2, 3, 5, 4, 78, 9, 5, 6, 2, 8, 75, 6, 8];

  // 1. 直接使用方法进行排序 (会改变原数组)
  // 升序
  // console.log(arr.sort((a, b) => a - b)); // [1, 2, 2, 3, 4, 5, 5, 6, 6, 8, 8, 9, 75, 78]
  // 降序
  // console.log(arr.sort((a, b) => b - a)); //  [78, 75, 9, 8, 8, 6, 6, 5, 5, 4, 3, 2, 2, 1]

  // 2. 封装函数进行排序
  function sortArr(arr, upOrDown = true) {
    if (upOrDown) {
      for (let i = 0; i < arr.length; i++) {
        for (let j = i; j < arr.length; j++) {
          if (arr[j] > arr[i]) {
            // 定义中间量
            let temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
          }
        }
      }
    } else {
      for (let i = arr.length; i > 0; i--) {
        for (let j = i; j > 0; j--) {
          if (arr[j] > arr[i]) {
            // 定义中间量
            let temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
          }
        }
      }
    }
    return arr;
  }
  console.log(sortArr(arr, false)); // [1, 2, 2, 3, 4, 5, 5, 6, 6, 8, 8, 9, 75, 78]

  // 3. 使用Math方法进行排序
  function sortArrMath(arr, upOrDown = true) {
    let newArr = [];
    let length = arr.length;
    for (let i = 0; i < length; i++) {
      upOrDown
        ? newArr.unshift(Math.max(...arr))
        : newArr.push(Math.max(...arr));
      //删除当前已经筛选过的数据
      arr.splice(arr.indexOf(Math.max(...arr)), 1);
    }
    return newArr;
  }
  console.log(sortArrMath(arr)); // [1, 2, 2, 3, 4, 5, 5, 6, 6, 8, 8, 9, 75, 78]

61. 判断一个数据属于什么数据类型

function chechDataType(data) {
  // 先判断是否为复杂数据类型
  if (typeof data === 'object') {
    return Object.prototype.toString.call(data);
  } else {
    return typeof data;
  }
}
console.log(chechDataType(10)); // number
console.log(chechDataType('abc')); // string
console.log(chechDataType(true)); // boolean
console.log(chechDataType(NaN)); // number
console.log(chechDataType(null)); // [object Null]
console.log(chechDataType(undefined)); // undefined
console.log(chechDataType({})); // [object Object]
console.log(chechDataType([])); // [object Array]
console.log(chechDataType(Symbol())); // symbol
console.log(chechDataType(Symbol)); // function
console.log(chechDataType(() => {})); // function

62. 实现字符串数组倒序

str.split('').reverse().join('')

63. js中的事件循环 Event Loops

  • 先执行同步任务 -> 微任务交替执行 - 再执行异步任务

64. 防抖和节流

  • 防抖

    // 基于事件的防抖;
      function throttle(callback, time = 500) {
        // 定义定时器句柄
        let timeBar = null;
        // 返回一个执行函数
        return function (...args) {
          // 在执行函数内部,先清除定时器
          // 判断是否存在定时器,有的话在去清除定时器
          timeBar && clearTimeout(timeBar);
          // 开启定时器
          timeBar = setTimeout(() => {
            // 执行回调函数,并且传递参数
            // 此时定时器的内的this指向的事件的调用者
            // 判断回调函数的类型 安全效验
            // 因为callback 函数是直接执行,并不是被谁调后去执行,因此callback函数内的this执行的是window 因此要改变函数的this指向
            typeof callback === 'function' && callback.call(this, ...args);
          }, time);
        };
      }
    
    // 简单版
    function throttle(callabck, time = 1000) {
      clearTimeout(callabck.__timerBar);
      callabck.__timerBar = setTimeout(() => {
        callabck();
      }, time);
    }
    
  • 节流

    //   基于时间的节流
    function throttle(callback, time = 1000) {
      // 定义锁
      let flag = false;
      //返回执行函数
      return function (...args) {
        // 判断锁的状态
        if (flag) return;
        // 改变锁的状态
        flag = true;
        // 安全效验 并且执行函数 改变this指向
        typeof callback === 'function' && callback.call(this, ...args);
        // 使用定时器进行节流操作
        setTimeout(() => {
          // 改变锁的状态
          flag = false;
        }, time);
      };
    }
    
    // 时间戳
    function throttle(callback, time = 1000) {
      // 定义锁
      let date = 0;
      //返回执行函数
      return function (...args) {
        // 判断触发事件是否在规定的事件范围内 在的话直接终止函数
        if (new Date() - date < time) return;
        // 改变函数的this指向,并且传递参数 做安全效验
        typeof callback === 'function' && callback.call(this, ...args);
        // 重置事件
        date = new Date();
      };
    }
    
    
    // 简单版节流
    function throttle(callback, time = 1000) {
      if (callback.__flag) return;
      callback.__flag = true;
      callback();
      setTimeout(() => {
        callback.__flag = false;
      }, time);
    }
    

65. 事件冒泡和事件捕获的执行顺序?

  • 默认情况下,事件采用冒泡事件流不采用事件捕获

  • 在谷歌火狐中 可以通过addEvenetListener()传递第二个参数来改变事件是采用冒泡还是捕获来执行,默认是false,是冒泡阶段

  • 事件冒泡是 当一个元素上添加的事件被触发的时候,会逐层向外进行传递,一直到根节点,绑定的事件都会执行【同类事件】

  • 冒泡阶段是 当一个元素上添加的事件被触发的时候,会从触发事件的元素向内开始查找,绑定的事件都会触发【同类事件】

  • 阻止冒泡

  • 高级浏览器

    e.stopstopPropagation()
    
  • IE低版本

    event.cancelBubble=true
    

66. z-index详解

- 一旦盒子设置了定位(相对定位,绝对定位,固定定位),就会产生遮盖的现象遮盖就是指盒子被另一个盒子盖住,
- 默认遮盖顺序有两个特点
    1. 设置定位的盒子,会遮盖住没有定位的盒子(包括浮动的盒子)
    2. 如果盒子都设置了定位,后面的盒子会遮盖住前面的盒子
- 默认情况下,如果盒子都设置了定位,遮盖的顺序是按照盒子创建的顺序遮盖的,后面的会遮盖住前面的
- 为了能够让盒子遮盖的顺序自定义,设置了定位的盒子会激活z-index属性,来设置盒子遮盖的顺序
- z-index:属性值是一个数字(不要带单位),遮盖的顺序是由该属性值的大小决定的,
- 其特点:
    1. 只有设置了定位的盒子才激活z-index,因此定位的盒子可以设置z-index,没有定位盒子不能设置z-index(包括浮动的盒子)
    2. z-index默认值是auto,可以看成是0,没有定位的盒子不能设置z-index,但是我们可以将其z-index看成0,如果z-index小于0,会渲染盒子(包括未定位的盒子)底部,只有大于等于0的时 候,才会渲染在上面
    3. 设置的z-index值越大,就会渲染在最前面,谁的大,谁在前面,注意:z-index属性值通常是整数
    4. 如果盒子设置了相同的z-index,此时盒子会根据创建的顺序决定谁在前面,不论是正值还是负值,后创建的盒子渲染在最前面
    5. 盒子的遮盖顺序不仅仅与自己的z-index相关,还与设置了定位的父盒子相关,父盒子z-index高的,不论子盒子z-index设置了多少,都会渲染在父盒子z-index低的子盒子的前面,这一现象我们称之为拼爹

67. Promise 和 Async/Await 的区别?

  • async/await与Promise一样,是非阻塞的
  • async/await使得异步代码看起来像同步代码
  • 使用async函数可以让代码简洁很多,不需要像Promise一样需要些then,不需要写匿名函数处理Promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码
  • Async/Await 让 try/catch 可以同时处理同步和异步错误

68. Vue组件内的name属性有什么用

  • .当使用组件递归调用时,被递归调用的组件必须定义name属性,因为在组件里面调用自己时,不是使用的在components里注册的组件,而是使用根据name属性查找组件
  • keep-alive包裹动态组件时,会缓存不活动的组件实例,会出现include和exclude属性,包含或者排除指定name组件
  • vue-tools插件调试时没有name属性会报错或警告

69. Cookie

  • 弊端:
    • 每一个域名下最多生成20个cookie
    • 存储数据有局限性:最多存储4096字节也就是4kb,因为每个浏览器存储的的大小都亦一样,为了兼容,一半不能超过4095字节
    • 如果cookie被拦截了,那么cookie内的数据结汇被盗取,即使加密了也无效,因为可以是直接专用cookie而不用去解密
  • 优点
    • 通过加密和安全存传输技术【SSL】,减少cookie被破解的可能性
    • 只在cookie内存放一些不敏感的信息,即使被盗也不会有重大损失
    • 控制cookie的生命周期,让其不会永久有效

70. cookie和session的区别

  • cookie存储的数据时放在客户浏览器上的,sesstion是将数据放在服务器上的
  • cookie不是很安全,别人可以解析存放在本地的cookie,考虑安全性能应该存放在session中
  • session 会在一定的时间将数据保存在服务器上,但是当访问增多的时候,会占用服务器的性能,考虑到减轻服务器性能,应该使用cookie
  • cookie限制数据大小不能超过4kb,并且 浏览器丢店都会限制一个站点最多保存20个cookie

71. 去除首位空白符

 function trim(str) {
    return str.replace(/^\s+|\s+$/g, '');
  }

72. 事件委托

  • 事件委托是将原本要绑定的事件委托给父元素,让其父元素监听的职务,事件委托原理是采用DOM事件的冒泡机制,使用事件委托的好处是 可以提高性能

73. 阻止a标签刷新页面的方法

  • 在a标签上href 上添加javascript:;
  • herf 变为#
  • a标签的herf=“jsvascript:(void()0)”

74. 手写Promise.all

  // Promise.all()为Promise实例上的一个静态方法
  Promise._all = (arr) => {
    // 定义一个接受返回值的数组
    let sendArr = [];
    // 定义一个计数器
    let count = 0;
    // 返回值应该为一个Promise
    return new Promise((resolve, reject) => {
      // 遍历所有的Promise
      for (let i = 0; i < arr.length; i++) {
        arr[i].then((res) => {
          sendArr[i] = res;
          count++;
          // 判断是否完成
          // 使用长度去检测时候完成,可能会因为异步处理时间的不一样导致有些异步没有加载完成,因此又bug
          // if (sendArr.length === arr.length) {
          //   resolve(sendArr);
          // }
          if (count === arr.length) resolve(sendArr);
        }, reject);
      }
    });
  };

  // 定义异步
  function fn(data) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(data);
      }, Math.random() * 1000);
    });
  }
  let p1 = fn('这是P1');
  let p2 = fn('这是P2');
  let p3 = fn('这是P3');
  let p4 = fn('这是P4');

  Promise._all([p1, p2, p3, p4]).then(
    (res) => {
      console.log(res);
    },
    (fail) => {
      console.log(fail);
    }
  );

75. 手写观察者模式

 let Observer = (function () {
    //   存储事件
    let data = {};
    return {
      // 订阅消息
      /**
       * @on 订阅消息
       * @type 消息类型 就是是触发的消息名称
       * @callback 回调函数
       */
      on(type, callback) {
        data[type] ? data[type].push(callback) : (data[type] = [callback]);
      },
      trigger(type, ...args) {
        // 先判断是否有该类型的函数
        if (!data[type]) throw Error(`${type}函数还没有被订阅`);
        // 使用解构语法进行传递参数
        // for (let i = 0; i < data[type].length; i++) {
        //   data[type][i](...args);
        // }
        // 当不能使用ES6提供的语法的时候 可以通过arguments 来进行处理
        for (let i = 0; i < data[type].length; i++) {
          data[type][i].apply(null, Array.prototype.slice.call(arguments, 1));
        }
      },
      off(type, callback) {
        if (!data[type]) throw Error(`${type}函数还没有被订阅`);
        if (callback) {
          let index = data[type].indexOf(callback);
          if (index >= 0) data[type].splice(index, 1);
        } else if (type) {
          data[type] = [];
        } else {
          data = {};
        }
      },
      once(type, callback) {
        // 单次触发
        let _this = this;
        // 包装回调函数
        function fn(...args) {
          // 注销
          _this.off(type, fn);
          // 触发之前注销
          callback(...args);
        }
        _this.on(type, fn);
      },
    };
  })();

76. Vue路由怎么重置

const createRouter = () =>
  new VueRouter({
    routes,
  });
const router = createRouter();
// 封装一个重置路由的方法
const resetRouter = () => {
  const newRouter = createRouter();
  router.matcher = newRouter.matcher;
};

77. 求出一个字符串出现次数最多的字符

 const str = 'ejdjgbgrjgbjfldbgdfbgljfdgbwbpiwebf';
  function findManyString(str) {
    // 定义一个空的对象,存储查找到的字符
    let strObj = {};
    for (let i = 0; i < str.length; i++) {
      strObj[str[i]] = strObj[str[i]] ? ++strObj[str[i]] : 1;
    }
    let obj = {
      s: '',
      n: 0,
    };
    for (let key in strObj) {
      if (strObj[key] > obj.n) {
        obj.n = strObj[key];
        obj.s = key;
      }
    }
    return obj;
  }
  console.log(findManyString(str)); // {s: 'b', n: 7}

  // 使用ES6的方法进行处理
  function findManyStringES6(str) {
    // 将字符串转为数组
    const result = [...str].reduce((prev, cur) => {
      prev[cur] = prev[cur] ? ++prev[cur] : 1;
      return prev;
    }, {});
    const [[s, n]] = Object.entries(result).sort((a, b) => b[1] - a[1]);
    return {
      s,
      n,
    };
  }
  console.log(findManyStringES6(str)); // {s: 'b', n: 7}

78. 获取一个标签内出现次最多的标签【一个页面出现次数最多的标签】

 // 查找某一个标签下出现最多次数的标签
  function findMostElement(
    targetElement = document.documentElement,
    prevs = {}
  ) {
    const allElement = Array.from(targetElement.children);
    const result = allElement.reduce((prev, cur) => {
      if (Array.from(cur.children).length > 0) {
        findMostElement(cur, prev);
      } else {
        prev[cur.nodeName]
          ? (prev[cur.nodeName] = ++prev[cur.nodeName])
          : (prev[cur.nodeName] = 1);
      }
      return prev;
    }, prevs);
    // 处理result 排序 解构 拿到最多的一项
    const [[e, n]] = Object.entries(result).sort((a, b) => b[1] - a[1]);
    // 返回结果
    return {
      e: e.toLowerCase(),
      n,
    };
  }


  // 获取当前页面下出现最多次数的标签
  function findPageMoseElement() {
    //  获取标签的两种方式 处理兼容
    const allElement = Array.from(
      document.querySelectorAll('*') || document.getElementsByTagName('*')
    );
    const result = allElement.reduce((prev, cur) => {
      prev[cur.localName] = prev[cur.localName] ? ++prev[cur.localName] : 1;
      return prev;
    }, {});
    const [[e, n]] = Object.entries(result).sort((a, b) => b[1] - a[1]);
    return {
      e,
      n,
    };
  }

79. 实现数组中reverse方法

// 定义一个数组
let arr = [1, 2, 3, 4, 5, 6];
let arr1 = [1, 2, 3, 4, 5];

//   console.log(arr.reverse()); // [6, 5, 4, 3, 2, 1]
//   console.log(arr); // [6, 5, 4, 3, 2, 1]
// Array.reverse 方法,不需要传递参数,就可以实现数组的反转,并且会改变原来的数组
// 返回值为该变后的数组

// 在使用reverse进行数组反转的时候,也就是改变;数组内第一个的位置和最后的位置做了交换
// 因此 在数组成员偶数个的时候, 也就是Array.length / 2
// 在数组成员为奇数的时候,Array.length / 2 时会出现小数应该做取整
Array.prototype.myReverse = function () {
  for (let i = 0; i < ~~(this.length / 2); i++) {
    // 缓存变量
    let temp = this[i];
    this[i] = this[this.length - 1 - i];
    this[this.length - 1 - i] = temp;
  }
  return this;
};
console.log(arr.myReverse(), arr); // [6, 5, 4, 3, 2, 1] [6, 5, 4, 3, 2, 1]
console.log(arr1.myReverse(), arr); //  [5, 4, 3, 2, 1]  //5, 4, 3, 2, 1]

80. 异步变同步改造

let p1 = (fn) => {
  setTimeout(() => {
    fn(1);
  }, Math.random() * 1000);
};
let p2 = (fn) => {
  setTimeout(() => {
    fn(2);
  }, Math.random() * 1000);
};
let p3 = (fn) => {
  setTimeout(() => {
    fn(3);
  }, Math.random() * 1000);
};

//   使用下面的方式,在执行函数的时候,会出现执行顺序不一致的情况
p1((v) => {
  console.log(v);
});
p2((v) => {
  console.log(v);
});
p3((v) => {
  console.log(v);
});

async function fun(p) {
  // 同步变异步,使用Promise + await 来实现
  for (let i = 0; i < p.length; i++) {
    let r = await new Promise((resolve) => {
      p[i]((v) => {
        resolve(v);
      });
    });
    console.log(r);
  }
}

fun([p1, p2, p3]);

81. 字符串前填充

function padZero(n) {
  return String(n).padStart(2, '0');
}

82. 脏话匹配

let str = '你TMD真的是一个CAIBI,不会玩你玩NM呢';
// 脏话:TMD CAI  BI NM
let reuslt = str.replaceAll(/(TMD|CAI|BI|SB|NM|NMD)/gi, (match) =>
  '*'.repeat(match.length)
);
console.log(reuslt); // 你***真的是一个*****,不会玩你玩**呢

83. 清除空白符

function trimAll(s) {
    // return s.replaceAll(' ', '');
    return s.replaceAll(/\s+/g, '');
  }

84. 数组求交集

let arr1 = [1, 2, 3, 4, 5, 6, 6, 8];
let arr2 = [2, 3, 4, 5, 6];

function intersection(arr1, arr2) {
  let newArr = [];
  for (let i = 0; i < arr1.length; i++) {
    arr2.includes(arr1[i]) ? newArr.push(arr1[i]) : '';
  }
  return [...new Set(newArr)];
}
console.log(intersection(arr1, arr2)); //[2, 3, 4, 5, 6]

// 使用reduce事件
function intersectionReduce(arr1, arr2) {
  return arr1.reduce((prev, cur) => {
    arr2.includes(cur) ? prev.push(cur) : '';
    return [...new Set(prev)];
  }, []);
}

console.log(intersectionReduce(arr1, arr2)); //[2, 3, 4, 5, 6]

// 过滤
function intersectionFilter(arr1, arr2) {
  let result = arr1.filter((n) => arr2.includes(n));
  return [...new Set(result)];
}
console.log(intersectionFilter(arr1, arr2)); // [2, 3, 4, 5, 6]

85. http 和 https的区别

  • http 是超文本传输协议,信息都是明文传输,https对数据进行了加密,是密文传输,可以防止数据在传递的过程中被劫持,破解。
  • http 的默认端口是80,https 的默认端口是443
  • http 是无状态的链接,耗时较少,https 在链接握手的时候,耗时较长
  • https 缓存不如 http 高效,会增加数据开销
  • https需要加密证书,http则不需要
  • SSL 证书需要绑定 ip,不能再同一个 IP 上绑定多个域名,IPV4 资源支持不了这种消耗

86. TCP的三次握手

  • 第一次

    • 客户端向服务器端发送消息,等待服务器确认
  • 第二次

    • 服务器端收到客户端发来的消息,并且服务端向客户端发送消息,表示收到客户端发来的消息
  • 第三次

    • 客户端收到服务器发来的确认消息,开始向服务器发送确认包,当此包发送完毕后,客户端和服务器端 TCP 就链接 成功,完成三次握手
  • 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
    第二次握手:服务器收到syn包并确认客户的SYN(ack=j+1),同时也发送一个自己的SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
    第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHEDTCP连接成功)状态,完成三次握手
    

87. TCP的四次挥手

  • 客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。

  • 服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。

  • 服务器B关闭与客户端A的连接,发送一个FIN给客户端A。

  • 客户端A发回ACK报文确认,并将确认序号设置为收到序号加1

  • 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
    
    2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
    3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最 后的数据)。
    4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
    5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
    6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
    

86. http缓存

  • 当浏览器发起请求之后,会先在浏览器本地查看是否有该次请求的结果和缓存的标志,如果有则使用缓存,如果没有,则从服务器中获取数据,服务器返回数据之后,浏览器会将该请求的解构和缓存标识存储到浏览器缓存中

  • 浏览器缓存主要分为强缓存和弱缓存

  • 强缓存:

    强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。当浏览器向服务器发起请求时,服务器会将缓存规则放入HTTP响应报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是 Expires 和 Cache-Control,其中Cache-Control优先级比Expires高。
    
    
    • 强制缓存的情况主要有三种(暂不分析协商缓存过程),如下:
      • 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致)。
      • 存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存。
      • 存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果
  • 协商缓存

    • 协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,同样,协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:Last-Modified / If-Modified-Since 和 Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。协商缓存主要有以下两种情况:
      
    • 协商缓存生效,返回304

    • 协商缓存失效,返回200和请求结果结果

88. 客户端从输入网址到呈现页面都做了哪些事情【从输入URL到页面加载的全过程】

  • 输入URL地址

  • 浏览器会去本地查找看是否有缓存,因为浏览器在通过DNS地址后,会将解析后的结果缓存一段时间

    • 如果本地缓存内有缓存文件,则呈现缓存文件,如果没有则进行下一步
  • DNS解析域名:浏览器向DNS服务器发起请求,DNS解析域名,找到对应的服务器

  • 和服务器建立TCP链接

  • 发起HTTP请求,读取资源文件,该请求报文作为TCP三次握手的第三次数据发送给服务器

  • 关闭TCP连接:通过四次挥手释放TCP连接

  • 浏览器渲染

    • 构建DOM树结构
    • 构建CSS树
    • CSS和DOM结合,构建渲染树
    • 布局
    • 绘制
  • JS引擎解析

89. Vue响应原理

  • 在Vue初始化的时候,给data中的数据全部通过ES5提供 Object.defineProperty 特性的方式,为每一个数据添加上getter 和 setter
  • Vue的每一个实例都有一个watcher实例,watcher实例会在渲染时记录这些属性,并且在data数据中的setter触发时重新渲染页面

90. 数组转树

<script>
  // 定义树结构
  const data = [
    {
      id: 1,
      title: '研发部',
      pid: null,
    },
    {
      id: 2,
      title: '开发部',
      pid: null,
    },
    {
      id: 3,
      title: '市场部',
      pid: null,
    },
    {
      id: 4,
      title: '销售部',
      pid: null,
    },
    {
      id: 5,
      title: '前端研发部',
      pid: 1,
    },
    {
      id: 6,
      title: '后端研发部',
      pid: 1,
    },
    {
      id: 7,
      title: '算法研发部',
      pid: 1,
    },
    {
      id: 8,
      title: '前端开发部',
      pid: 2,
    },
    {
      id: 9,
      title: '后端开发部',
      pid: 2,
    },
    {
      id: 10,
      title: '算法开发部',
      pid: 2,
    },
  ];

  // 使用循环的方式进行查找
  //   function arrayToTree(array) {
  //     // 定义返回素组数组
  //     let result = [];
  //     // 使用forEach 来进行操作
  //     array.forEach((item) => {
  //       // 先判断pid是否为null 为null 直接push进去
  //       if (item.pid === null) {
  //         result.push(item);
  //       }
  //       // 开始判断是否有id === Pid 的结构
  //       array.forEach((items) => {
  //         // 在result 内查找是否有存在项
  //         let index = result.findIndex((itemss) => items.pid === itemss.id);
  //         // 判断index的值
  //         if (index >= 0) {
  //           // 判断当前项下是否有children属性
  //           if (result[index].children) {
  //             // 查找当前符合向内是否已经添加过相同的pid,如果有相同的 则直接结束循环
  //             if (result[index].children.includes(items)) return;
  //             // 没有则添加一条
  //             result[index].children.push(items);
  //           } else {
  //             // 如果没有children属性 则直接给一个空数组
  //             result[index].children = [];
  //           }
  //         }
  //       });
  //     });
  //     return result;
  //   }
  //   console.log(arrayToTree(data));

  // 使用reduce
  function arrayToTree(array, pid = null) {
    return array.reduce((prev, cur) => {
      if (cur.pid === pid) {
        // 接受每次的返回值
        let index = arrayToTree(array, cur.id);
        // 判断返回值的长度 如果 返回值有长度 则为数据添加 children 属性
        if (index.length) {
          cur.children = index;
        }
        prev.push(cur);
      }
      return prev;
    }, []);
  }
  console.log(arrayToTree(data));
</script>

91. 树转数组

<script>
  // 定义树结构
  const data = [
    {
      id: 1,
      title: '研发部',
      pid: null,
    },
    {
      id: 2,
      title: '开发部',
      pid: null,
    },
    {
      id: 3,
      title: '市场部',
      pid: null,
    },
    {
      id: 4,
      title: '销售部',
      pid: null,
    },
    {
      id: 5,
      title: '前端研发部',
      pid: 1,
    },
    {
      id: 6,
      title: '后端研发部',
      pid: 1,
    },
    {
      id: 7,
      title: '算法研发部',
      pid: 1,
    },
    {
      id: 8,
      title: '前端开发部',
      pid: 2,
    },
    {
      id: 9,
      title: '后端开发部',
      pid: 2,
    },
    {
      id: 10,
      title: '算法开发部',
      pid: 2,
    },
  ];

  function arrayToTree(array, pid = null) {
    return array.reduce((prev, cur) => {
      if (cur.pid === pid) {
        // 接受每次的返回值
        let index = arrayToTree(array, cur.id);
        // 判断返回值的长度 如果 返回值有长度 则为数据添加 children 属性
        if (index.length) {
          cur.children = index;
        }
        prev.push(cur);
      }
      return prev;
    }, []);
  }
  // 定义树结构
  let tree = arrayToTree(data);

  // 使用原生的方式
  // function treeToArray(tree) {
  //   let treeToArrayResult = [];
  //   // 生成一个子函数
  //   function son(tree) {
  //     tree.forEach((item) => {
  //       if (item.children) {
  //         let pushIndex = treeToArrayResult.push(item);
  //         son(item.children);
  //         delete treeToArrayResult[pushIndex - 1].children;
  //       } else {
  //         treeToArrayResult.push(item);
  //       }
  //     });
  //   }
  //   son(tree);
  //   // 开始遍历树形结构
  //   return treeToArrayResult;
  // }
  // console.log(treeToArray(tree));

  // 使用reduce的方式
  function treeToArray(tree) {
    return tree.reduce((prev, cur) => {
      if (cur.children) {
        let pushIndex = prev.push(cur);
        prev.push(...treeToArray(cur.children));
        delete prev[pushIndex - 1].children;
      } else {
        prev.push(cur);
      }
      return prev;
    }, []);
  }
  console.log(treeToArray(tree));
</script>

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值