1.前端面试知识点及常考面试题

73 阅读6分钟

学习来源支持正版:juejin.im/book/5bdc71…

原始(Primitive)类型

在 JS 中,存在着 7 种原始值,分别是:

  • boolean
  • null
  • undefined
  • number
  • string
  • symbol
  • bigint

首先原始类型存储的都是值,是没有函数可以调用的,比如 undefined.toString() null.toString()
'1'.toString() 是可以使用,被强制转换成了 String 类型也就是对象类型,
null 来说,很多人会认为他是个对象类型,其实这是错误的。虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来

对象类型

原始类型存储的是值,对象类型存储的是地址(指针)。当你创建了一个对象类型的时候,计算机会在内存中帮我们开辟一个空间来存放值,但是我们需要找到这个空间,这个空间会拥有一个地址(指针)

typeof vs instanceof

typeof 对于原始类型来说,除了 null 都可以显示正确的类型
typeof 对于对象来说,除了函数都会显示 object

instanceof 判断类型方法

class PrimitiveString {
  static [Symbol.hasInstance](x) {
    return typeof x === 'string'
  }
}
console.log('hello world' instanceof PrimitiveString) //true

类型转换

首先我们要知道,在 JS 中类型转换只有三种情况,分别是:

  • 转换为布尔值
  • 转换为数字
  • 转换为字符串

转Boolean

在条件判断时,除了 undefinednullfalseNaN''0-0,其他所有值都转为 true,包括所有对象。

[]==![]  // ![]等于true,true和空数组比,此时数组是对象,所以都是true,但是若完全等于[]===![]时,两者不进行转换,所以true和[]比较是false

对象转原始类型

对象在转换类型的时候,会调用内置的 [[ToPrimitive]] 函数,对于该函数来说,算法逻辑一般来说如下:

  • 如果已经是原始类型了,那就不需要转换了
  • 调用 x.valueOf(),如果转换为基础类型,就返回转换的值
  • 调用 x.toString(),如果转换为基础类型,就返回转换的值
  • 如果都没有返回原始类型,就会报错

当然你也可以重写 Symbol.toPrimitive ,该方法在转原始类型时调用优先级最高。

let a = {
  valueOf() {
    return 0
  },
  toString() {
    return '1'
  },
  [Symbol.toPrimitive]() {
    return 2
  }
}
1 + a // => 3

四则运算符

加法运算符不同于其他几个运算符,它有以下几个特点:

  • 运算中其中一方为字符串,那么就会把另一方也转换为字符串
  • 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
1 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3"

如果你对于答案有疑问的话,请看解析:

  • 对于第一行代码来说,触发特点一,所以将数字 1 转换为字符串,得到结果 '11'
  • 对于第二行代码来说,触发特点二,所以将 true 转为数字 1
  • 对于第三行代码来说,触发特点二,所以将数组通过 toString 转为字符串 1,2,3,得到结果 41,2,3

另外对于加法还需要注意这个表达式 'a' + + 'b'

'a' + + 'b' // -> "aNaN"

因为 + 'b' 等于 NaN,所以结果为 "aNaN",你可能也会在一些代码中看到过 + '1' 的形式来快速获取 number 类型。

那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字

4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN

比较运算符

  1. 如果是对象,就通过 toPrimitive 转换对象
  2. 如果是字符串,就通过 unicode 字符索引来比较
let a = {
  valueOf() {
    return 0
  },
  toString() {
    return '1'
  }
}
a > -1 // true

在以上代码中,因为 a 是对象,所以会通过 valueOf 转换为原始类型再比较值。

== vs ===

对于 == 来说,如果对比双方的类型不一样的话,就会进行类型转换

  1. 首先会判断两者类型是否相同。相同的话就是比大小了
  2. 类型不相同的话,那么就会进行类型转换
  3. 会先判断是否在对比 nullundefined,是的话就会返回 true
  4. 判断两者类型是否为 stringnumber,是的话就会将字符串转换为 number
    1 == '1'1 ==  1
    
  5. 判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断
    '1' == true'1' ==  11  ==  1
    
  6. 判断其中一方是否为 object 且另一方为 stringnumber 或者 symbol,是的话就会把 object 转为原始类型再进行判断
    '1' == { name: 'yck' }
            ↓
    '1' == '[object Object]'
    

=== 来说就简单多了,就是判断两者类型和值是否相同。

this

箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this

let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => ?

如果你认为输出结果是 a,那么你就错了,其实我们可以把上述代码转换成另一种形式

// fn.bind().bind(a) 等于
let fn2 = function fn1() {
  return function() {
    return fn.apply()
  }.apply(a)
}
fn2()

可以从上述代码中发现,不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以结果永远是 window

var a = 1
var b = {
  a: 2,
  c: () => {
    var a = 3
    console.log('c',a,this.a)
  },
  d: function() {
    var a = 4
    console.log('d',a,this.a)
  },
  e: () => {
    var a = 5
    return () => {
      var a=6
      console.log('e',a,this.a)
    }
  },
 f: function()  {
    var a = 5
    return () => {
      var a=6
      console.log('f',a,this.a)
    }
  },

}

b.c()  // c 3 1  this指向从箭头函数向其父级第一个不是箭头函数的地方
var m = b.c
m() // c 3 1  // this指向从箭头函数向其父级第一个不是箭头函数的地方

b.d() // d 4 2 //this指向当前执行对象b本身的this指向
var n = b.d 
n() // d 4 1 // this指向当前声明n父级的this指向

b.e()() // e 6 1 // this指向从箭头函数向其父级第一个不是箭头函数的地方
var p = b.e
p()()   // e 6 1 // this指向从箭头函数向其父级第一个不是箭头函数的地方
var p = b.e()
p()   // e 6 1 // this指向从箭头函数向其父级第一个不是箭头函数的地方

b.f()()  // f 6 2  // 执行的箭头函数找到父级f不是箭头函数为止的this
var o = b.f
o()() // f 6 1 // 执行箭头函数找到父级本身的this指向
var o = b.f()
o() // f 6 2 // 执行箭头函数找到父级的内部匿名函数的this指向,