• 精选面试题


    文章目录

    css面试题

    1.1-让元素水平居中的方法有哪些?

    方法一:使用flex+margin

    通过为父元素设置display: flex 子元素设置 margin: auto实现

    方式二: 转成行内块, 给父盒子设置 text-align: center

    方法三:使用 flex 布局

    使用 flex 提供的子元素居中排列功能,对元素进行居中。

     justify-content: center;  align-items: center;
    
    • 1

    方式四: 使用定位+transform

    position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
    
    • 1
    • 2
    • 3
    • 4

    1.2-flex:1什么意思

    flex:1实际代表的是三个属性的简写

    flex-grow:1 (增长)

    flex-grow是用来增大盒子的,比如,当父盒子的宽度大于子盒子的宽度,父盒子的剩余空间可以利用flex-grow来设置子盒子增大的占比

    flex-shrink:1 (收缩)

    flex-shrink用来设置子盒子超过父盒子的宽度后,进行缩小的比例取值

    flex-basis:0% (基准)

    设置盒子的基准宽度,并且basis和width同时存在会把width干掉

    1.3-BFC 的应用场景:

    1. 场景一:防止两个相邻块级元素的上下 margin 发生重叠 (上下 margin 合并问题)
    2. 场景二:清除浮动
    3. 场景三:实现自适应布局, 防止元素被浮动元素覆盖(左边固定, 右边自适应)

    1.4-w3c标准盒模型

    默认就是content-box,也就是默认标准盒模型,标准盒模型width设置了内容的宽,所以盒子实际宽度加上padding和border

    JavaScript 基础

    1.1-数组的常用方法有哪些?

    下面前三种是对原数组产生影响的增添方法,第四种则不会对原数组产生影响

    • push()方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度
    • unshift () 在数组开头添加任意多个值,然后返回新的数组长度
    • splice() 传入三个参数,分别是开始位置、0(要删除的元素数量)、插入的元素,返回空数组
    • concat() 首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组

    下面三种都会影响原数组,最后一项不影响原数组:

    • pop() 方法用于删除数组的最后一项,同时减少数组的 length 值,返回被删除的项
    • shift()方法用于删除数组的第一项,同时减少数组的 length 值,返回被删除的项
    • splice() 传入两个参数,分别是开始位置,删除元素的数量,返回包含删除元素的数组
    • slice() 用于创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组

    即修改原来数组的内容,常用splice

    传入三个参数,分别是开始位置,要删除元素的数量,要插入的任意多个元素,返回删除元素的数组,对原数组产生影响

    即查找元素,返回元素坐标或者元素值

    • indexOf() 返回要查找的元素在数组中的位置,如果没找到则返回 -1
    • includes() 返回要查找的元素在数组中的位置,找到返回true,否则false
    • find() 返回第一个匹配的元素

    常见的转换方法有:join() 方法接收一个参数,即字符串分隔符,返回包含所有项的字符串

    迭代方法

    常用来迭代数组的方法(都不改变原数组)有如下:

    • some() 对数组每一项都运行传入的函数,如果有一项函数返回 true ,则这个方法返回 true
    • every() 对数组每一项都运行传入的函数,如果对每一项函数都返回 true ,则这个方法返回 true
    • forEach() 对数组每一项都运行传入的函数,没有返回值
    • filter() 对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回
    • map() 对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组

    1.2-bind、call、apply 区别?

    • 三者都可以改变函数的this对象指向
    • 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefinednull,则默认指向全局window
    • 三者都可以传参,但是apply是数组,而call是参数列表,且applycall是一次性传入参数,而bind可以分为多次传入
    • bind 是返回绑定this之后的函数,apply call 则是立即执行

    1.3-深拷贝浅拷贝的区别?如何实现一个深拷贝?

    浅拷贝

    浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝

    如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址

    即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址

    深拷贝

    深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

    常见的深拷贝方式有:

    • _.cloneDeep()

    • jQuery.extend()

    • JSON.stringify() 但是这种方式存在弊端,会忽略undefinedsymbol函数

    • 手写循环递归

      // 如果是null或者undefined我就不进行拷贝操作
      // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
      // 是对象的话就要进行深拷贝
      // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
      
      • 1
      • 2
      • 3
      • 4

      浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象

      但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象

      前提为拷贝类型为引用类型的情况下:

      • 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
      • 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址

    1.4-什么是防抖和节流?有什么区别?如何实现?

    一、定义
    • 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
    • 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
    二、区别:

    相同点:

    • 都可以通过使用 setTimeout 实现
    • 目的都是,降低回调执行频率。节省计算资源

    不同点:

    • 函数防抖,在一段连续操作结束后,处理回调,利用clearTimeout setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能
    • 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次

    例如,都设置时间频率为500ms,在2秒时间内,频繁触发函数,节流,每隔 500ms 就执行一次。防抖,则不管调动多少次方法,在2s后,只会执行一次

    三、应用场景

    防抖在连续的事件,只需触发一次回调的场景有:

    • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
    • 手机号、邮箱验证输入检测
    • 窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

    节流在间隔一段时间执行一次回调的场景有:

    • 滚动加载,加载更多或滚到底部监听
    • 搜索框,搜索联想功能

    1.5-说说你对事件循环的理解

    首先,JavaScript 是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环

    JavaScript中,所有的任务都可以分为

    • 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
    • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout 定时函数等

    从上面我们可以看到,同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环

    1.6-原型链和成员的查找机制

    任何对象都有原型对象,也就是prototype属性,任何原型对象也是一个对象,该对象就有__proto__属性,这样一层一层往上找,就形成了一条链,我们称此为原型链;

    当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
    如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
    如果还没有就查找原型对象的原型(Object的原型对象)。
    依此类推一直找到 Object 为止(null)。
    __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.7-JavaScript字符串的常用方法有哪些?

    一、操作方法

    我们也可将字符串常用的操作方法归纳为增、删、改、查,需要知道字符串的特点是一旦创建了,就不可变

    这里增的意思并不是说直接增添内容,而是创建字符串的一个副本,再进行操作

    除了常用+以及${}进行字符串拼接之外,还可通过concat

    concat

    用于将一个或多个字符串拼接成一个新字符串

    这里的删的意思并不是说删除原字符串的内容,而是创建字符串的一个副本,再进行操作

    常见的有:

    • slice()
    • substr()
    • substring()

    这三个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。

    这里改的意思也不是改变原字符串,而是创建字符串的一个副本,再进行操作

    常见的有:

    • trim()、trimLeft()、trimRight()
    • repeat()
    • padStart()、padEnd()
    • toLowerCase()、 toUpperCase()
    trim()、trimLeft()、trimRight()

    删除前、后或前后所有空格符,再返回新的字符串

    repeat()

    接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果

    padEnd()

    复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件

    toLowerCase()、 toUpperCase()

    大小写转化

    除了通过索引的方式获取字符串的值,还可通过:

    • chatAt() 返回给定索引位置的字符,由传给方法的整数参数指定
    • indexOf() 从字符串开头去搜索传入的字符串,并返回位置(如果没找到,则返回 -1 )
    • startWith() 从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值
    • includes() 从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值
    二、转换方法、转换方法)
    split

    把字符串按照指定的分割符,拆分成数组中的每一项

    三、模板匹配方法

    针对正则表达式,字符串设计了几个方法:

    • match() 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,返回数组
    • search() 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,找到则返回匹配索引,否则返回 -1
    • replace() 接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素(可用函数)

    1.8-谈谈this对象的理解

    一、定义

    函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别

    在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)

    this 关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象

    同时,this在函数执行过程中,this一旦被确定了,就不可以再更改

    二、绑定规则

    根据不同的使用场合,this有不同的值,主要分为下面几种情况:

    • 默认绑定

    • 隐式绑定

    • new绑定

    • 显示绑定

      全局环境中定义person函数,内部使用this关键字

      var name = 'Jenny';
      function person() {
          return this.name;
      }
      console.log(person());  //Jenny
      
      • 1
      • 2
      • 3
      • 4
      • 5

      上述代码输出Jenny,原因是调用函数的对象在游览器中位window,因此this指向window,所以输出Jenny

      注意:

      严格模式下,不能将全局对象用于默认绑定,this会绑定到undefined,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象

      隐式绑定

      函数还可以作为某个对象的方法调用,这时this就指这个上级对象

      function test() {
        console.log(this.x);
      }
      
      var obj = {};
      obj.x = 1;
      obj.m = test;
      
      obj.m(); // 1
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象

      var o = {
          a:10,
          b:{
              fn:function(){
                  console.log(this.a); //undefined
              }
          }
      }
      o.b.fn();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      上述代码中,this的上一级对象为bb内部并没有a变量的定义,所以输出undefined

      这里再举一种特殊情况

      var o = {
          a:10,
          b:{
              a:12,
              fn:function(){
                  console.log(this.a); //undefined
                  console.log(this); //window
              }
          }
      }
      var j = o.b.fn;
      j();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      此时this指向的是window,这里的大家需要记住,this永远指向的是最后调用它的对象,虽然fn是对象b的方法,但是fn赋值给j时候并没有执行,所以最终指向window

      new绑定

      通过构建函数new关键字生成一个实例对象,此时this指向这个实例对象

      function test() {
       this.x = 1;
      }
      
      var obj = new test();
      obj.x // 1
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      上述代码之所以能过输出1,是因为new关键字改变了this的指向

      这里再列举一些特殊情况:

      new过程遇到return一个对象,此时this指向为返回的对象

      function fn()  
      {  
          this.user = 'xxx';  
          return {};  
      }
      var a = new fn();  
      console.log(a.user); //undefined
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      如果返回一个简单类型的时候,则this指向实例对象

      function fn()  
      {  
          this.user = 'xxx';  
          return 1;
      }
      var a = new fn;  
      console.log(a.user); //xxx
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      注意的是null虽然也是对象,但是此时new仍然指向实例对象

      function fn()  
      {  
          this.user = 'xxx';  
          return null;
      }
      var a = new fn;  
      console.log(a.user); //xxx
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      显示修改

      apply()、call()、bind()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this指的就是这第一个参数

      var x = 0;
      function test() {
       console.log(this.x);
      }
      
      var obj = {};
      obj.x = 1;
      obj.m = test;
      obj.m.apply(obj) // 1
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    三、箭头函数

    在 ES6 的语法中还提供了箭头函语法,让我们在代码书写时就能确定 this 的指向(编译时绑定)

    举个例子:

    const obj = {
      sayThis: () => {
        console.log(this);
      }
    };
    
    obj.sayThis(); // window 因为 JavaScript 没有块作用域,所以在定义 sayThis 的时候,里面的 this 就绑到 window 上去了
    const globalSay = obj.sayThis;
    globalSay(); // window 浏览器中的 global 对象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    虽然箭头函数的this能够在编译的时候就确定了this的指向,但也需要注意一些潜在的坑

    下面举个例子:

    绑定事件监听

    const button = document.getElementById('mngb');
    button.addEventListener('click', ()=> {
        console.log(this === window) // true
        this.innerHTML = 'clicked button'
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上述可以看到,我们其实是想要this为点击的button,但此时this指向了window

    包括在原型上添加方法时候,此时this指向window

    Cat.prototype.sayName = () => {
        console.log(this === window) //true
        return this.name
    }
    const cat = new Cat('mm');
    cat.sayName()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    同样的,箭头函数不能作为构建函数

    1.9-typeof 与 instanceof 区别

    typeofinstanceof都是判断数据类型的方法,区别如下:

    • typeof会返回一个变量的基本类型,instanceof返回的是一个布尔值
    • instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型
    • typeof 也存在弊端,它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了 function 类型以外,其他的也无法判断

    可以看到,上述两种方法都有弊端,并不能满足所有场景的需求

    如果需要通用检测数据类型,可以采用Object.prototype.toString,调用该方法,统一返回格式“[object Xxx]” 的字符串

    2.1- 谈谈你对 JavaScript 作用域链的理解?

    JavaScript 在执⾏过程中会创建一个个的可执⾏上下⽂。 (每个函数执行都会创建这么一个可执行上下文)

    每个可执⾏上下⽂的词法环境中包含了对外部词法环境的引⽤,可通过该引⽤来获取外部词法环境中的变量和声明等。

    这些引⽤串联起来,⼀直指向全局的词法环境,形成一个链式结构,被称为作⽤域链。

    简而言之: 函数内部 可以访问到 函数外部作用域的变量, 而外部函数还可以访问到全局作用域的变量,

    这样的变量作用域访问的链式结构, 被称之为作用域链

    全局作用域

    任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问

    函数作用域

    函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问

    块级作用域

    ES6引入了letconst关键字,和var关键字不同,在大括号中使用letconst声明的变量存在于块级作用域中。在大括号之外不能访问这些变量

    2.2-谈谈你对闭包的理解?

    什么是闭包

    内层函数, 引用外层函数上的变量, 就可以形成闭包

    闭包让你可以在一个内层函数中访问到其外层函数的作用域

    JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁

    闭包的主要作用是什么?

    任何闭包的使用场景都离不开这两点:

    • 创建私有变量
    • 延长变量的生命周期

    一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的

    2.3-如何判断是否是数组?

    方法一:使用 toString 方法

    方法二:使用 ES6 新增的 Array.isArray 方法

    2.4-谈谈你对 this 的理解?

    this 是一个在运行时才进行绑定的引用,在不同的情况下它可能会被绑定不同的对象。

    默认绑定 (指向 window 的情况) (函数调用模式 fn() )

    默认情况下,this 会被绑定到全局对象上,比如在浏览器环境中就为window对象,在 node.js 环境下为global对象。

    隐式绑定 (谁调用, this 指向谁) (方法调用模式 obj.fn() )

    如果函数的调用是从对象上发起时,则该函数中的 this 会被自动隐式绑定为对象:

    显式绑定 (又叫做硬绑定) (上下文调用模式, 想让 this 指向谁, this 就指向谁)

    硬绑定 => call apply bind

    new 绑定 (构造函数模式)

    另外,在使用 new 创建对象时也会进行 this 绑定

    当使用 new 调用构造函数时,会创建一个新的对象并将该对象绑定到构造函数的 this

    2.5- 箭头函数中的 this 指向什么?

    箭头函数不同于传统函数,它其实没有属于⾃⼰的 this

    它所谓的 this 是, 捕获其外层 上下⽂的 this 值作为⾃⼰的 this 值。

    并且由于箭头函数没有属于⾃⼰的 this ,它是不能被 new 调⽤的。

    我们可以通过 Babel 转换前后的代码来更清晰的理解箭头函数

    2.6-Promise 的静态方法

    promise 的三个状态: pending(默认) fulfilled(成功) rejected(失败)

    1. resolve 函数被执行时, 会将 promise 的状态从 pending 改成 fulfilled 成功
    2. reject 函数被执行时, 会将 promise 的状态从 pending 改成 rejected 失败

    Promise.all([promise1, promise2, promise3]) 等待原则, 是在所有 promise 都完成后执行, 可以用于处理一些并发的任务

    // 后面的.then中配置的函数, 是在前面的所有promise都完成后执行, 可以用于处理一些并发的任务
    Promise.all([promise1, promise2, promise3]).then((values) => {
      // values 是一个数组, 会收集前面promise的结果 values[0] => promise1的成功的结果
    })
    
    • 1
    • 2
    • 3
    • 4

    Promise.race([promise1, promise2, promise3]) 赛跑, 竞速原则, 只要三个 promise 中有一个满足条件, 就会执行.then(用的较少)

    2.7-宏任务 微任务 是什么

    宏任务: 主线程代码, setTimeout 等属于宏任务, 上一个宏任务执行完, 才会考虑执行下一个宏任务

    微任务: promise .then .catch 的需要执行的内容, 属于微任务, 满足条件的微任务, 会被添加到当前宏任务的最后去执行

    执行流程
    1. 主线程先判断任务类型
      • 如果是同步任务,主线程自己执行
      • 如果是异步任务,交给宿主环境(浏览器)执行
    2. 浏览器进行异步任务的执行,每个异步执行完后,会将回调放进任务队列,先执行完成的先放进任务队列,依次放入
    3. 等主线程任务全部执行完后,发现主线程没有任务可执行了,会取任务队列中的任务,由于任务队列里是依次放入进来的,所以取得时候也会先取先进来的,也就是先进先出原则
    4. 在任务队列中取出来的任务执行完后,在取下一个,依次重复,这个过程也称为 eventLoop 事件轮训

    2.8-async/await 是什么?

    ES7 标准中新增的 async 函数,从目前的内部实现来说其实就是 Generator 函数的语法糖。

    它基于 Promise,并与所有现存的基于 Promise 的 API 兼容。

    async 关键字

    1. async 关键字用于声明⼀个异步函数(如 async function asyncTask1() {...}
    2. async 会⾃动将常规函数转换成 Promise,返回值也是⼀个 Promise 对象
    3. async 函数内部可以使⽤ await

    await 关键字

    1. await 用于等待异步的功能执⾏完毕 var result = await someAsyncCall()
    2. await 放置在 Promise 调⽤之前,会强制 async 函数中其他代码等待,直到 Promise 完成并返回结果
    3. await 只能与 Promise ⼀起使⽤
    4. await 只能在 async 函数内部使⽤

    2.9-相较于 Promise,async/await 有何优势?

    1. 同步化代码的阅读体验(Promise 虽然摆脱了回调地狱,但 then 链式调⽤的阅读负担还是存在的)
    2. 和同步代码更一致的错误处理方式( async/await 可以⽤成熟的 try/catch 做处理,比 Promise 的错误捕获更简洁直观)
    3. 调试时的阅读性, 也相对更友好

    3.0-深拷贝 浅拷贝

    引用类型, 进行赋值时, 赋值的是地址

    1.浅拷贝

    对数据拷贝的时候只拷贝一层,深层次的只拷贝了地址

    2.深拷贝

    JSON 方法实现深拷贝
    var obj = {
      a: 1,
      c: {
        c1: 1,
        c2: 2
      }
    }
    var str = JSON.stringify(obj)
    var b = JSON.parse(str)
    // 修改 obj的属性
    obj.c.c1 = 2
    console.log(b)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们先将需要拷贝的代码利用 JSON.stringify 转成字符转,然后再利用

    JSON.parse 将字符转转回对象,即完成拷贝

    问题:

    • 造成数据丢失和数据异常
    • function、undefined 直接丢失
    • NaN、Infinity 和-Infinity 变成 null
    • RegExpError对象只得到空对象;
    递归深拷贝

    递归实现的思路是什么样的?我们来分析一下

    1. 我们肯定要定义一个方法,那么这个方法最终应该返回一个深拷贝的数据
    2. 既然要返回一个数据,我们首先就要定义一个数据,但是数据是对象还是数组?所以需要判断,如果要拷贝的数据是数组,即定义一个数组,如果是一个对象,即定义一个对象
    3. 方法里面怎么拷贝啊?还是一样的利用 for in 循环,在循环内部,需要判断,如果是类型是简单类型,直接拷贝,如果是引用类型,就需要在一次的将引用类型里面的值取出来

    但是递归也会遇到上面同样的问题

    数据丢失和异常处理

    处理函数 Symbol 正则 Error 等数据类型正常拷贝

    // 日期格式
    if (obj instanceof Date) {
      return new Date(obj)
    }
    // Symbol
    if (obj instanceof Symbol) {
      return new Symbol(obj)
    }
    // 函数
    if (obj instanceof Function) {
      return new Function(obj)
    }
    // 正则
    if (obj instanceof RegExp) {
      return new RegExp(obj)
    }		
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    循环引用问题

    数据自己引用自己,此时拷贝就会进入死循环

    解决思路:

    将每次拷贝的数据进行存储,每次在拷贝之前,先看该数据是否拷贝过,如果拷贝过,直接返回,不在拷贝,如果没有拷贝,对该数据进行拷贝并记录该数据以拷贝

    1、使用数组

    // 要是有 别再循环拷贝了 直接返回 该值
    // 数据不存在,保存源数据,以及对应的引用
    // 新增方法,用于查找
    
    • 1
    • 2
    • 3

    2、使用 map 数据:强引用,无法被垃圾回收

    // 读取要拷贝的数据
    // 要是有 别再循环拷贝了 直接返回 该值
    // 存拷贝的数据
    
    • 1
    • 2
    • 3

    3、使用 hash 表:弱引用,可被垃圾回收

    // 读取要拷贝的数据 // 要是有 别再循环拷贝了 直接返回 该值
    // 新增代码,哈希表设值
    // 新增代码,传入哈希表
    
    • 1
    • 2
    • 3

    使用 lodash 实现深拷贝

    3.1-常见状态码

    304表示,客户端有缓存文件并向服务器发送了一个options请求,服务器返回304 Not Modified,告诉客户端,原来缓存的文件没有修改过,可以继续使用原来缓存的文件。

    403: ‘得到访问授权,但访问是被禁止’,

    404: ‘访问的是不存在的资源’,

    500: ‘服务器不可用,未返回正确的数据’,

    3.2-重绘(repaint)和重排(回流 reflow)是什么?

    重排

    重排是指部分或整个渲染树需要重新分析,并且节点的尺⼨需要重新计算。

    表现为 重新⽣成布局,重新排列元素。

    重绘

    重绘是由于节点的⼏何属性发⽣改变,或由于样式发⽣改变(例如:改变元素背景⾊)。

    表现为某些元素的外观被改变。或者重排后, 进行重新绘制!

    两者的关系

    重绘不⼀定会出现重排,重排必定会触发重绘。

    每个页面至少需要一次回流+重绘。(初始化渲染)

    重排和重绘的代价都很⾼昂,频繁重排重绘, 会破坏⽤户体验、让界面显示变迟缓。

    我们需要尽可能避免频繁触发重排和重绘, 尤其是重排

    3.3-何时会触发重排?

    重排什么时候发生?

    1、添加或者删除可见的 DOM 元素;

    2、元素位置改变;

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

    4、内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;

    5、页面渲染初始化;

    6、浏览器窗口尺寸改变——resize 事件发生时;

    3.4-如何实现跨域获取数据?

    历史上出现过的跨域⼿段有很多,主要了解 3 种跨域⽅案:

    • JSONP
    • CORS
    • 服务器代理(webpack 代理, Nginx 反向代理)

    JSONP

    这是一种非常经典的跨域方案,它利用了