变量和类型--从头学习JavaScript

135 阅读11分钟

变量和类型

JavaScript规定了几种语言类型

名称类别内置对象
String值类型String
Number值类型Number
Boolean值类型Boolean
Null值类型
Undefined值类型
Symbol值类型Symbol
BigInt值类型BigInt
Object引用类型Object
Function引用类型
Array引用类型Array

装箱拆箱是什么

  • 装箱

    • 显示装箱

      • 通过内置对象String、Boolean、Number等可以对基本类型进行显示装箱
    • 隐式装箱

      • 当读取一个基本类型值时,后台会为该基本类型创建一个对应的基本类型包装对象。在这个基本类型上调用方法,实际上是在这个基本类型包装对象上调用方法。这个基本类型包装对象是临时的,它只存在于方法调用那一行代码的瞬间,一旦调用完毕就立即销毁。
       num.toFixed(1);
       ​
       // 在后台的具体执行如下:
       ​
       const c = new Number(12.345);
       ​
       c.toFixed(1);c = null;
       ```
      
  • 拆箱

    • 拆箱与装箱相反,将对象转变成基本类型的值。

    • 拆箱过程内部调用了抽象操作 ToPrimitive 。该操作接受两个参数,第一个参数是要转变的对象,第二个参数 PreferredType 是对象被期待转成的类型。第二个参数不是必须的,默认该参数为 number,即对象被期待转为数字类型。有些操作如 String(obj) 会传入 PreferredType 参数。有些操作如 obj + " " 不会传入 PreferredType。

    • 具体转换过程是这样的。默认情况下,ToPrimitive 先检查对象是否有 valueOf 方法,如果有则再检查 valueOf 方法是否有基本类型的返回值。如果没有 valueOf 方法或 valueOf 方法没有返回值,则调用 toString 方法。如果 toString 方法也没有返回值,产生 TypeError 错误。

      PreferredType 影响 valueOf 与 toString 的调用顺序。如果 PreferrenType 的值为 string。则先调用 toString ,再调用 valueOf。

值类型和引用类型有什么区别

  • 值类型:

    • 存储位置:栈内存中,栈内存中包含了变量的标识符和指向堆内存中该对象的指针
    • 内存占用:栈是自动分配相对固定大小的内存空间,并由系统自动释放
    • 复制方式:直接赋值是深拷贝
    • 无法添加属性和方法
    • 比较:比较值
  • 引用类型:

    • 存储位置:堆内存中,堆内存中包含了对象的内容,是按引用访问
    • 内存占用:动态分配内存,内存大小不一,也不会自动释放
    • 复制方式:直接赋值是传递引用,是浅拷贝
    • 可以添加属性和方法
    • 比较:比较引用地址

Number

有哪些内置方法

方法用途
Number.isNaN()确定传递的值是否是 NaN
Number.isFinite()确定传递的值类型及本身是否是有限数
Number.isInteger()确定传递的值类型是“number”,且是整数
Number.isSafeInteger()确定传递的值是否为安全整数 ( -(253 - 1) 至 253 - 1之间)
Number.toInteger() 计算传递的值并将其转换为整数 (或无穷大)
Number.parseFloat() 和全局对象 parseFloat() 一样
Number.parseInt() 和全局对象 parseInt() 一样

有哪些内置属性

属性含义
Number.EPSILON两个可表示(representable)数之间的最小间隔
Number.MAX_SAFE_INTEGERJavaScript 中最大的安全整数 (2^53 - 1)
Number.MAX_VALUE能表示的最大正数。最小的负数是 -MAX_VALUE
Number.MIN_SAFE_INTEGERJavaScript 中最小的安全整数 (-(2^53 - 1)).
Number.MIN_VALUE 能表示的最小正数即最接近 0 的正数 (实际上不会变成 0)。最大的负数是 -MIN_VALUE
Number.NaN 特殊的“非数字”值
Number.NEGATIVE_INFINITY特殊的负无穷大值,在溢出时返回该值
Number.POSITIVE_INFINITY特殊的正无穷大值,在溢出时返回该值
Number.prototype (en-US)Number 对象上允许的额外属性。
  • 最大数字:2^53(9007199254740992)

  • 最大安全数字:2^53(9007199254740992)-1

  • 为什么 0.1 + 0.2 != 0.3

    • JavaScript使用Number类型表示数字(整数和浮点数),遵循 IEEE 754 标准(二进制浮点数算术标准) 通过64位来表示一个数字,0.1、0.2转换成二进制后会无限循环,遵循IEEE 754 标准,截取了多余的位数。
  • 那为什么 x=0.1 能得到 0.1?

    • 标准中规定尾数f的固定长度是52位,再加上省略的一位,这53位是JS精度范围。它最大可以表示2^53(9007199254740992), 长度是 16,所以可以使用 toPrecision(16) 来做精度运算,超过的精度会自动做凑整处理。 0.10000000000000000555.toPrecision(16) // 返回 0.1000000000000000,去掉末尾的零后正好为 0.1
  • tofixed()对于小数最后一位为5时进位不正确

    • firefox/chrome中,对于小数最后一位为5时进位不正确,修复方式即判断最后一位为5的,改成6,再调用toFixed
  • 避免精度丢失的方法

    • 扩大整数,再缩小

    • ES6在Number对象上新增了一个极小的常量——Number.EPSILON

    目的在于为浮点数计算设置一个误差范围,如果误差能够小于Number.EPSILON,我们就可以认为结果是可靠的。

    • 也可以用成熟的库来解决此问题

      • math.js
      • number-precision
  • JavaScript处理大数字的方法

    • 参考网上常用的一种方案是将Number转为String,然后将String转为Array,并且注意补齐较短的数组,将他们的长度标称一样再一一相加得到一个新数组,再讲和组成的新数组转为数字就可以了。
     function sumString(a, b) {
       a = '0' + a;
       b = '0' + b;  //加'0'首先是为了转为字符串,而且两个数相加后可能需要进位,这样保证了和的长度就是a、b中长的那个字符的长度
       var arrA = a.split(''),  //将字符串转为数组
           arrB = b.split(''),
           res = [],  //相加结果组成的数组
           temp = '',  //相同位数相加的值
           carry = 0,  //同位数相加结果大于等于10时为1,否则为0
           distance = a.length - b.length,  //计算两个数字字符串的长度差
           len = distance > 0 ? a.length : b.length;  //和的长度
       // 在长度小的那个值前加distance个0,保证两个数相加之前长度是想等的
       if(distance > 0) {
         for(let i = 0; i < distance; i++) {
           arrB.unShift('0');
         }
       }else{
         for(let i = 0; i < distance; i++) {
           arrA.unShift('0');
         }
       }
       // 现在得到了两个长度一致的数组,需要做的就是把他们想通位数的值相加,大于等于10的要进一
       // 最终得到一个和组成的数组,将数组转为字符串,去掉前面多余的0就得到了最终的和
       for(let i = len-1; i >= 0; i--) {
         temp = Number(arrA[i]) + Number(arrB[i]) + carry;
         if(temp >= 10) {
           carry = 1;
           res.unshift((temp + '')[1])
         }
         else{
           carry = 0;
           res.unshift(temp)
         }
       }
       res = res.join('').replace(/^0/, '');
       console.log(res);
     }
     ```
    

BigInt

用法

  • 可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数BigInt()

  • 注意点

    • 不能用于Math对象中的方法
    • 不能和任何Number实例混合运算,两者必须转换成同一类型。
    • 在两种类型来回转换时要小心,因为 BigInt 变量在转换成 Number 变量时可能会丢失精度。
    • 使用 typeof 测试时, BigInt 对象返回 "bigint"
    • 使用 Object 包装后, BigInt 被认为是一个普通 "object"
    • BigIntNumber 不是严格相等的,但是宽松相等的。
    • 对任何 BigInt 值使用 JSON.stringify() 都会引发 TypeError,因为默认情况下 BigInt 值不会在 JSON 中序列化。但是,如果需要,可以实现 toJSON 方法

Null | Undefined 的区别

  • undefined:希望表示一个变量最原始的状态,而非人为操作的结果。

    • 出现undefined的场景
      • 使用void对表达式求值
      • 函数默认返回值
      • 函数定义了形参数,但没有传递实参
      • 获取对象不存在的key
      • 获取对象不存在的下标
      • 声明变量,不初始化
      • 未声明的变量执行typeof操作符返回了undefined值
  • null:希望表示一个对象被人为的重置为空对象,而非一个变量最原始的状态。 在内存里的表示就是,栈中的变量没有指向堆中的内存对象。

    • 如果定义的变量在将来用于保存对象,那么最好将该变量初始化为null,而不是其他值。
    • 当一个数据不再需要使用时,我们最好通过将其值设置为null来释放其引用,这个做法叫做解除引用。不过解除一个值的引用并不意味着自动回收改值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器在下次运行时将其回收。解除引用还有助于消除有可能出现的循环引用的情况。这一做法适用于大多数全局变量和全局对象的属性,局部变量会在它们离开执行环境时(函数执行完时)自动被解除引用。
    • 注意⚠️:当我们使用typeof操作符检测null值,我们理所应当地认为应该返"Null"类型呀,但是事实返回的类型却是"object"。
      • 一方面从逻辑角度来看,null值表示一个空对象指针,它代表的其实就是一个空对象,所以使用typeof操作符检测时返回"object"也是可以理解的。
      • 另一方面,其实在JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的(对象的类型标签是 0)。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null的类型标签也成为了 0,typeof null就错误的返回了"object"。在ES6中,当时曾经有提案为历史平反, 将type null的值纠正为null, 但最后提案被拒了,所以还是保持"object"类型。

Symbol

参考资料:www.dazhuanlan.com/tomj/topics…

  • 含义:它是一切非字符串的对象 key 的集合
  • 用法:Symbol(value)

用途

-   每个Symbol都不相等,可以作为对象的属性名,可以保证属性不重名。
-   使用 Symbol定义常量,这样就可以保证这一组常量的值都不相等。
-   定义类的私有属性/方法,Symbol是唯一的,因此在外部无法访问到这个属性。 可以用static替代。

注意点

-   Symbol 可以具有字符串类型的描述,但是即使描述相同,Symbol 也不相等。

-   Symbol 作为对象属性名时不能用`.`运算符,要用`[]`。因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。
-   Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for...infor...of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。

常用API

-   Symbol.for()
类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。如果我们希望使用同一个 Symbol 值,可以用 for。
-   Symbol.keyFor()
返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。

常见手写面试题

手动实现一个简单的Symbol

(function(){
  var root = this
  var generateName = (function(){
    var flag = 0
    return function(name){
      flag++
      return 'troubledot'+ name +'_'+ flag
    }
  })()
  var Generatesymbol = function (description){
    var descString = description == undefined ? undefined : String(description)
    if (this instanceof Generatesymbol) {
      throw new TypeError('Symbol is not a constructor')
    }
    var symbol = Object.create({
      toString : function(){
        return this.__Name__
      },
      valueOf: function(){
        return this
      }
    })
    Object.defineProperties(symbol, {
      __Description__:{
        value: descString,
        writable: false,
        enumerable: false,
        configurable: false
      },
      __Name__:{
        value: generateName(descString),
        writable: false,
        enumerable: false,
        configurable: false
      }
    })
    return symbol
  }
  var symbolMap = {}
    Object.defineProperties(Generatesymbol, {
      'for': {
        value: function(description){
          var descString = description == undefined ? undefined : String(description)
          return symbolMap[descString] ? symbolMap[descString] : symbolMap[descString] = Generatesymbol(description)
        },
        writable: true,
        enumerable: false,
        configurable: true
      },
      'keyFor': {
        value: function(symbol){
          for (var key in symbolMap) {
            if (symbolMap[key] === symbol){
              return key
            }
          }
        },
        writable: true,
        enumerable: false,
        configurable: true
      }
    })
  root.Generatesymbol = Generatesymbol
})()

Object

Object

Array

Function

类型判断

  • typeof

    • 优点:使用简单
    • 缺点:只能检测基本类型(除了null)
    • 用法:typeof 值
  • instanceof

    • 优点:能检测引用类型
    • 缺点:不能检测出基本类型,且不能跨iframe
    • 用法:值 instanceof 类型
  • constructor

    • 优点:基本能检测所有类型(除null、undefined)
    • 缺点:易被修改,也不能跨iframe
    • 用法:值.constructor === 类型
  • Object.prototype.toString

    • 优点:检测出所有的类型
    • 缺点:IE6下,null、undefined为Object
    • 用法:Object.prototype.toStringO() === 类型

类型转换

参考资料:juejin.cn/post/684490…

类型转换

  • 转换为字符串:Boolean 值、数字和字符串的原始值都是伪对象,ECMAScript 定义所有对象都有 toString() 方法,无论它是伪对象,还是真对象。

  • 转换为数字:ECMAScript 提供了两种把非数字的原始值转换成数字的方法,即 parseInt() 和 parseFloat() 。 只有对 String 类型调用这些方法,它们才能正确运行;对其他类型返回的都是 NaN。

  • 转换为布尔:

    参数结果
    undefinedfalse
    nullfalse
    boolean无需转换
    number+0,-0转换为false,其他为true
    string''为false,其他为true
    []true
    {}true
    Symboltrue

隐式转换

  • 自动转换Boolean 。例如 if 语句 或者其他需要 Boolean 的地方

  • 运算符。非 Numeber 类型进行数学运算符 - * / 时,会先将非 Number 转换成 Number 类型。+ 运算符要考虑字符串的情况,在操作数中存在字符串时,优先转换成字符串。

  • 对象。只有在 JavaScript 表达式语句中需要用到数字或字符串时,对象才被隐式转换。

    • 调用 valueOf()。如果结果是原始值(不是一个对象),则将其转换为一个数字。
    • 否则,调用 toString() 方法。如果结果是原始值,则将其转换为一个数字。
    • 否则,抛出一个类型错误。

Boolean

String

常用API

常见手写面试题