一、JS数据类型

image.png

基本数据类型

引用数据类型

Object

存储方式

  • 基础数据类型存储在栈内存,被引用或者拷贝时,会创建一个完全相等的变量。
  • 引用类型存储在堆内存,存储的是地址,多个引用指向同一个地址。

image.png
这段代码打印出来,为:30,24。因为 function 传入的是内存地址,而 return 会创建一个新的内存地址。(没有return,b的值会是undefined)
b: { name: ‘Kath’, age: 30}; a: {name: ‘Julia’, age: 24}

数据类型检测

typeof

image.png
null的数据类型并非object,判断方法:null === null

instanceof

当new一个新对象时,这个新对象的原型链( prototype )继承它new的对象。通过 instanceof 我们可以判断这个对象是不是构造函数生成的对象。
image.png
手动实现 instanceof
image.png

  • 两种判断数据类型的差异

    • typeof 可以判断基础数据类型(null 除外),不能判断除了 function 以外的引用数据类型。
    • instanceof 可以判断复杂数据引用类型(通过构造函数的 new),但不能判断基础数据类型。

      Object.prototype.toString

      推荐使用
      image.png
      除 object({})以外,其它都需要加上 call。
      toString 方法返回具体数据类型
      1. function getType(obj){
      2. let type = typeof obj;
      3. if (type !== "object") { // 先进行typeof判断,如果是基础数据类型,直接返回
      4. return type;
      5. }
      6. // 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
      7. return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1'); // 注意正则中间有个空格
      8. }
      9. /* 代码验证,需要注意大小写,哪些是typeof判断,哪些是toString判断?思考下 */
      10. getType([]) // "Array" typeof []是object,因此toString返回
      11. getType('123') // "string" typeof 直接返回
      12. getType(window) // "Window" toString返回
      13. getType(null) // "Null"首字母大写,typeof null是object,需toString来判断
      14. getType(undefined) // "undefined" typeof 直接返回
      15. getType() // "undefined" typeof 直接返回
      16. getType(function(){}) // "function" typeof能判断,因此首字母小写
      17. getType(/123/g) //"RegExp" toString返回

      数据类型转换

      1. '123' == 123 // false or true?
      2. '' == null // false or true?
      3. '' == 0 // false or true?
      4. [] == 0 // false or true?
      5. [] == '' // false or true?
      6. [] == ![] // false or true?
      7. null == undefined // false or true?
      8. Number(null) // 返回什么?0
      9. Number('') // 返回什么?0
      10. parseInt(''); // 返回什么?NaN
      11. parseInt(null); // NaN
      12. {}+10 // 返回什么?10
      13. let obj = {
      14. [Symbol.toPrimitive]() {
      15. return 200;
      16. },
      17. valueOf() {
      18. return 300;
      19. },
      20. toString() {
      21. return 'Hello';
      22. }
      23. }
      24. console.log(obj + 200); // 这里打印出来是多少?400

      强制类型转换

      Number()
  • 布尔—true和false分别被转换成 1 和 0

  • 数字—返回自身(不仅仅是十进制)
  • null—返回 0
  • undefined—返回 NaN
  • 字符串—如果字符串中只包含数字,则将其转换成十进制
    • 如果字符串中包含有效的浮点格式,将其转换成浮点数值
    • 空字符串,转换成 0
    • 不是以上格式的字符串,均返回 NaN
  • Symbol—抛出错误
  • 对象—如果部署了[Symbol.toPrimitive],那么调用此方法,否则调用对象的 valueOf() 方法

parseInt()
parseFloat()
toString()
String()
Boolean()

  • undefined、null、false、’’、0(包括+0,-0)、NaN —转换成false
  • 其他都是 true。

    隐式类型转换

    逻辑运算符

  • &&

  • ||
  • !

运算符

      • 仅当‘+’两边都是数字时,进行加法运算。如果两边都是字符串,则直接拼接,不进行隐式转换。
      • 如果其中一个是字符串,另一个是 undefined、null或者布尔,则调用 toString() 方法进行字符串拼接;如果是纯对象、数组、正则等,则默认调用对象的转换方法会存在优先级,再进行拼接。
      • 如果其中一个是数字,另一个是undefined、null、布尔或者数字,则会将其转换成数字进行加法运算,对象情况参考上一条。
      • 如果一个是字符串,一个是数字,则按照字符串规则进行拼接。

        1. 1 + 2 // 3 常规情况
        2. '1' + '2' // '12' 常规情况
        3. // 下面看一下特殊情况
        4. '1' + undefined // "1undefined" 规则1,undefined转换字符串
        5. '1' + null // "1null" 规则1,null转换字符串
        6. '1' + true // "1true" 规则1,true转换字符串
        7. '1' + 1n // '11' 比较特殊字符串和BigInt相加,BigInt转换为字符串
        8. 1 + undefined // NaN 规则2,undefined转换数字相加NaN
        9. 1 + null // 1 规则2,null转换为0
        10. 1 + true // 2 规则2,true转换为1,二者相加为2
        11. 1 + 1n // 错误 不能把BigInt和Number类型直接混合相加
        12. '1' + 3 // '13' 规则3,字符串拼接
      • object的转换规则

        • 如果部署了 Symbol.toPrimitive 方法,优先调用再返回;
        • 调用 valueOf(),如果转换为基础类型,则返回;
        • 调用 toString(),如果转换为基础类型,则返回;
          1. var obj = {
          2. value: 1,
          3. valueOf() {
          4. return 2;
          5. },
          6. toString() {
          7. return '3'
          8. },
          9. [Symbol.toPrimitive]() {
          10. return 4
          11. }
          12. }
          13. console.log(obj + 1); // 输出5
          14. // 因为有Symbol.toPrimitive,就优先执行这个;如果Symbol.toPrimitive这段代码删掉,则执行valueOf打印结果为3;如果valueOf也去掉,则调用toString返回'31'(字符串拼接)
          15. // 再看两个特殊的case:
          16. 10 + {}
          17. // "10[object Object]",注意:{}会默认调用valueOf是{},不是基础类型继续转换,调用toString,返回结果"[object Object]",于是和10进行'+'运算,按照字符串拼接规则来,参考'+'的规则
          18. [1,2,undefined,4,5] + 10
          19. // "1,2,,4,510",注意[1,2,undefined,4,5]会默认先调用valueOf结果还是这个数组,不是基础数据类型继续转换,也还是调用toString,返回"1,2,,4,5",然后再和10进行运算,还是按照字符串拼接规则,参考'+'的第3条规则
  • -

  • *
  • /

关系操作符

  • >
  • <
  • <=
  • =

相等运算符

  • ==

    • 如果类型相同,无需进行类型转换
    • 如果有一方为 null 或者 undefined,另一个也必须为 null 或 undefined,否则返回 false。
    • 其中一个为 Symbol 类型,返回 false
    • 两个操作值为 string 或 number 类型,那么会把字符串转换成 number
    • 如果一个操作值是 boolean,那么转换成 number(true转换成 1,false转换成 0)
    • 如果一个操作值是 object,且另一方为 string、number 或者 symbol,就会把 object 转换成原始类型再判断。
      1. null == undefined // true 规则2
      2. null == 0 // false 规则2
      3. '' == null // false 规则2
      4. '' == 0 // true 规则4 字符串转隐式转换成Number之后再对比
      5. '123' == 123 // true 规则4 字符串转隐式转换成Number之后再对比
      6. 0 == false // true e规则 布尔型隐式转换成Number之后再对比
      7. 1 == true // true e规则 布尔型隐式转换成Number之后再对比
      8. var a = {
      9. value: 0,
      10. valueOf: function() {
      11. this.value++;
      12. return this.value
      13. }
      14. };
      15. // 注意这里a又可以等于1、2、3
      16. console.log(a == 1 && a == 2 && a ==3); //true f规则 Object隐式转换
      17. // 注:但是执行过3遍之后,再重新执行a==3或之前的数字就是false,因为value已经加上去了,这里需要注意一下
      if/while 条件

      深浅拷贝

      浅拷贝

      原理:自己创建一个新对象来重新赋值或者引用原来的对象值,如果对象属性是基本数据类型,则是复制的值给新对象,如果属性是引用数据类型,则复制的是内存中的地址(这会导致只真正拷贝了第一层的数据)。
      方法一: object.assign(目标对象,源对象1,源对象2、、、)
  • 不会拷贝对象的继承属性

  • 不会拷贝对象的不可枚举属性(enumerable = false)
  • 可拷贝 Symbol 类型的属性

方法二:扩展运算符方式...

  • 对象和数组皆可用,数组使用和 arr.slice() 相同的效果。

方法三:concat 拷贝数组

  • arr2 = arr1.concat

手动实现浅拷贝:

  1. const shallowClone = (target) => {
  2. if (typeof target === 'object' && target !== null) {
  3. const cloneTarget = Array.isArray(target) ? []: {};
  4. for (let prop in target) {
  5. if (target.hasOwnProperty(prop)) {
  6. cloneTarget[prop] = target[prop];
  7. }
  8. }
  9. return cloneTarget;
  10. } else {
  11. return target;
  12. }
  13. }

总结:浅拷贝只会拷贝一层对象(改变第一层对象不会改变源对象),而赋值操作只是将指针改变,引用的仍然是同一个对象,所以赋值操作的对象第一层改变值源对象也会改变。
方法四:slice 拷贝数组

  • 不会影响和改变原始数组,arr.slice(begin, end)

    1. let arr = [1, 2, {val: 4}];
    2. let newArr = arr.slice();
    3. newArr[2].val = 1000;
    4. console.log(arr); //[ 1, 2, { val: 1000 } ]

    深拷贝

    原理:将一个对象从内存中完整的拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。
    方法一:JSON.stringify

  • 使用方法:先用 JSON.stringify(obj) 将对象转换成 JSON 字符串,再用 JSON.parse() 将之转换成 对象。

  • 注意:
    • 拷贝的对象中的值如果有函数、undefined、symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失。
    • 拷贝 Date 引用类型会变成字符串
    • 无法拷贝不可枚举的属性(enumerable为false)
    • 无法拷贝对象的原型链
    • 拷贝 RegExp 引用类型会变成空对象{}
    • 对象中含有 NaN、Infiniy 以及 -Infinity,JSON 序列化结果会变成 null
    • 无法拷贝对象的循环应用,即对象成环(obj[key] = obj)

方法二:基础版(手写递归实现)

  1. let obj1 = {
  2. a:{
  3. b:1
  4. }
  5. }
  6. function deepClone(obj) {
  7. let cloneObj = {}
  8. for(let key in obj) { //遍历
  9. if(obj[key] && typeof obj[key] ==='object') {
  10. cloneObj[key] = deepClone(obj[key]) //是对象就再次调用该函数递归
  11. } else {
  12. cloneObj[key] = obj[key] //基本类型的话直接复制值
  13. }
  14. }
  15. return cloneObj
  16. }
  17. let obj2 = deepClone(obj1);
  18. obj1.a.b = 2;
  19. console.log(obj2); // {a:{b:1}}

注意:

  • 这个深拷贝函数并不能复制不可枚举的属性以及 Symbol 类型;
  • 这种方法只是针对普通的引用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝;
  • 对象的属性里面成环,即循环引用没有解决。

方法三:改进版(改进后递归实现)

  • 针对能够遍历对象的不可枚举属性以及 Symbol 类型,可以使用 Reflect.ownKeys 方法。(Reflect 详见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
  • 当参数为 Date,RegExp 类型,则直接生成一个新的实例返回。
  • 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性,以及对应的特性,顺便结合 Object 的 create 方法创建一个新对象,并继承传入原对象的原型链。
  • 利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏,作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值。 ```javascript const isComplexDataType = obj => (typeof obj === ‘object’ || typeof obj === ‘function’) && (obj !== null) const deepClone = function (obj, hash = new WeakMap()) { if (obj.constructor === Date) return new Date(obj) // 日期对象直接返回一个新的日期对象 if (obj.constructor === RegExp) return new RegExp(obj) //正则对象直接返回一个新的正则对象 //如果循环引用了就用 weakMap 来解决 if (hash.has(obj)) return hash.get(obj) let allDesc = Object.getOwnPropertyDescriptors(obj) //遍历传入参数所有键的特性 let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc) //继承原型链 hash.set(obj, cloneObj) for (let key of Reflect.ownKeys(obj)) { cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== ‘function’) ? deepClone(obj[key], hash) : obj[key] } return cloneObj } // 下面是验证代码 let obj = { num: 0, str: ‘’, boolean: true, unf: undefined, nul: null, obj: { name: ‘我是一个对象’, id: 1 }, arr: [0, 1, 2], func: function () { console.log(‘我是一个函数’) }, date: new Date(0), reg: new RegExp(‘/我是一个正则/ig’),

}; Object.defineProperty(obj, ‘innumerable’, { enumerable: false, value: ‘不可枚举属性’ } ); obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj)) obj.loop = obj // 设置loop成循环引用的属性 let cloneObj = deepClone(obj) cloneObj.arr.push(4) console.log(‘obj’, obj) console.log(‘cloneObj’, cloneObj) ```