• javaScript ES5常考面试题总结


    js的六种原始值

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

    坑1:
    首先原始类型存储的都是值,是没有函数可以调用的,比如 undefined.toString() 会报错
    但是 '1'.toString()是可以调用的,因为已经转换成了对应的对象类型了。

    坑2:
    number的类型 0.1 + 0.2 !== 0.3

    坑3:
    对于 null 来说,很多人会认为他是个对象类型,其实这是错误的。虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

    对象(Object)类型

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

    const a = []; // 对于常量 a 来说,假设内存地址(指针)为 #001,那么在地址 #001 的位置存放了值 [],常量 a 存放了地址(指针) #001
    
    const b = 1;
    
    b.push(a); // 当我们将变量赋值给另外一个变量时,复制的是原本变量的地址(指针),
    也就是说当前变量 b 存放的地址(指针)也是 #001,
    当我们进行数据修改的时候,就会修改存放在地址(指针) #001 上的值,也就导致了两个变量的值都发生了改变。
    

    typeof vs instanceof

    ** 涉及面试题:typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么?

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

    typeof 1 // 'number'
    typeof '1' // 'string'
    typeof undefined // 'undefined'
    typeof true // 'boolean'
    typeof Symbol() // 'symbol'
    

    typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型

    typeof [] // 'object'
    typeof {} // 'object'
    typeof console.log // 'function
    

    如果我们想判断一个对象的正确类型,这时候可以考虑使用 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
    

    todo: 类型判断再去深入学习。

    类型转换

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

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

    【对象转原始类型】
    对象在转换类型的时候,会调用内置的 [[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" 数组 -> 字符串 的转换
    

    经典考题:

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

    比较运算符

    如果是对象,就通过 toPrimitive 转换对象
    如果是字符串,就通过 unicode 字符索引来比较

    let a = {
      valueOf() {
        return 0
      },
      toString() {
        return '1'
      }
    }
    a > -1 // true
    // 在以上代码中,因为 a 是对象,所以会通过 valueOf 转换为原始类型再比较值。
    

    this

    1、谁调用this,this就是谁
    2、箭头函数没有this,另外对箭头函数使用 bind 这类函数是无效的

    function foo() {
      console.log(this.a)
    }
    var a = 1
    foo() // 这里调用相当于 window.foo() 所以 this则是window 那么window.a = 1
    
    const obj = {
      a: 2,
      foo: foo
    }
    obj.foo() // this = obj 所以 obj.a = 2 所以这里输出2
    
    const c = new foo() // 对于 new 的方式来说,this 被永远绑定在了 c 上面,不会被任何方式改变 this
    

    bind

    原则:对于这些函数来说,this 取决于第一个参数,如果第一个参数为空,那么就是 window。

    let a = {}
    let fn = function () { console.log(this) }
    fn.bind().bind(a)() // => 根据规则 打印出来的就是window
    

    == vs ===

    == 会进行类型转换
    === 不会进行类型转换

    平时使用还是强烈建议使用===进行严格的判断

    闭包

    闭包的定义其实很简单:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。

    function A() {
      let a = 1
      window.B = function () {
          console.log(a)
      }
    }
    A()
    B() // 1
    

    在 JS 中,闭包存在的意义就是让我们可以间接访问函数内部的变量。

    通过闭包解决for循环的访问

    for (var i = 1; i <= 5; i++) {
      ;(function(j) {
        setTimeout(function timer() {
          console.log(j)// 1 ,2 ,3 ,4 ,5
        }, j * 1000)
      })(i)
    }
    

    另外一个方案

    for (let i = 1; i <= 5; i++) {
      setTimeout(function timer() {
        console.log(i) // 输出 1,2,3,4,5
      }, i * 1000)
    }
    

    上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

    深浅拷贝

    之前,我们了解了对象类型在赋值的过程中其实是复制了地址,从而会导致改变了一方其他也都被改变的情况。通常在开发中我们不希望出现这样的问题,我们可以使用浅拷贝来解决这个情况。

    let a = {
      age: 1
    }
    let b = a
    a.age = 2
    console.log(b.age) // 2
    

    【浅拷贝】
    首先可以通过 Object.assign 来解决这个问题,很多人认为这个函数是用来深拷贝的。其实并不是,Object.assign 只会拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,所以并不是深拷贝。

    let a = {
      age: 1
    }
    let b = Object.assign({}, a)
    a.age = 2
    console.log(b.age) // 1
    

    另外我们还可以通过展开运算符 ... 来实现浅拷贝

    let a = {
      age: 1
    }
    let b = { ...a }
    a.age = 2
    console.log(b.age) // 1
    

    通常浅拷贝就能解决大部分问题了,但是当我们遇到如下情况就可能需要使用到深拷贝了

    let a = {
      age: 1,
      jobs: {
        first: 'FE'
      }
    }
    let b = { ...a }
    a.jobs.first = 'native'
    console.log(b.jobs.first) // native
    

    浅拷贝只解决了第一层的问题,如果接下去的值中还有对象的话,那么就又回到最开始的话题了,两者享有相同的地址。要解决这个问题,我们就得使用深拷贝了。

    【深拷贝】
    这个问题通常可以通过 JSON.parse(JSON.stringify(object)) 来解决

    但是该方法也是有局限性的:

    会忽略 undefined
    会忽略 symbol
    不能序列化函数
    不能解决循环引用的对象

    let a = {
      age: undefined,
      sex: Symbol('male'),
      jobs: function() {},
      name: 'yck'
    }
    let b = JSON.parse(JSON.stringify(a))
    console.log(b) // {name: "yck"}
    

    看输出结果忽略了undefined , symbol 和 函数

    实现一个真正的深拷贝(简易版)

    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() 返回一个由目标对象自身的属性键组成的数组
      Reflect.ownKeys(newObj).forEach(key => {
        newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] // 这里进行递归实现多层对象的复制
      })
    
      return newObj
    }
    

    原型

    当我们创建一个对象时 let obj = { age: 25 },我们可以发现能使用很多种函数,但是我们明明没有定义过它们,对于这种情况你是否有过疑惑?

    其实每个 JS 对象都有 proto 属性,这个属性指向了原型。这个属性在现在来说已经不推荐直接去使用它了,这只是浏览器在早期为了让我们访问到内部属性 [[prototype]] 来实现的一个东西。

    其实原型链就是多个对象通过 proto 的方式连接了起来。为什么 obj 可以访问到 valueOf 函数,就是因为 obj 通过原型链找到了 valueOf 函数。

  • 相关阅读:
    Spring MVC中@ControllerAdvice注解实现全局异常拦截
    IDEA一个窗口打开多个项目
    IntelliJ IDEA中Spring Boot项目使用spring-boot-devtools无法实现热部署/热更新的问题解决
    Java中String/StringBuffer/StringBuilder区别(转)
    IDEA查看源码时提示:Library source does not match the bytecode for class的问题分析
    忙的一天的复盘
    Apollo专题
    使用Spring JPA中Page、Pageable接口和Sort类完成分页排序【专题】
    公众号开发之wx-tools+springboot应用实战-音乐爬虫推送[JAVA]
    [LeetCode] 152. Maximum Product Subarray 求最大子数组乘积
  • 原文地址:https://www.cnblogs.com/shiyou00/p/10493709.html
Copyright © 2020-2023  润新知