上篇文章聊了js两种数据类型的区别以及基本数据类型,这篇文章主要聊一下js的引用数据类型、包装对象、js的强转换、隐性数据类型转换。
一、引用数据类型
在ECMAScript中,引用类型是一种数据结构,用于将数据和功能组织在一起。
除了对象,引用数据类型还有很多:
Array 数组
Date 日期
RegExp 正则
Function 函数
除了上面的数据类型,还有三个比较特殊的(基本数据类型的包装对象):
包装对象
Boolean
Number
String
大家可能遇到过一道面试题:
let num1 = 3
let num2 = new Number(3)
console.log(num === num2) // false
这个Number()就是基本数据类型的number的包装对象:
typeof(num1) // number
typeof(num2) // object
这两个不是一个东西,当然也不相等了,那么我们就来搞清楚,这个包装对象到底是干什么的?
let str = 'wanghuahua'
let str1 = str.substr(0, 4)
console.log(str1) // wang
console.log(String.substr) // undefined
刚才调用substr方法截取了一下str这个字符串,str是基本数据类型,打印String的substr方法是undefined,这说明String是没有substr方法的,但是为什么能够截取呢?
这中间有一个计算机的操作,我们把它称作‘加工厂’,那么这个加工厂进行了哪些操作呢?(重点)
1、原材料进厂:把基本数据类型转成包装对象
因为基本数据类型是没有方法的,如果你想操作基本数据类型,那就必须转成包装对象
2、加工:调用substr的相关方法
包装对象是可以加方法和属性,所以这个时候包装对象会调用方法进行操作
3、成品出厂:包装对象转成基本数据类型
ECMAScript规范中提供了toPrimitive原则,所以这个地方会遵从toPrimitive原则进行转换。
上篇文章说到了,这个js弱类型语言把相当多的操作都交给了计算机,这个地方就是一个明显的栗子,这些操作就都交给了计算机,相对来说增加了计算机的负担。
包装对象的由来,其实也没有那么复杂,就是为了处理基本数据类型的数据,加了中间的一层。
刚才在说成品出厂的时候,聊到了toPrimitive,toPrimitive就是我们常说的js强制转换:
二、js强制转换
所谓强制转换,就是人为的进行转换:
(一)toString() 转成字符串
let num2 = new String('hhh')
console.log(num2.toString()) // hhh
每个对象都有一个 toString()方法,当对象被表示为文本值时或者当以期望字符串的方式引用对象时,该方法被自动调用。
有时候会有一些非常恶心的面试题,这儿也列举几个不常见的:
console.log(null.toString()) // 报错
console.log(undefined.toString()) // 报错
console.log(true.toString()) // 'true'
let a = 4
console.log(a.toString()) // 4
console.log('hhh'.toString()) // 'hhh'
console.log(Symbol().toString()) // 报错
除此之外,toString()还接收数字参数,来代表转换的进制:
二进制:.toString(2);
八进制:.toString(8);
十进制:.toString(10);
十六进制:.toString(16);
(二)valueOf() 返回原始值
let num2 = new String('hhh')
console.log(num2.valueOf()) // hhh
JavaScript 调用 valueOf()方法用来把对象转换成原始类型的值(数值、字符串和布尔值)。但是我们很少需要自己调用此函数,valueOf 方法一般都会被 JavaScript 自动调用。
String => 返回字符串
Number => 返回数字
Date => 返回时间戳
Boolean => 返回Boolean的this值
Object => 返回this
上面两种方法都是object的方法,除了这两个方法之外,还有三个运算符:
(三)Number运算符 尽量搞成数字
null 转换为 0
undefined 转换为 NaN
true 转换为 1,false 转换为 0
字符串转换时遵循数字常量规则,转换失败返回NaN
(四)String运算符 全部转成字符串
刚才看toString的时候,null和undefined都会报错,但是String就不会报错,万物皆可转,(也不是那么绝对)但是String运算符没办法接收参数。
String(null) // null
string(undefined) // undefined
(五)Boolean运算符 全部转成true或者false
Boolean也很好理解,就是转成布尔值,只需要记住,一下几种转成false,其余的全部是true就行了。
undefined
null
-0
0或+0
NaN
''(空字符串)
除了这六种,其余全部都是true,什么空对象,空数组全部都是true,甚至这种:
Boolean(new Boolean(false)) // true
(六)ToPrimitive运算符 转换成原始值
除了上面五种方法,js还有一个相对综合了一下的ToPrimitive,它对基础数据类型是没用的,只有引用数据类型可以。
ToPrimitive(obj,type)
ToPrimitive运算符接受一个需要转换的对象值,以及一个可选择的type值,根据这个可选的type值,来进行转换:
type为string:
先调用obj的toString方法,如果为原始值,则return,否则进行第2步
调用obj的valueOf方法,如果为原始值,则return,否则进行第3步
抛出TypeError 异常
type为number:
先调用obj的valueOf方法,如果为原始值,则return,否则进行第2步
调用obj的toString方法,如果为原始值,则return,否则第3步
抛出TypeError 异常
type参数为空
该对象为Date,则type被设置为String
否则,type被设置为Number
三、隐式数据类型转换
因为js是所类型语言,所以相对语法非常宽松,字符串可以和数字运算,数字还能和布尔值运算,在运算过程中就产生了隐式数据类型转换,首先,隐式转换有下面三个原则:
1、遇到+(字符串连接符)就转成string类型:
2、转成number类型:
++/--(自增自减运算符)
+ - * / %(算术运算符)
> < >= <= == != === !=== (关系运算符)
3、遇到 !(逻辑非运算符),就转成boolean类型:
但是,这里面还有很多常见的坑,比如:
1、字符串拼接的+和算数运算符的+都是+,到底是哪个?
常见的面试题:
console.log(1 + 'true') // '1true'
console.log(1 + true) // 2
console.log(1 + null) // 1
console.log(1 + undefined) // NaN
console.log(null + undefined) //NaN
关于字符串拼接+和算数运算符+,有个原则,只要有一边是字符串,那这个+就是字符串拼接符,其余的所有的情况都是算数运算符。
console.log(1 + 'true') // '1true'
'true'是字符串,所以是字符串拼接,只要是字符串拼接,两边都会调用String运算符
console.log(1 + true) // 2 Number(1)+Number(true) 1+1 2
console.log(1 + null) // 1 Number(1)+Number(null) 1+0 1
console.log(1 + undefined) // Number(1)+Number(undefined) 0+undefined NaN
console.log(null + undefined) //Number(null)+Number(undefined) 0+undefined NaN
上面的没有一边是字符串,所以是运算符,如果被当做运算符,那么两边都会调用Number运算符
2、关系运算符,为什么2<'10','2'>'10'?
常见的面试题:
console.log(2>'10') // false
console.log('2'>'10') // true
如果关系运算符一边是字符串,那么会调用Number运算符,把两边转成数字,再比较
如果关系运算符两边都是字符串,会用charCodeAt()方法转成数字,再比较
console.log(2>'10') // Number(2)>Number(10) 2>10 false
console.log('2'>'10') //2.charCodeAt()>10.charCodeAt() 50>49 true
'2'>'10'进行charCodeAt()的时候,会先比第一个字节,如果不一样,直接就返回结果,如果第一个一样会接着比第二个字节。就是2.charCodeAt()会先跟1.charCodeAt()进行比较。
3、引用数据类型在进行隐式转换的时候,会先转成string。
console.log([1,2] == '1,2') //true
在这个过程中[1,2]是引用数据类型,会先调用valueOf()方法
如果返回的还是引用数据类型,再调用toString()方法
4、逻辑非运算符和关系运算符,为啥[] == 0,![] == 0?(重点)
入门级别:
console.log([] == 0) // true
console.log(![] == 0) // true
为啥[]等于0,![]也等于0,这不是毁三观吗?
首先分析下[] == 0:
1、在讲引用数据类型阴性转换的时候,我们说了引用数据类型进行隐形类型转换必须先转成string
[].valueOf().toString() // ''
2、==数据关系运算符,两边必须都转成Number
Number('') // 0
所以true
然后分析下![]为啥也是0:
1、在讲Boolean运算符的时候说了
除了undefined、null、-0、0或+0、NaN、''(空字符串),其余全部都是true
所以[]是true,那么![]就是false
2、两边在转Number的时候:
Number(false) // 0
所以![] == 0也是true
进阶级别
[] == ![] //true
[] == [] // false
[]等于非[]竟然是true,[]等于他自己反倒是false?
[] == ![]
1、[]还是会转成string,就是'''
2、![]还是false
3、‘’和false都进行Number操作都是0
所以相等
[] == [](这是个坑)
这个在上一篇讲栈内存的时候说了,引用数据类型在栈中存的都是地址,所以这两个肯定不一样
高阶级别
{} == !{} // false
{} == {} // false
{}和[]都是引用数据类型,为啥他是false呢?
{} == !{}
1、{}还是会转成string,但是{}转成字符串是""[object Object]"
2、![]还是false
3、Number(""[object Object]")和Number(false)
所以不相等
{} = {}
同理,也是比较栈内存的地址,所以false
关于js的数据类型,主要的就是隐性数据类型转换,会经常出错,这个系列的两篇文章主要是聊了一下js的赋值和赋址的区别、强转换以及隐性转换我们经常出现的问题。
个人的微信公众号:小Jerry有话说,平时会发一些技术文章和读书笔记,欢迎交流。
后面会持续更新一些js基础的文章,有兴趣的可以点个关注。