当前位置 博文首页 > God_Y:编程面试题整理

    God_Y:编程面试题整理

    作者:[db:作者] 时间:2021-07-29 15:36

    点进来之后你的噩梦就要来了,接下来你要面对上百道面试题,那么,如果你——

    是个小白菜:
    推荐使用2~3周的时间来消化接下来的面试题,
    遇到不会的没听说过名词请立刻去搜;
    文章中只是简答,如果想要详细了解的话还需要你自觉去搜索
    如果你是个大神:
    好叭先给您拜个早年,大哥大嫂过年好。
    请温柔点黑我。
    顺便,如果有错误的地方请各位一定要指出,免得误导更多人。
    接下来的题我会根据重点程度使用?来标记,?越多标明越重点,满星是5颗星
    ok,你准备好了吗?咱们开始吧!

    在这里插入图片描述

    JS
    数据类型
    面试官:JavaScript中什么是基本数据类型什么是引用数据类型?以及各个数据类型是如何存储的??????

    答:
    基本数据类型有

    Number
    String
    Boolean
    Null
    Undefined
    Symbol(ES6新增数据类型)
    bigInt
    引用数据类型统称为Object类型,细分的话有

    Object
    Array
    Date
    Function
    RegExp
    基本数据类型的数据直接存储在栈中;而引用数据类型的数据存储在堆中,每个对象在堆中有一个引用地址。引用类型在栈中会保存他的引用地址,以便快速查找到堆内存中的对象。

    顺便提一句,栈内存是自动分配内存的。而堆内存是动态分配内存的,不会自动释放。所以每次使用完对象的时候都要把它设置为null,从而减少无用内存的消耗

    类型转换
    面试官:在JS中为什么0.2+0.1>0.3?????

    答:

    因为在JS中,浮点数是使用64位固定长度来表示的,其中的1位表示符号位,11位用来表示指数位,剩下的52位尾数位,由于只有52位表示尾数位。

    而0.1转为二进制是一个无限循环数0.0001100110011001100…(1100循环)

    小数的十进制转二进制方法:https://jingyan.baidu.com/article/425e69e6e93ca9be15fc1626.html
    要知道,小数的十进制转二进制的方法是和整数不一样的,推荐看一看

    由于只能存储52位尾数位,所以会出现精度缺失,把它存到内存中再取出来转换成十进制就不是原来的0.1了,就变成了0.100000000000000005551115123126,而为什么02+0.1是因为

    // 0.1 和 0.2 都转化成二进制后再进行运算
    0.00011001100110011001100110011001100110011001100110011010 +
    0.0011001100110011001100110011001100110011001100110011010 =
    0.0100110011001100110011001100110011001100110011001100111

    // 转成十进制正好是 0.30000000000000004
    1
    2
    3
    4
    5
    6
    面试官:那为什么0.2+0.3=0.5呢?????

    // 0.2 和 0.3 都转化为二进制后再进行计算
    0.001100110011001100110011001100110011001100110011001101 +
    0.0100110011001100110011001100110011001100110011001101 =
    0.10000000000000000000000000000000000000000000000000001 //尾数为大于52位

    // 而实际取值只取52位尾数位,就变成了
    0.1000000000000000000000000000000000000000000000000000 //0.5
    1
    2
    3
    4
    5
    6
    7
    答:0.2 和0.3分别转换为二进制进行计算:在内存中,它们的尾数位都是等于52位的,而他们相加必定大于52位,而他们相加又恰巧前52位尾数都是0,截取后恰好是0.1000000000000000000000000000000000000000000000000000也就是0.5

    面试官:那既然0.1不是0.1了,为什么在console.log(0.1)的时候还是0.1呢????

    答:在console.log的时候会二进制转换为十进制,十进制再会转为字符串的形式,在转换的过程中发生了取近似值,所以打印出来的是一个近似值的字符串

    面试官:判断数据类型有几种方法?????

    答:

    typeof

    缺点:typeof null的值为Object,无法分辨是null还是Object
    instanceof

    缺点:只能判断对象是否存在于目标对象的原型链上
    constructor

    Object.prototype.toString.call()

    一种最好的基本类型检测方式 Object.prototype.toString.call() ;它可以区分 null 、 string 、

    boolean 、 number 、 undefined 、 array 、 function 、 object 、 date 、 math 数据类型。

    缺点:不能细分为谁谁的实例

    // -----------------------------------------typeof
    typeof undefined // 'undefined' 
    typeof '10' // 'String' 
    typeof 10 // 'Number' 
    typeof false // 'Boolean' 
    typeof Symbol() // 'Symbol' 
    typeof Function // ‘function' 
    typeof null // ‘Object’ 
    typeof [] // 'Object' 
    typeof {} // 'Object'
    
    
    // -----------------------------------------instanceof
    function Foo() { }
    var f1 = new Foo();
    var d = new Number(1)
    
    
    console.log(f1 instanceof Foo);// true
    console.log(d instanceof Number); //true
    console.log(123 instanceof Number); //false   -->不能判断字面量的基本数据类型
    
    
    // -----------------------------------------constructor
    var d = new Number(1)
    var e = 1
    function fn() {
      console.log("ming");
    }
    var date = new Date();
    var arr = [1, 2, 3];
    var reg = /[hbc]at/gi;
    
    console.log(e.constructor);//? Number() { [native code] }
    console.log(e.constructor.name);//Number
    console.log(fn.constructor.name) // Function 
    console.log(date.constructor.name)// Date 
    console.log(arr.constructor.name) // Array 
    console.log(reg.constructor.name) // RegExp
    
    
    
    
    //-----------------------------------------Object.prototype.toString.call()
    console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]" 
    console.log(Object.prototype.toString.call(null)); // "[object Null]" 
    console.log(Object.prototype.toString.call(123)); // "[object Number]" 
    console.log(Object.prototype.toString.call("abc")); // "[object String]" 
    console.log(Object.prototype.toString.call(true)); // "[object Boolean]" 
    
    
    function fn() {
      console.log("ming");
    }
    var date = new Date();
    var arr = [1, 2, 3];
    var reg = /[hbc]at/gi;
    console.log(Object.prototype.toString.call(fn));// "[object Function]" 
    console.log(Object.prototype.toString.call(date));// "[object Date]" 
    console.log(Object.prototype.toString.call(arr)); // "[object Array]"
    console.log(Object.prototype.toString.call(reg));// "[object RegExp]"
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    instanceof原理?????

    instanceof原理实际上就是查找目标对象的原型链
    function myInstance(L, R) {//L代表instanceof左边,R代表右边
    var RP = R.prototype
    var LP = L.proto
    while (true) {
    if(LP == null) {
    return false
    }
    if(LP == RP) {
    return true
    }
    LP = LP.proto
    }
    }
    console.log(myInstance({},Object));

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    面试官:为什么typeof null是Object????

    答:

    因为在JavaScript中,不同的对象都是使用二进制存储的,如果二进制前三位都是0的话,系统会判断为是Object类型,而null的二进制全是0,自然也就判断为Object

    这个bug是初版本的JavaScript中留下的,扩展一下其他五种标识位:

    000 对象
    1 整型
    010 双精度类型
    100字符串
    110布尔类型

    面试官:=有什么区别?????

    答:

    ===是严格意义上的相等,会比较两边的数据类型和值大小

    数据类型不同返回false
    数据类型相同,但值大小不同,返回false
    ==是非严格意义上的相等,

    两边类型相同,比较大小

    两边类型不同,根据下方表格,再进一步进行比较。

    Null == Undefined ->true
    String == Number ->先将String转为Number,在比较大小
    Boolean == Number ->现将Boolean转为Number,在进行比较
    Object == String,Number,Symbol -> Object 转化为原始类型

    面试官:手写call、apply、bind?????

    答:

    call和apply实现思路主要是:
    判断是否是函数调用,若非函数调用抛异常
    通过新对象(context)来调用函数
    给context创建一个fn设置为需要调用的函数
    结束调用完之后删除fn
    bind实现思路
    判断是否是函数调用,若非函数调用抛异常
    返回函数
    判断函数的调用方式,是否是被new出来的
    new出来的话返回空对象,但是实例的__proto__指向_this的prototype
    完成函数柯里化
    Array.prototype.slice.call()
    call:

    Function.prototype.myCall = function (context) {
      // 先判断调用myCall是不是一个函数
      // 这里的this就是调用myCall的
      if (typeof this !== 'function') {
        throw new TypeError("Not a Function")
      }
    
      // 不传参数默认为window
      context = context || window
    
      // 保存this
      context.fn = this
    
      // 保存参数
      let args = Array.from(arguments).slice(1)   //Array.from 把伪数组对象转为数组
    
      // 调用函数
      let result = context.fn(...args)
    
      delete context.fn
    
      return result
    
    }
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    apply

    Function.prototype.myApply = function (context) {
    // 判断this是不是函数
    if (typeof this !== “function”) {
    throw new TypeError(“Not a Function”)
    }

      let result
    
      // 默认是window
      context = context || window
    
      // 保存this
      context.fn = this
    
      // 是否传参
      if (arguments[1]) {
        result = context.fn(...arguments[1])
      } else {
        result = context.fn()
      }
      delete context.fn
    
      return result
    }
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    bind

    Function.prototype.myBind = function(context){
      // 判断是否是一个函数
      if(typeof this !== "function") {
        throw new TypeError("Not a Function")
      }
      // 保存调用bind的函数
      const _this = this 
      // 保存参数
      const args = Array.prototype.slice.call(arguments,1)
      // 返回一个函数
      return function F () {
        // 判断是不是new出来的
        if(this instanceof F) {
          // 如果是new出来的
          // 返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化
          return new _this(...args,...arguments)
        }else{
          // 如果不是new出来的改变this指向,且完成函数柯里化
          return _this.apply(context,args.concat(...arguments))
        }
      } 
    }
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    面试官:字面量创建对象和new创建对象有什么区别,new内部都实现了什么,手写一个new?????

    答:

    字面量:

    字面量创建对象更简单,方便阅读
    不需要作用域解析,速度更快
    new内部:

    创建一个新对象
    使新对象的__proto__指向原函数的prototype
    改变this指向(指向新的obj)并执行该函数,执行结果保存起来作为result
    判断执行函数的结果是不是null或Undefined,如果是则返回之前的新对象,如果不是则返回result
    手写new

    // 手写一个new
    function myNew(fn, ...args) {
      // 创建一个空对象
      let obj = {}
      // 使空对象的隐式原型指向原函数的显式原型
      obj.__proto__ = fn.prototype
      // this指向obj
      let result = fn.apply(obj, args)
      // 返回
      return result instanceof Object ? result : obj
    }
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    面试官:字面量new出来的对象和 Object.create(null)创建出来的对象有什么区别???

    答:

    字面量和new创建出来的对象会继承Object的方法和属性,他们的隐式原型会指向Object的显式原型,

    而 Object.create(null)创建出来的对象原型为null,作为原型链的顶端,自然也没有继承Object的方法和属性

    执行栈和执行上下文
    面试官:什么是作用域,什么是作用域链?????

    答:

    规定变量和函数的可使用范围称作作用域
    每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链。
    面试官:什么是执行栈,什么是执行上下文?????

    答:

    执行上下文分为:

    全局执行上下文
    创建一个全局的window对象,并规定this指向window,执行js的时候就压入栈底,关闭浏览器的时候才弹出
    函数执行上下文
    每次函数调用时,都会新创建一个函数执行上下文
    执行上下文分为创建阶段和执行阶段
    创建阶段:函数环境会创建变量对象:arguments对象(并赋值)、函数声明(并赋值)、变量声明(不赋值),函数表达式声明(不赋值);会确定this指向;会确定作用域
    执行阶段:变量赋值、函数表达式赋值,使变量对象编程活跃对象
    eval执行上下文
    执行栈:

    首先栈特点:先进后出
    当进入一个执行环境,就会创建出它的执行上下文,然后进行压栈,当程序执行完成时,它的执行上下文就会被销毁,进行弹栈。
    栈底永远是全局环境的执行上下文,栈顶永远是正在执行函数的执行上下文
    只有浏览器关闭的时候全局执行上下文才会弹出
    闭包
    很多人都吃不透js闭包,这里推荐一篇文章:彻底理解js中的闭包

    面试官:什么是闭包?闭包的作用?闭包的应用??????

    答:

    函数执行,形成私有的执行上下文,使内部私有变量不受外界干扰,起到保护和保存的作用

    作用:

    保护
    避免命名冲突
    保存
    解决循环绑定引发的索引问题
    变量不会销毁
    可以使用函数内部的变量,使变量不会被垃圾回收机制回收
    应用:

    设计模式中的单例模式
    for循环中的保留i的操作
    防抖和节流
    函数柯里化
    缺点

    会出现内存泄漏的问题
    原型和原型链
    面试官:什么是原型?什么是原型链?如何理解?????

    答:

    原型: 原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。

    原型链: 多个__proto__组成的集合成为原型链

    所有实例的__proto__都指向他们构造函数的prototype
    所有的prototype都是对象,自然它的__proto__指向的是Object()的prototype
    所有的构造函数的隐式原型指向的都是Function()的显示原型
    Object的隐式原型是null
    继承
    面试官:说一说 JS 中的常用的继承方式有哪些?以及各个继承方式的优缺点。?????

    答:

    原型继承、组合继承、寄生组合继承、ES6的extend

    原型继承

    // ----------------------方法一:原型继承
    // 原型继承
    // 把父类的实例作为子类的原型
    // 缺点:子类的实例共享了父类构造函数的引用属性   不能传参
    
    var person = {
      friends: ["a", "b", "c", "d"]
    }
    
    var p1 = Object.create(person)
    
    p1.friends.push("aaa")//缺点:子类的实例共享了父类构造函数的引用属性
    
    console.log(p1);
    console.log(person);//缺点:子类的实例共享了父类构造函数的引用属性
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    组合继承

    // ----------------------方法二:组合继承
    // 在子函数中运行父函数,但是要利用call把this改变一下,
    // 再在子函数的prototype里面new Father() ,使Father的原型中的方法也得到继承,最后改变Son的原型中的constructor
    
    // 缺点:调用了两次父类的构造函数,造成了不必要的消耗,父类方法可以复用
    // 优点可传参,不共享父类引用属性
    function Father(name) {
      this.name = name
      this.hobby = ["篮球", "足球", "乒乓球"]
    }
    
    Father.prototype.getName = function () {
      console.log(this.name);
    }
    
    function Son(name, age) {
      Father.call(this, name)
      this.age = age
    }
    
    Son.prototype = new Father()
    Son.prototype.constructor = Son
    
    
    var s = new Son("ming", 20)
    
    console.log(s);
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    寄生组合继承

    // ----------------------方法三:寄生组合继承
    function Father(name) {
      this.name = name
      this.hobby = ["篮球", "足球", "乒乓球"]
    }
    
    Father.prototype.getName = function () {
      console.log(this.name);
    }
    
    function Son(name, age) {
      Father.call(this, name)
      this.age = age
    }
    
    Son.prototype = Object.create(Father.prototype)
    Son.prototype.constructor = Son
    
    var s2 = new Son("ming", 18)
    console.log(s2);
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    extend

    // ----------------------方法四:ES6的extend(寄生组合继承的语法糖)
    //     子类只要继承父类,可以不写 constructor ,一旦写了,则在 constructor 中的第一句话
    // 必须是 super 。
    
    class Son3 extends Father { // Son.prototype.__proto__ = Father.prototype
      constructor(y) {
        super(200)  // super(200) => Father.call(this,200)
        this.y = y
      }
    }
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    内存泄露、垃圾回收机制
    面试官:什么是内存泄漏?????

    答:

    ? 内存泄露是指不再用的内存没有被及时释放出来,导致该段内存无法被使用就是内存泄漏

    面试官:为什么会导致的内存泄漏?????

    答:

    内存泄漏指我们无法在通过js访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法释放,积少成多,系统会越来越卡以至于崩溃

    面试官:垃圾回收机制都有哪些策略??????

    答:

    标记清除法
    垃圾回收机制获取根并标记他们,然后访问并标记所有来自它们的引用,然后在访问这些对象并标记它们的引用…如此递进结束后若发现有没有标记的(不可达的)进行删除,进入执行环境的不能进行删除
    引用计数法
    当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数+1,当该值赋值给另一个变量的时候,该计数+1,当该值被其他值取代的时候,该计数-1,当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象
    深拷贝和浅拷贝
    手写浅拷贝深拷贝?????

    // ----------------------------------------------浅拷贝
    // 只是把对象的属性和属性值拷贝到另一个对象中
    var obj1 = {
      a: {
        a1: { a2: 1 },
        a10: { a11: 123, a111: { a1111: 123123 } }
      },
      b: 123,
      c: "123"
    }
    // 方式1
    function shallowClone1(o) {
      let obj = {}
    
      for (let i in o) {
        obj[i] = o[i]
      }
      return obj
    }
    
    // 方式2
    var shallowObj2 = { ...obj1 }
    
    // 方式3
    var shallowObj3 = Object.assign({}, obj1)
    
    let shallowObj = shallowClone1(obj1);
    
    shallowObj.a.a1 = 999
    shallowObj.b = true
    
    console.log(obj1);  //第一层的没有被改变,一层以下就被改变了
    
    
    
    // ----------------------------------------------深拷贝
    
    // 简易版  
    function deepClone(o) {
      let obj = {}
      for (var i in o) {
        // if(o.hasOwnProperty(i)){
        if (typeof o[i] === "object") {
          obj[i] = deepClone(o[i])
        } else {
          obj[i] = o[i]
        }
        // }
      }
      return obj
    }
    
    
    var myObj = {
      a: {
        a1: { a2: 1 },
        a10: { a11: 123, a111: { a1111: 123123 } }
      },
      b: 123,
      c: "123"
    }
    
    var deepObj1 = deepClone(myObj)
    deepObj1.a.a1 = 999
    deepObj1.b = false
    console.log(myObj);
    
    
    
    // 简易版存在的问题:参数没有做检验,传入的可能是 Array、null、regExp、Date
    function deepClone2(o) {
      if (Object.prototype.toString.call(o) === "[object Object]") {  //检测是否为对象
        let obj = {}
        for (var i in o) {
          if (o.hasOwnProperty(i)) {
            if (typeof o[i] === "object") {
              obj[i] = deepClone(o[i])
            } else {
              obj[i] = o[i]
            }
          }
        }
        return obj
      } else {
        return o
      }
    }
    
    function isObject(o) {
      return Object.prototype.toString.call(o) === "[object Object]" || Object.prototype.toString.call(o) === "[object Array]"
    }
    
    // 继续升级,没有考虑到数组,以及ES6中的map、set、weakset、weakmap
    function deepClone3(o) {
      if (isObject(o)) {//检测是否为对象或者数组
        let obj = Array.isArray(o) ? [] : {}
        for (let i in o) {
          if (isObject(o[i])) {
            obj[i] = deepClone(o[i])
          } else {
            obj[i] = o[i]
          }
        }
        return obj
      } else {
        return o
      }
    }
    
    
    // 有可能碰到循环引用问题  var a = {}; a.a = a; clone(a);//会造成一个死循环
    // 循环检测
    // 继续升级
    function deepClone4(o, hash = new map()) {
      if (!isObject(o)) return o//检测是否为对象或者数组
      if (hash.has(o)) return hash.get(o)
      let obj = Array.isArray(o) ? [] : {}
    
      hash.set(o, obj)
      for (let i in o) {
        if (isObject(o[i])) {
          obj[i] = deepClone4(o[i], hash)
        } else {
          obj[i] = o[i]
        }
      }
      return obj
    }
    
    // 递归易出现爆栈问题
    //  将递归改为循环,就不会出现爆栈问题了
    var a1 = { a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' };
    var b1 = { b: { c: { d: 1 } } }
    function cloneLoop(x) {
      const root = {};
      // 栈 
      const loopList = [  //->[]->[{parent:{a:1,b:2},key:c,data:{ c1: 3, c2: { c21: 4, c22: 5 } }}]
        {
          parent: root,
          key: undefined,
          data: x,
        }
      ];
      while (loopList.length) {
        // 深度优先
        const node = loopList.pop();
        const parent = node.parent; //{} //{a:1,b:2}
        const key = node.key; //undefined //c
        const data = node.data; //{ a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' }  //{ c1: 3, c2: { c21: 4, c22: 5 } }}
        // 初始化赋值目标,key 为 undefined 则拷贝到父元素,否则拷贝到子元素
        let res = parent; //{}->{a:1,b:2,d:'asd'} //{a:1,b:2}->{}
        if (typeof key !== 'undefined') {
          res = parent[key] = {};
        }
        for (let k in data) {
          if (data.hasOwnProperty(k)) {
            if (typeof data[k] === 'object') {
              // 下一次循环 
              loopList.push({
                parent: res,
                key: k,
                data: data[k],
              })
            } else {
              res[k] = data[k];
            }
          }
        }
      }
      return root
    }
    
    
    function deepClone5(o) {
      let result = {}
      let loopList = [
        {
          parent: result,
          key: undefined,
          data: o
        }
      ]
    
      while (loopList.length) {
        let node = loopList.pop()
        let { parent, key, data } = node
        let anoPar = parent
        if (typeof key !== 'undefined') {
          anoPar = parent[key] = {}
        }
    
        for (let i in data) {
          if (typeof data[i] === 'object') {
            loopList.push({
              parent: anoPar,
              key: i,
              data: data[i]
            })
          } else {
            anoPar[i] = data[i]
          }
        }
      }
      return result
    }
    
    
    let cloneA1 = deepClone5(a1)
    cloneA1.c.c2.c22 = 5555555
    console.log(a1);
    console.log(cloneA1);
    
    
    // ------------------------------------------JSON.stringify()实现深拷贝
    
    function cloneJson(o) {
      return JSON.parse(JSON.stringify(o))
    }
    
    // let obj = { a: { c: 1 }, b: {} };
    // obj.b = obj;
    // console.log(JSON.parse(JSON.stringify(obj))) // 报错 // Converting circular structure to JSON
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    深拷贝能使用hash递归的方式写出来就可以了
    不过技多不压身,推荐还是看一看使用while实现深拷贝方法

    单线程,同步异步
    面试官:为什么JS是单线程的??????

    **答:**因为JS里面有可视的Dom,如果是多线程的话,这个线程正在删除DOM节点,另一个线程正在编辑Dom节点,导致浏览器不知道该听谁的

    面试官:如何实现异步编程?

    **答:**回调函数

    面试官:Generator是怎么样使用的以及各个阶段的变化如何????

    答:

    首先生成器是一个函数,用来返回迭代器的

    调用生成器后不会立即执行,而是通过返回的迭代器来控制这个生成器的一步一步执行的

    通过调用迭代器的next方法来请求一个一个的值,返回的对象有两个属性,一个是value,也就是值;另一个是done,是个布尔类型,done为true说明生成器函数执行完毕,没有可返回的值了,

    done为true后继续调用迭代器的next方法,返回值的value为undefined

    状态变化:

    每当执行到yield属性的时候,都会返回一个对象
    这时候生成器处于一个非阻塞的挂起状态
    调用迭代器的next方法的时候,生成器又从挂起状态改为执行状态,继续上一次的执行位置执行
    直到遇到下一次yield依次循环
    直到代码没有yield了,就会返回一个结果对象done为true,value为undefined
    面试官:说说 Promise 的原理?你是如何理解 Promise 的??????

    做到会写简易版的promise和all函数就可以
    答:

    class MyPromise2 {
    constructor(executor) {
    // 规定状态
    this.state = “pending”
    // 保存 resolve(res) 的res值
    this.value = undefined
    // 保存 reject(err) 的err值
    this.reason = undefined
    // 成功存放的数组
    this.successCB = []
    // 失败存放的数组
    this.failCB = []

        let resolve = (value) => {
          if (this.state === "pending") {
            this.state = "fulfilled"
            this.value = value
            this.successCB.forEach(f => f())
          }
        }
        let reject = (reason) => {
          if (this.state === "pending") {
            this.state = "rejected"
            this.value = value
            this.failCB.forEach(f => f())
          }
        }
    
        try {
          // 执行
          executor(resolve, reject)
        } catch (error) {
          // 若出错,直接调用reject
          reject(error)
        }
      }
      then(onFulfilled, onRejected) {
        if (this.state === "fulfilled") {
          onFulfilled(this.value)
        }
        if (this.state === "rejected") {
          onRejected(this.value)
        }
        if (this.state === "pending") {
          this.successCB.push(() => { onFulfilled(this.value) })
          this.failCB.push(() => { onRejected(this.reason) })
        }
      }
    }
    
    
    Promise.all = function (promises) {
      let list = []
      let count = 0
      function handle(i, data) {
        list[i] = data
        count++
        if (count == promises.length) {
          resolve(list)
        }
      }
      return Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
          promises[i].then(res => {
            handle(i, res)
          }, err => reject(err))
        }
      })
    }
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    面试官:以下代码的执行顺序是什么?????

    答:

    async function async1() {

    上一篇:没有了
    下一篇:没有了