JavaScript中的强制类型转换规则

JavaScript中的类型从来都不是在定义的时候就确定的,而是在运行期间确定的

JavaScript中的强制类型转换总是返回基本类型值

JavaScript中常见的转换就是向数字字符串布尔值进行强制类型转换

向字符串转换

三种方法:toStringString()+ ''

总结来讲就是nullundefinedtruefalse都会转换成对应的原始字符串,数字的话,所有数字类型都会以原始值的形式转换为字符串,包括NaNInfinity-Infinity,但是对于-0的话,转换为字符串并不会保留它的符号,转换后值为'0'
至于Symbol的话,如果你尝试将它转换成为一个字符串类型的话,那你将会得到一个类型报错:

var symbol = Symbol()

symbol += ''

// Uncaught TypeError: Cannot convert a Symbol value to a string

这里看到,Symbol类型值是不允许类型转换的

对于普通的对象{}来讲,除非有自行定义的toString函数,否则toString返回的是内部属性[[Class]]的值,就像这样子的 ‘[object object]’ ,对象类型在向字符串转换时,会自动调用其内的toString方法,如果找不到内部自己定义的toString方法,便会顺着原型链向上找,也就是说,在没有自己指定toString的时候,将一个对象转换成字符串的操作,调用的是Object.prototype.toString,这里看其实就特别熟悉了,这就是我们常常用来检测一个变量的真实类型的方法:

function checkType(value) {
	return Object.prototype.toString.apply(value).split(' ')[1].split('').join('').replace(']', '').toLowerCase()
}

checkType(1) // 'number'
checkType('') // 'string'
checkType({}) // 'object'
checkType([]) // 'array'

下面是默认对象toString和自定义toString

对象默认toString

const obj = { name: 'young' }

console.log(obj.toString()) // '[object object]'

console.log(obj + 'Hello') // '[object object]Hello'

自定义toString

const obj = {
	name: 'young',
	toString: function () {
		return JSON.stringify(this)
	}
}

/*
 *	对象在转为字符串时自动调用其内的toString方法
 */

console.log(obj + '') // '{"name":"young"}'

数组这个特殊的对象类型它也定义了自己的toString

[1, 2, {}].toString() // '1,2,[object object]'

可以看到,数组的toString方法对数据内每一个单元调用其toString方法且以逗号进行分隔后返回

使用JSON字符串化

JSON.stringify() 常常用来进行对象的序列化

JSON.stringify() 对于基本类型值的效果和toString是相同的,但是某些结果可能会和直接的toString不相同,例如下面利用它去处理一个字符串类型的数据:

JSON.stringify('Hello World') // '"Hello World"'

结果是一个带着双引号的字符串,可见,JSON.stringify()返回的结果总是字符串,即使原本就是字符串,它也会将这个字符串使用引号包起来再返回
又例如:

JSON.stringify(NaN)	// 'null'
JSON.stringify(Infinity) // 'null'

遇到数字类型且为NaNInfinityJSON.stringify()会将它们处理成'null'

安全的JSON值与不安全的JSON值

安全
  • Number
  • String
  • Null
  • Boolean
不安全
  • undefined
  • function
  • symbol
  • 包含循环引用的对象

这里分别输出一下用 JSON.strinify() 处理这四个不安全的值会得出什么结果:

  • undefined
JSON.stringify(undefined) // undefined

处理undefined后得到的结果并不是一个字符串,还是undefined

  • function
JSON.stringify(function () {}) // undefined
JSON.stringify(() => {}) // undefined

处理函数得到的结果也是undefined,并不是预期的字符串

  • symbol
JSON.stringify(Symbol()) // undefined

处理Symbol值得到的结果也是undefined,也不是预期的字符串

  • 包含循环引用的对象
    先提一嘴,啥是 包含循环引用的对象,就是“你中有我,我中有你”,无限循环,这种对象是展开不完的,具体形式长这个样子:

一篇文章

const article = {
	title: '物种起源'
}

一个作者

const author = {
	name: 'Darwin'
}

现在指定文章所属为author,作者的作品为article

article.belong = author

author.work = article

接下来看看它变成什么样子了,拿author举例:
示例图片

不难发现这个对象永远展开不完,这就是循环引用的对象,接下来使用JSON.stringify()来处理它试试:

JSON.stringify(author)

报错图片
这里就报错了,所以它无法处理包含循环引用的对象

这几种危险类型值还有特殊的情况,它们不仅仅会被转为undefined,在数组和对象中,它们有不同的表现:

JSON.stringify([undefined, funtion () {}, Symbol()]) // '[null,null,null]'

JSON.stringif({a: undefined, b: function () {}, c: Symbol()}) // '{}'
单独处理结果数组中对象中
undefinedundefinednull丢失
functionundefinednull丢失
Symbolundefinednull丢失
循环引用对象ErrorErrorError

所以若对象包含这4种情况的话,最好不要用JSON.stringify()处理,否则无法出现预期的结果,影响程序的执行


JSON.stringify的中间层toJSON

如果对象中定义了toJSON方法,用JSON.stringify()处理这个对象时就会首先调用toJSON方法,用它的返回值来进行处理,这里的用处就是:

  • 此方法定义了哪些值该被序列化
  • 如果要对含有非法JSON值的对象来做字符串序列化,或是对象中某些值无法进行序列化,那么需要定义一个 toJSON 方法来返回安全的JSON值

就接着刚才的循环引用对象来继续,上面说到,我们因为循环引用而让JSON.stringify()方法报错了:

const article = {
	title: '物种起源'
}

const author = {
	name: 'Darwin'
}

article.belong = author
author.work = article

但是我实在是想处理它,怎么办呢,这里通过toJSON方法来中转它:

author.toJSON = function () {
	// 这里返回安全的JSON值,可以手动去掉循环引用对象
	return {
		name: this.name
	}
}

JSON.stringify(author) // '{"name":"Drawin"}'

结果并没有报错,我通过toJSON方法返回了一个对象,其内只包含了author的名字Darwin,类型为字符串,是一个安全的JSON值,所以就能够得到正确的处理结果

JSON.stringify的其它参数

MDN能够看到,JSON.stringify()的参数不止一个

JSON.stringify(value[, replacer [, space]])

后两个参数replacer、space为可选参数,意义如下:

  • replacer
    • 类型:Array | Function
    • 含义:指定对象序列化过程中哪些属性应该被处理,哪些应该被排除掉
    • 用法:
    // 需要排除掉id属性,分别用数组格式的参数和函数格式的参数来做
    const person = {
    	name: 'young',
    	age: 21,
    	city: 'GuangZhou',
    	id: '123456'
    }
    
    JSON.stringify(person, ['name', 'age', 'city'])
    // '{"name":"young","age":21,"city":"GuangZhou"}'
    
    JSON.stringify(person, function (key, value) {
    	if (key !== 'id') return value
    })
    // '{"name":"young","age":21,"city":"GuangZhou"}'
    
    两者得到了相同的处理结果,用数组格式的replacer时,传入的是一个字符串数组,每个元素为需要进行处理的属性名,如果不需要处理某个属性名,不写它就好 了;用函数格式的replacer时,这个函数接收两个参数:keyvalue,第一个参数key表示对象的属性名,第二个参数value表示对象的属性值,被序列化的值的每个属性都会经过此函数的转换和处理。
  • space
    • 类型:Number | String
    • 含义:指定输出的缩进格式
    • 用法:在space为Number类型(正整数)时,它表示了每级缩进的字符数;在space为字符串类型时,表示每一级缩进使用的字符,最长支持10位
    const tree = {
    	branch1: 12,
    	branch2: 10,
    	branch3: '4'
    }
    
    // replacer为null时,与不传的行为相同,序列化所有字段
    JSON.stringify(tree, null, 2)
    // '{\n  "branch1": 12,\n  "branch2": 10,\n  "branch3": "4"\n}'
    
    JSON.stringify(tree, null, '**')
    // '{\n**"branch1": 12,\n**"branch2": 10,\n**"branch3": "4"\n}'
    

向数字转换

三种方式 - * / %parseInt \ parseFloatNumber

值类型转换后
undefinedNaN
null0
true1
false0
''空字符串0
SymbolTypeError

Number

使用函数Number()来转换数字:如果Number中传递的值是一个字符串的的话,且这个字符串包含非数字字符,那么Number便会返回NaN

parseInt

执行机制

与Number不同,parseInt的专精就是将字符串数据转换为数字。parseInt将第一个参数转换为字符串,对这个字符串进行解析,直到遇到第一个不能转换为数字的字符,返回一个整数或者NaN,如果第一个字符就无法转换为数字,那么程序将返回NaN
但是+-这两个字符虽然无法转换为数字,但是parseInt能够识别它们并作为返回结果的依据,仅限它们在第一个字符时。

parseInt('-123') // -123
parseInt('+123') // 123

还有一个字符无法转换为数字,' '空格,和+ -一样,它也必须在字符串的头部才可以被parseInt识别,parseInt会忽略掉开头的所有空格。

parseInt('         123') // '123'
参数

parseInt(string, radix)

  • 第一个参数string为要处理的数据
  • 第二个参数代表了按照几进制处理数据,也就是处理结果整数的基数,类型为整数,值为2到36

经常可以看到,parseInt处理的结果都是十进制的数,但是它并不是默认去以10为基数来处理数据的。如果处理字符串由'0x'0X开头(不传 radix 的情况下),parseInt会猜想你的radix是16,接下来就会以16为基数来处理数据;如果字符串以0开头,我一开始以为它是会以8为基数来处理数据的,但是事实告诉我并不是这样:

parseInt('012')	// 12

并没有像十六进制数('0X11')那样猜想我们是想以8为基数来处理当前字符串,转而给了我们一个十进制的处理结果,所以这里可以先这样理解:如果没有传radix时,对于0X0x开头的字符串,parseInt会将其作为16进制数来处理,其它情况,parseInt都会采用radix = 10的情况来处理字符串,最好还是在parseInt的时候由我们手动指定radix的值。

对于那些非字符串的数据,parseInt首先会将它们先字符串化,再去进行操作,

parseInt(false),先将false转换为字符串为'false',接下来parseInt开始解析,遇到第一个字符,十进制数中并没有'f'这个字符,故无法转换为十进制数,返回NaN

而当我们指定了16为处理基数时(radix = 16)——parseInt(false, 16),那么接下来的操作就是:先将false转换为字符串'false',然后parseInt以16为基数开始计算,首先遇到f字符,🌍人都知道十六进制中是有f这个数的,所以,是一个正确可以转换成整数的值,存下来;再往下来一个,a,也是十六进制中的整数,符合;再往下看,l,这个就不是十六进制中的整数了,所以到这里没法找到更多的可转换的字符,就停下,最后得到的结果就是十六进制的fa,我们再算一下就好了,也就是15 * 16^1 + 10 * 16^0,结果为 250

运算结果

其它类型转换数字

顶上那个表格只写了常见的Boolean类型,空字符串,undefined,null还有Symbol的一些转换结果,现在在这里来总结一下平时不常见且不常用的转换结果:

Object

对象在转换为数字的时候例如这种操作:

const person = { age: 20 }

person + 1 // NaN 

对象在转换为数字的时候,首先会检查对象的valueOf方法,若valueOf返回了基本类型值,则使用这个值作为后续转换为数字的基本类型值,如果它不是基本类型(object...),那就接着去检查该对象的toString方法,拿着toString的返回结果去做转换,如果,我说的是如果,toString也没有返回基本类型,那么就产生TypeError

这里来证明一下valueOftoString的先后问题:

const person = {
	name: 'young',
	valueOf: function () {
		return 100
	},
	toString: function () {
		return 200
	}
}

person - 1 // 99

这里可以看到,用的值是valueOf返回的100,的确先于toString返回的200,接下来去掉valueOf,只写一个toString:

const person = {
	name: 'young',
	toString: function () {
		return true
	}
}

person - 1 // 0

这里JavaScript会首先找person的valueOf方法,因为我们并没有重写valueOf方法,所以调用的是Object.prototype.valueOf返回结果得到了:

{
	name: 'young',
	toString: function () {
		return true
	}
}

也就是这里的原始对象,但是它是个引用类型值,不属于基本类型,那么就接着往下找,toString返回了一个true,是基本类型值,所以将拿着这个ture进行数字转换,🌍人都知道,true对应的Number类型值为1,所以1 - 1的结果自然也就是0了

接下来看一眼当valueOftoString都返回的是引用类型值时是如何报错的:

const person = {
	name: 'young',
	// valueOf:这个就不写了,对象调用对象原型链上的valueOf肯定返回的是它本身
	toString: function () {
		return [] // 故意让toString返回一个引用类型值
	}
}

person - 1

报错如下:
报错
这里

Array

Array的行为跟上面的Object就很相像了,但是唯一不同的就是,Array的toString重写了,而Object的toString默认用的是Object.prototype.toString,下面看一下区别:

const arr = [1, 2, 3]
const obj = {}

parseInt(arr) // 1
parseInt(obj) // NaN
  • 这是因为Array的toString的处理逻辑是:将每个元素转换成字符型,并用逗号隔开,返回最后的结果,也就是说这里arr返回的是'1,2,3',parseInt读到了一个'1',读到逗号的时候读取结束,所以结果为1
  • 而Object的默认toString返回的都是'[object object]',parseInt无法识别'[',故在第一位就结束解析,返回NaN

其它,Array和Object的行为是相同的


向布尔值转换

这个就可以说是重中之重了,但同时也是最简单的

需要着重记忆的就是,在JavaScript中,只有这些值转换为布尔类型后是false

  • undefined
  • null
  • false
  • 0
  • -0
  • NaN
  • 空字符串

上面这7个值,无一例外全部都是基本类型值,并不包括包装对象包装后的值,如:

const num = new Number(0)
const bool = new Boolean(false)
const str = new String('')

!num // false
!bool // false
!str // false

取反后的结果都为假,所以它们的都是真值,true

除了上面列表的7个值之外,其它所有的值都为真值,也就是进行布尔转换后的结果为:true

只要记住这7个值,程序中的布尔转换基本就没有问题了

总结

总结此篇意在提醒自己强制类型转换在JavaScript中的重要性,并且在我们编程时得到了某个结果并不是它原来的类型,我们也需要知道它为什么会这样变,否则,debug之路将异常艰辛,如有错误内容,烦请指出。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第一章JavaScript基础 1. 如何实现JavaScript 4 (1) 如何把JavaScript代码放到HTML页面里 4  使用[removed]标签,直接在HTML代码里加入JavaScript代码 4  使用[removed]调用外部的JavaScript(.js文件) 4  如何与老浏览器打交道 4 (2) 把JavaScript代码放到HTML页面不同的位置 4  把JavaScript代码放到HTML<body>部分 4  把JavaScript代码放到HTML<head>部分 4  示例代码 4 2. JavaScript语句与注释 5 (1) JavaScript语句结束符 5 (2) JavaScript代码块 5 (3) JavaScript注释 5 (4) JavaScript变量 5  变量命名规则 5  申明或创建变量 5  重新申明变量 5 3. JavaScript运算符 5 (1) 基本运算符 5 (2) 位运算符(~(非运算)、&(与运算)、|(或运算)、^(异或运算)) 5 (3) Boolean运算(!(求反运算)、&&(与运算)、||(或运算)) 6 (4) 关系运算(<、>、<=、>=、==、!=) 6 (5) 等性运算符(==、===、!=、!==) 6 (6) 条件运算符 7 (7) 其他运算符 7  赋值运算符 7  逗号运算符 7  typeof运算符 7  delete运算符 7  instanceof运算符 7 4. with语句 8 (1) 语法及作用 8 (2) 示例 8 5. 分支语句 8 (1) if...else...语句 8 (2) switch语句 8 6. 消息框 8 (1) 警告框 8 (2) 确认框 8 (3) 提示框 9 7. 函数(一般定义到<head>标签之间) 9 (1) 定义函数 9 (2) 关于函数的arguments对象 9  在函数代码,使用特殊对象 arguments,开发者无需明确指出参数名,就能访问它们。 9  使用arguments.length检测参数个数 9  模拟函数重载 10 (3) Function对象(类) 10  Function对象的使用 10  使用Function类的length属性 11  使用Function类的valueOf()方法和toString()方法 11 (4) 闭包 11 8. 循环语句 11 (1) for循环 12  for循环的使用格式 12  例子 12 (2) while循环 12  While循环的使用格式 12  例子 12 (3) 使用break和continue退出循环 12 9. JavaScript事件 12 (1) 事件句柄 12 (2) onload和onUnload 13 (3) onFocus, onBlur 和 onChange 13 (4) onSubmit 13 (5) onMouseOver 和 onMouseOut 13 (6) JavaScript 计时事件 13 10. 异常处理Try...Catch 语句 14 (1) 异常处理格式 14 (2) 例子 14 (3) Throw声明 14  Throw语法 14  例子 14 (4) onerror事件 14  onerror事件功能 14  语法 14  例子 14 11. 特殊字符与注意点 15 (1) 特殊字符 15 (2) 注意点 15  JavaScript 对大小写敏感 15  空格 15  换行 15 第二章JavaScript对象 1. JavaScript对象简介 15 (1) JavaScript对象也是有属性和方法的 15  对象属性的使用 15  对象方法的使用 15 (2) 对象的定义与实例化 15 (3) 对象的作用域 16  JavaScript对象只有公用作用域 16  JavaScript对象没有静态作用域 16  关键字this 16 (4) 定义类或对象的方法 16  工厂方式 16  构造函数方式和原型方式 17  混合的构造函数/原型方式(常用方式) 18  动态原型方法(常用方式) 18  总结(使用哪种方式好) 18 2. 修改对象 18 (1) 创建新方法 18 (2) 重命名已有方法 19 (3) 添加与已有方法无关的方法 19 (4) 为本地对象添加新方法 19 3. 对象类型转换 19 (1) 转换成字符串 19 (2) 转换成数字 20  parseInt() 20  parseFloat() 20 (3) 强制类型转换 20  Boolean() 函数 20  Number()函数 20  String() 函数 21 4. JavaScript常用对象 21 (1) 对象类型说明 21  本地对象 21  内置对象 21  宿主对象 21 (2) Object对象 21  Object对象的属性 21  Object对象的方法 21 (3) JavaScript对象参考(略) 21 (4) Browser(浏览器)对象参考(略) 21 (5) HTML DOM 对象参考(略) 22 第三章JavaScript 高级 1. Cookie 23 (1) cookie的作用 23 (2) 示例 23 2. 访问HTML DOM节点 24 (1) 查找并访问节点(得到节点对象) 24  使用getElementById()得到某个节点对象 24  使用getElementsByTagName()得到某些节点对象 24  使用getElementsByName()得到某些节点对象 25  parentNode、firstChild以及lastChild属性 25 (2) 访问节点的内容 26  innerText(IE)、textContent(FF)、innerHTML 26  outerText、outerHTML(少用) 26  value属性获取表单节点内容 26 (3) 访问节点的样式 27  使用节点对象的style属性对象改变样式 27  使用className设置样式 27 3. 继承机制实现 27 (1) 继承的方式 27 (2) 继承方式1—对象冒充 27 (3) 继承方式2—call()方法与apply()方法 28  call()方法 28  apply()方法 28 (4) 继承方式3—原型链(prototype chaining) 29 (5) 继承方式4—混合方式 29 附:ECMAScript关键字与保留字 1. 关键字 30 2. 保留字 30

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值