【学习笔记】JavaScript 基础 – 数据类型、运算符、类型转换

数据类型

原始(Primitive)类型

  • 原始(Primitive)值一般叫做栈数据(一旦开了个房间、不可能在这个房间里对其进行修改)

  • 原始类型存储的都是值,是没有函数可以调用的,如 undefined.toString() 会报错。一般我们看到的 ‘1’.toString() 可以调用是因为实际上它已经被强制转换成了 String 类型也就是对象类型,所以可以调用 toString 函数
    image.png

  • 在 JS 中,存在着以下几种原始值,分别是:

    number(typeof undefined === “undefined”)
    string(typeof ” === ‘string’)
    boolean(typeof true === ‘boolean’)
    null(typeof null === ‘object’)
    undefined(typeof null === ‘undefined’)
    symbol(typeof Symbol() === ‘symbol’)
    bigInt(typeof 10n === ‘bigint’)(没有正式发布但即将被加入标准的原始类型)

  • undefined 和 null 的区别

    • null 有值,不过这个值是空值,null 表示为空,代表此处不应该有值的存在,一个对象可以是 null,代表是个空对象,null 一般用作占位
    • undefined 是未定义,表示『不存在』,完全没有值的意思,JavaScript 是一⻔动态类型语言,成员除了表示存在的空值外,还有可能根本就不存在(因为存不存在只在运行期才知道),这就是 undefined 的意义所在
  • 除了会在必要的情况下类型转换以外,原始类型还有一些坑

    • typeof null 会输出 object,这是 JS 存在的一个悠久 Bug,在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object
    • JS 的 number 类型是浮点类型,在使用中会可能会遇到某些 Bug,如 0.1 + 0.2 !== 0.3,此处详见分析篇:为什么 0.1 + 0.2 !== 0.3

引用类型

  • 一般叫做堆数据,包括:

    对象(Object)(typeof {} === ‘object’)
    数组(Array)(typeof [] === ‘object’)
    函数(Function)(typeof function(){} === ‘function’)

  • 引用类型原始类型的区别

    • 因为原始值是存放在栈里的,而引用值是存放在堆里的,原始值不可以被改变,引用值可以被改变
    • 原始值的赋值是把值的内容赋值一份给另一个变量,栈内存一旦被赋值了就不可以改变,即使给 num 重新赋值为 234,也是在栈里面重新开辟了一块空间赋值 234,然后把 num 指向了这个空间,前面那个存放 123 的空间还存在
    • 但引用值却不是这样:引用值的变量名存在栈里,但是值却是存在堆里,栈里的变量名只是个指针并指向了一个堆空间,这个堆空间存的是一开始赋的值,当 arr1 = arr 时,其实是把 arr1 指向了和 arr 指向的同一个堆空间,这样当改变 arr 的内容时,其实就是改变了这个堆空间的内容,自然同样指向这个堆空间的 arr1 的值也随着改变
      // num的改变对num1完全没有影响
      var num = 123, num1 = num;
      num = 234;
      console.log(num) // 234
      console.log(num1) // 123
      
      // 只是改变了arr的值,但是arr1也跟着改变了
      var arr = [1,2,3], arr1 = arr;
      arr.push(4)
      console.log(arr) // [1,2,3,4]
      console.log(arr1) // [1,2,3,4]
      复制代码

    再来看个函数参数是对象的情况

    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) // {name: "yck", age: 26}
    console.log(p2) // {name: "yyy", age: 30}
    复制代码

    (1)上面代码中,首先函数传参是传递对象指针的副本
    (2)到函数内部修改参数的属性这步,当前 p1 的值也被修改了
    (3)但是当重新为 person 分配了一个对象时就出现了分歧,请看下图,所以最 后 person 拥有了一个新的地址(指针),即和 p1 没有任何关系,导致了 最终两个变量的值是不相同的
    image.png

  • 原始数据类型的值直接存放在栈中,对象为引用数据类型,它们的引用变量存储在栈中,指向于存储在堆中的实际对象。无法直接操纵堆中的数据,即无法直接操纵对象,但可通过栈中对对象的引用来操作对象,就像通过遥控机操作电视机一样,区别在于这个电视机本身并没有控制按钮

为什么引用值要放在堆中,而原始值要放在栈中?
(1)堆比栈大,栈比堆的运算速度快;对象是一个复杂的结构且可以自由扩展,如数组可以无限扩充、对象可以自由添加属性
(2)相对而言原始类型比较稳定且它只占据很小的内存,不将原始类型放在堆是因为是为了不影响栈的效率,且通过引用到堆中查找实际对象是要花费时间的,而这个综合成本远大于直接从栈中取得实际值的成本,所以原始类型值直接存放在栈中

运算符

算术运算符(算术运算符的优先级是从左到右的)

  • +:数学上的相加功能、拼接字符串(字符串和任何数据相加都会变成字符串)
  • /*///%:f分别对应数学上的相减、相乘、相除、取余功能
  • =:赋值运算符,优先级最低
  • ():和数学上一样,加括号的部分优先级最高
  • ++:自加 1 运算,当写在变量前时是先自加 1 再执行运算,写在变量后时是先运算再自加 1
  • --:用法和 ++ 一样,不过是减法操作
  • +=:让变量自加多少
  • 相同的还有 -=、/=、*-、%= 等等

比较运算符

  • 比较运算符有 > 、< 、>= 、<= 、!= 、== 不严格等于、===严格等于
  • 不严格等于和严格等于的区别:当比较两个数据时,是否先转化成同一个类型的数据之后再进行比较。不严格等于就是说这两个数据进行了转化后值相同则整两个数据相等;而严格等于则是两个数据不进行数据转化也相等

注:NaN 不等于任何数据包括它本身,null 和 undefined 就等于它本身

逻辑运算符

  • 逻辑运算符主要是 && 和 ||(与和或)
  • && 的作用:只有是 true 时才会继续往后执行,一旦第一个表达式就错了后面的第二个表达式根本不执行。若表达式的返回结果都是 true 则这里 && 的返回结果是最后一个正确的表达式的结果
  • || 的作用:只要有一个表达式是 true 则结束,后面的就不走了且返回的结果是这个正确的表达式的结果,若都是 false 则返回结果就是 false
  • 一般来说,&& 有当做短路语句的作用,因为运算符的运算特点,只有第一个条件成立时才会运行第二个表达式,所以可以把简单的 if 语句用 && 来表现出来
  • 一般来说,|| 有当做赋初值的作用,有时希望函数参数有一个初始值,在不使用ECMA6 的语法的情况下,最好的做法就是利用 || 语句

    注意:这里有一个缺点,当传的参数是一个布尔值且传的是 false,则 || 语句的特点就会忽略掉所传的这个参数值而去赋成默认的初始值,所以为了解决这个弊端,就需要利用 ES6 的一些知识

默认为 false 的值:undefined、null、" "、0、-0、false、NaN

类型转换

显示类型转换

  • typeof 能返回的类型一共有 6 种:numner、string、boolean、undefined、object、function
    • 数组和 null 的都返回 ‘object’
    • NaN 属于 number 类型:虽然是非数,但是非数也是数字的一种
  • Number(mix):该方法可以把其他类型的数据转换成数字类型的数据
  • parseInt(string, radix):该方法是将字符串转换成整型数字类型的
    • 第二个参数 radix 是可选择的参数
    • 当 string 里既包括数字又包括其他字符时会从左到右只会转换数字部分,遇到其他非数字的字符就停止,即使后面还有数字也不会继续转换
    • 当 radix 不为空时,该函数可用来作为进制转换,radix 作用则是把第一个参数的数字当成几进制的数字来转换成十进制(radix 参数的范围是 2 – 36)
  • parseFloat(string, radix):这个方法和 parseInt 类似,将字符串转换成浮点类型的数字,同样是碰到第一个非数字型字符停止,但由于浮点型数据有小数点,所以它会识别第一个小数点以及后面的数字,但第二个小数点则无法识别
    • 一旦数字变得足够大,其字符串表示将以指数形式呈现,如下,此时得到的是 1
      // String(100000000000000000000000) -> "1e+23"
      parseInt(100000000000000000000000);  // 1
      复制代码
  • toString(radix):它是对象上的方法,任何数据类型都可使用,转换成字符串类型
    • 同样 radix 基底是可选参数,当为空时仅仅代表将数据转化成字符串
    • 当写了 radix 基底时则代表要将这个数字转化成几进制的数字型字符串
    • undefiend 和 null 没有 toString 方法
  • String(mix):把任何类型转换成字符串类型
  • Boolean(mix):把任何类型转换成布尔类型

隐式类型转换

  • isNaN():这个方法可以检测数据是不是非数类型,这中间隐含了一个隐式转换,先将传的参数调用 Number 方法,再看结果是不是 NaN,该方法可以检测 NaN 本身
isNaN(NaN) // true
isNaN('abc') // true
isNaN(123) // false
复制代码
  • 算术运算符
    • ++ n:先将数据调一遍 Number 后,再自加 n
    • n ++:虽然是执行完后才自加 n,但执行前就调用 Number 进行类型转换
    • 同样一目运算符也可以进行类型转换:+、-、*、/ 在执行前都会先转换成数字类型再进行运算
  • 逻辑运算符也会隐式调用类型转换
    • && 和 || 都是先把表达式调用 Boolean 换成布尔值再进行判断,不过返回的结果还是本身表达式的结果
    • !取反操作符返回的结果也是调用 Boolean 方法后的结果
    • 转 Boolean:在条件判断时除了 undefinednullfalseNaN''0-0,其他所有值都转为 true,包括所有对象

转换规则

原始值 转换为数值 转换为字符串 转换为布尔值
number / 0 -> “0”,5 -> “5” 除了 0、-0、NaN 都为 true
string ” ” -> 0,”1″ -> 1,”a” -> NaN / 除了空字符串都为 true
undefined NaN “undefined” false
null 0 “null” false
[] 0 ” “ true
[10,20] NaN “10,20” true
{} NaN “[object, Object]” true
{a: 1} NaN “[object, Object]” true
function() {} NaN “function(){}” true
true 1 “true” true
false 0 “false” false
Symbol NaN “function Symbol() { [native code] }” true
Symbol() 抛错 “Symbol()” true

引用类型转换为原始类型

  • 引用类型在转换类型时会调用内置的 [[ToPrimitive]] 函数
  • ToPrimitive(obj, preferredType):JS 引擎内部转换为原始值
  • ToPrimitive(obj, preferredType) 函数接受两个参数:obj 为被转换的对象,preferredType 为希望转换成的类型(默认为空,接受的值为 Number 或 String)
  • 在执行 ToPrimitive(obj, preferredType) 时若第二个参数为空且 obj 为 Date 的实例时,此时 preferredType 会被设置为 String,其他情况下 preferredType 都会被设置为 Number,若 preferredType 为 Number,ToPrimitive 执行过程如下:
    • 若 obj 为原始值,直接返回
    • 否则调用 obj.valueOf(),若执行结果是原始值则返回
    • 否则调用 obj.toString(),若执行结果是原始值则返回
    • 否则抛异常
  • 若 preferredType 为 String,将上面的第 2 步和第 3 步调换,即:
    • 若 obj 为原始值,直接返回
    • 否则调用 obj.toString(),若执行结果是原始值则返回
    • 否则调用 obj.valueOf(),若执行结果是原始值则返回
    • 否则抛异常

== 运算规则

一些常规和非常规的转换情况

// 常规
"0" == null  // false
"0" == undefined // false
"0" == NaN // false
"0" == 0 // true
"0" == "" // false
false == null // false
false == undefined // false
false == NaN // false
false == {} // false
"" == null // false
"" == undefined // false
"" == NaN // false
"" == {} // false
0 == null // false
0 == undefined // false
0 == NaN // false
0 == {} // false

// 非常规
"0" == false // true
false == 0 // true
false == "" // true
false == [] // true
"" == 0 // true
"" == [] // true
0 == [] // true
复制代码

对 == 两边的值认真推敲,以下两个原则可以有效地避免出错,这时最好用 === 来避免不经意的强制类型转换
(1)若两边的值中有 truefalse,千万不要使用 ==
(2)若两边的值中有 []"" 或者 0,尽量不要使用 ==

总结

  • undefined == null,结果是 true 且它俩与所有其他值比较的结果都是 false
  • String == Boolean,需要两个操作数同时转为 Number
  • String/Boolean == Number,需要 String/Boolean 转为 Number
  • Object == Primitive,需要 Object 转为 Primitive(具体通过 valueOf 和 toString 方法)

扩展

为什么会有 BigInt 的提案?

  • JavaScript 中 Number.MAX_SAFE_INTEGER 表示最大安全数字,计算结果是 9007199254740991,即在这个数范围内不会出现精度丢失(小数除外)
  • 但是一旦超过这个范围,JS 就会出现计算不准确的情况,这在大数计算时不得不依靠一些第三方库进行解决,因此官方提出了 BigInt 来解决此问题
  • bigInt 类型可以用任意精度表示整数,使用 bigInt 可以安全地存储和操作大整数,甚至可以超过数字的安全整数限制,bigInt 是通过在整数末尾附加 n 或调用构造函数来创建的

{} + [] 的结果是什么?

详见:juejin.cn/post/696307…

[‘1′,’2′,’3’].map(parseInt)的返回值是什么?

详见:juejin.cn/post/696291…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享