前端面试系列-JS 基础知识点

原始类型有哪几种?null 是对象嘛?基本数据类型和引用数据类型在存储上的区别?

六种原始数据类型(原始类型存储的都是值,是没有函数可以调用的),"1".toString() 为什么能够调用,因为"1"会强制转换为String类型也就是对象类型(装箱操作)。 JS 的 number 类型是浮点类型的,在使用中会遇到某些 Bug,比如 0.1 + 0.2 !== 0.3

  • string
  • number
  • boolean
  • null
  • undefined
  • symbol(es6新增)

null也并不是对象,虽然typeof null 为object。但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object

基本数据类型是存储在栈中的值,而引用数据类型是存在栈中的引用,查询的时候通过这个栈中的引用来查找对应中的堆中的具体的值。

0.1 + 0.2 !== 0.3

想要知道为什么0.1+0.2!=0.3 ,首先知道计算机用位来储存及处理数据,每一个二进制数(二进制串)都一一对应一个十进制数。十进制整数转二进制方法:除2取余;十进制小数转二进制方法:乘2取整。所以十进制0.1转化为2进制乘以2的过程就是
0.1 * 2 = 0.2 取 0 0.2 * 2 = 0.4 取 0 0.4 * 2 = 0.8 取 0 0.8 * 2 = 1.6 取1 0.6 * 2 = 1.2 取 1 0.2 * 2 = 0.4 取 0
所以0.1的二进制格式就是0.00011 无线循环,但计算机内存有限 在某个精度点直接舍弃。当然,代价就是,0.1在计算机内部根本就不是精确的0.1,而是一个有舍入误差的0.1 所以一开始计算之前0.1 就是不准确的 所以最终的结果0.1+0.2!=0.3

对象类型和原始类型的不同之处?函数参数是对象会发生什么问题?

原始类型存储的是具体的值,对象类型存储的是地址的引用(指针)。

参数是对象的情况。

    function test(person) {
      person.age = 26
      person = {
        name: 'yyy',
        age: 30
      }
    
      return person
    }
    const p1 = {
      name: 'yck',
      age: 25
    }
    const p2 = test(p1)
    console.log(p1) // -> ?
    console.log(p2) // -> ?
复制代码

过程如下图:

typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么?

  • typeof对于原始值,除了null都能判断出正确的类型。
  • typeof对于对象来说,除了函数都会显示object,所以说typeof不能准备判断类型
  • instanceof可以判断一个实例是否属于当前类,因此可以判断类型
    const Person = function() {}
    const p1 = new Person()
    p1 instanceof Person // true
    
    var str = 'hello world'
    str instanceof String // false
    
    var str1 = new String('hello world')
    str1 instanceof String // true
    
    
    //对于原始类型来说,你想直接通过 instanceof 来判断类型是不行的,当然我们还是有办法让 instanceof 判断原始类型的
    class PrimitiveString {
      static [Symbol.hasInstance](x) {
        return typeof x === 'string'
      }
    }
    console.log('hello world' instanceof PrimitiveString) // true
复制代码

类型转换

  • undefined, null, false, NaN, '', 0, -0 为false外,其余都为true
  • 对象转换为原始类型会调用valueof()和toString()(不同的运算两者的顺序还会有所不同),当然你也可以重写 Symbol.toPrimitive ,该方法在转原始类型时调用优先级最高。
    let a = {
      valueOf() {
        return 0
      },
      toString() {
        return '1'
      },
      [Symbol.toPrimitive]() {
        return 2
      }
    }
    1 + a // => 3
复制代码
  • 'a'++'b' // 'aNaN' ,因为 + 'b' 等于 NaN,所以结果为 "aNaN",你可能也会在一些代码中看到过 + '1' 的形式来快速获取 number 类型
  • 比较运算符
    • 如果是对象,就通过 toPrimitive 转换对象
    • 如果是字符串,就通过 unicode 字符索引来比较
    let a = {
      valueOf() {
        return 0
      },
      toString() {
        return '1'
      }
    }
    a > -1 // true
复制代码

this指向问题

    function foo() {
      console.log(this.a)
    }
    var a = 1
    foo()
    
    const obj = {
      a: 2,
      foo: foo
    }
    obj.foo()
    
    const c = new foo()
复制代码
  • foo() 无论在什么环境下执行,都是window
  • obj.foo() 看是谁调用的foo ,this就是谁(也就是obj)
  • new 出来的this 指向当前实例(也就是c)
  • 箭头函数其实是没有 this 的,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this
  • 改变this的指向(call、apply、bind),bind多次的this只取决于第一次的bind。
    // 自己实现bind
    Function.prototype.testBind = function(that){
    var _this = this,
        /*
        *由于参数的不确定性,统一用arguments来处理,这里的arguments只是一个类数组对象,有length属性
        *可以用数组的slice方法转化成标准格式数组,除了作用域对象that以外,
        *后面的所有参数都需要作为数组参数传递
        *Array.prototype.slice.apply(arguments,[1])/Array.prototype.slice.call(arguments,1)
        */
        slice = Array.prototype.slice,
        args = slice.apply(arguments,[1]);
    //返回函数    
    return function(){
        //apply绑定作用域,进行参数传递
        return _this.apply(that,args)
    }    
}
复制代码
  • this的优先级(你不知道的js很清楚了讲了这个优先级的问题,这里列一下,从高到低)
    • new 方式
    • bind等函数
    • obj.foo()
    • foo()

== 和 === 有什么区别?([]==![],![] 转换为false,左边先转换为字符串"",最后两者都转换为0)

  • == 会进行隐式的类型转换(简单的理解最终两个类型不相同会转换为数字进行比较,如果到字符串级别可以比较了就不会再向下进行转换)
    • 判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number
    • 判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断
    • 判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断
  • 对于 === 来说就简单多了,就是判断两者类型和值是否相同。

闭包(经典)(函数能够记住访问其所在的词法作用域(你不知道的js书中解释))

函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。

经典面试题:循环中使用闭包解决 var 定义函数的问题

    for (var i = 1; i <= 5; i++) {
      setTimeout(function timer() {
        console.log(i)
      }, i * 1000)
    }
    // 6 6 6 6 6
    
    // 解决方法1:闭包
    for (var i = 1; i <= 5; i++) {
      ;(function(j) {
        setTimeout(function timer() {
          console.log(j)
        }, j * 1000)
      })(i)
    }
    
    // 解决方法2:setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入
    for (var i = 1; i <= 5; i++) {
      setTimeout(
        function timer(j) {
          console.log(j)
        },
        i * 1000,
        i
      )
    }
    
    // 解决方法3:let
    for (let i = 1; i <= 5; i++) {
      setTimeout(function timer() {
        console.log(i)
      }, i * 1000)
    }
复制代码

深浅拷贝及其实现

  • 浅拷贝
    • Object.assign()
    • es6的扩展运算符(...)
  • 深拷贝
    • JSON.parse(JSON.stringify(object)),但是有局限性。
      • 会忽略 undefined
      • 会忽略 symbol
      • 不能序列化函数
      • 不能解决循环引用的对象

    let a = {
      age: undefined,
      sex: Symbol('male'),
      jobs: function() {},
      name: 'xxx'
    }
    let b = JSON.parse(JSON.stringify(a))
    console.log(b) // {name: "xxx"}
复制代码
  • 如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel
    function structuralClone(obj) {
  return new Promise(resolve => {
    const { port1, port2 } = new MessageChannel()
    port2.onmessage = ev => resolve(ev.data)
    port1.postMessage(obj)
  })
}

var obj = {
  a: 1,
  b: {
    c: 2
  }
}

obj.b.d = obj.b

// 注意该方法是异步的
// 可以处理 undefined 和循环引用对象
const test = async () => {
  const clone = await structuralClone(obj)
  console.log(clone)
}
test()
复制代码
    // 自己实现的deepCopy
    function deepClone(obj) {
      function isObject(o) {
        return (typeof o === 'object' || typeof o === 'function') && o !== null
      }
    
      if (!isObject(obj)) {
        throw new Error('非对象')
      }
    
      let isArray = Array.isArray(obj)
      let newObj = isArray ? [...obj] : { ...obj }
      Reflect.ownKeys(newObj).forEach(key => {
        newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
      })
    
      return newObj
    }
复制代码

原型

  • Object 是所有对象的爸爸,所有对象都可以通过 proto 找到它
  • Function 是所有函数的爸爸,所有函数都可以通过 proto 找到它
  • 函数的 prototype 是一个对象
  • 对象的 proto 属性指向原型, proto 将对象和原型连接起来组成了原型链

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值