身为三本的我就是凭借这些前端面试题拿到百度京东offer的,前端面试题2021及答案 - Go语言中文社区

身为三本的我就是凭借这些前端面试题拿到百度京东offer的,前端面试题2021及答案


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

  • 是个小白菜:
    • 推荐使用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

面试官:那为什么0.2+0.3=0.5呢?⭐⭐⭐⭐

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

// 而实际取值只取52位尾数位,就变成了
0.1000000000000000000000000000000000000000000000000000   //0.5

答:0.20.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]"

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)); 
    

面试官:为什么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__指向_thisprototype
    • 完成函数柯里化
      • 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

    }

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
    }

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))
        }
      } 
    }

面试官:字面量创建对象和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
    }

面试官:字面量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);//缺点:子类的实例共享了父类构造函数的引用属性

组合继承

    // ----------------------方法二:组合继承
    // 在子函数中运行父函数,但是要利用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);

寄生组合继承

    // ----------------------方法三:寄生组合继承
    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);

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
      }
    }

内存泄露、垃圾回收机制

面试官:什么是内存泄漏⭐⭐⭐⭐⭐

答:

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

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

答:

内存泄漏指我们无法在通过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
                            
                            版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_33277654/article/details/112758362
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢