数据类型
原始(Primitive)类型
-
原始(Primitive)值一般叫做栈数据(一旦开了个房间、不可能在这个房间里对其进行修改)
-
原始类型存储的都是值,是没有函数可以调用的,如 undefined.toString() 会报错。一般我们看到的 ‘1’.toString() 可以调用是因为实际上它已经被强制转换成了 String 类型也就是对象类型,所以可以调用 toString 函数
-
在 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 没有任何关系,导致了 最终两个变量的值是不相同的
-
原始数据类型的值直接存放在栈中,对象为引用数据类型,它们的引用变量存储在栈中,指向于存储在堆中的实际对象。无法直接操纵堆中的数据,即无法直接操纵对象,但可通过栈中对对象的引用来操作对象,就像通过遥控机操作电视机一样,区别在于这个电视机本身并没有控制按钮
为什么引用值要放在堆中,而原始值要放在栈中?
(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 复制代码
- 一旦数字变得足够大,其字符串表示将以指数形式呈现,如下,此时得到的是 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:在条件判断时除了
undefined
,null
,false
,NaN
,''
,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)若两边的值中有 true
或 false
,千万不要使用 ==
(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 或调用构造函数来创建的