我目前正在学习前端,我专业并不是计算机科学,因为对这方面喜欢吧,现在转行学习前端,希望自己可以在前端路上不断进步。写技术博客一方面是为了督促自己不断学习,另一方法也是为了能够从大家的的评论中得到自己的不足,当然如果有同学觉得有点帮助就更好啦,因为属于前端小白,如有错误,请大家不吝赐教,感谢大家。
思考题
个人感觉js的类型转换涉及知识点蛮多也蛮杂,我在学习的过程中就 迷惑了好久,现在对这个知识点进行整理一下,首先从几个题目引入今天的问题。
(1) console.log([]==![]);
(2) console.log(“A” - “B” + “2”);
(3) console.log(“A” - “B” + 2);
(4) console.log(1<2<3);
(5) console.log([]+true);
(6) console.log({}+true);
(7) console.log({}+[]);
(8) console.log([]+[]);
(9) console.log({}+{});
(10) console.log([]+null);
(11) console.log({}+null)
这几道题应该输出什么呢?如果你对此有疑问请继续往下看,看完这篇文章,你应该有所了解。
言归正传
js是弱类型语言,那么和强类型语言有什么区别呢?强类型语言声明变量时需要声明变量的类型,申明什么变量就是什么变量,强类型语言转换变量类型时需要采用强制类型转换。而js声明变量使用var,const,let等关键字,对变量赋值的时候会自动判断变量类型而不会显性声明,可以对一个变量赋予不同的数据类型,js在进行运算或者重新赋值时会对数据类型进行转换,比如:alert会自动转化为字符串显示,数学运算符会将值转化为数字。js类型转换分为显性类型转换、隐性类型转换两种方式。转换的数据类型又分为两大类,简单数据类型(number,string,boolean,undefined,null,symbol),复杂数据类型(object,array,function)。对不同的数据类型转化方式会有所不同。
数据类型转换
1、Symbol.toPrimitive()方法介绍
对数据类型转换需要先了解一个方法,Symbol.toPrimitive(),作为对象的函数值属性存在的,当一个对象需要跳转对应的原始值时,会调用这个函数。该函数被调用时,会传递一个字符串参数hint,表示要转换到原始值的预期类型。hint的取值是“number”,“string”,“default”中的任何一个。那么这个参数有什么用呢?下面我们来了解一下。
var obj = {
[Symbol.toPrimitive](init) {
console.log(init)
}
}
String(obj); //string
`${obj}`; //string
obj+'abc'; //default
obj+1; //default
obj=='1'; //default
Number(obj); //number
2/obj; //number
-obj; //number
得出结论:
当需要对象转化为字符串再进行操作时,hint为'string’,如
alert(),String(),模版字符串;当对对象进行数学运算时,为'number',如Number(),运算符-,>;在极少数的情况下,不确定所期望的类型时,为'default';比如+可以同时使用字符串和数字,对象==与字符串,数字或符号进行比较也不知道是哪种转换,默认为default。没有boolean的提示,这是为什么呢?因为所有的对象都会转化为true
那么当复杂数据类型转化为基本类型时,会发生什么呢?首先会调用对象的
[Symbol.toPrimitive](hint)这个方法,判断这个方法输出什么,如果是string,就尝试使用toString()和valueOf();如果是number或者default;就尝试使用valueOf()和toString();这两个有什么区别呢,调用的先后顺序不同罢了。下面还会重点讲。 参考于Object to primitive conversion
2、toString()方法及valueOf()方法介绍
对复杂数据类型转换还需要介绍两个方法,一个是toString()方法,一个是valueOf()方法。这两个方法定义在Object的原型上。Object.prototype.toString()返回反映这个对象类型的字符串,Object.prototype.valueOf()返回相应的原始值;如下面所示
let obj={
name:'song',
age:18,
sayHi(){
console.log('hi')
}
}
console.log(obj.toString()); //[object object]
console.log(obj.valueOf()); //{name:'song',age:18,sayHi:f}
Objectd原型上的toString()方法返回的是对象内部的[[class]]属性,这个属性是指这个对象属于什么类型,为[object objectName]。这也是为什么大家会借用Object.prototype.toString.call()对对象类型进行判断,后面的内容还会讲到这个方法。
但也有一些其他的情况,如下面所示
let str=new String('hihihi');
console.log(typeof str); //object
console.log(str.toString()); //hihihi
console.log(str.valueOf()); //hihihi
这好像这和我们上面写的不一样,为什么这样子呢,这是因为js内置对象重写了Object原型对象的toString()和valueOf()方法。
还记得我们对象方法查找顺序了吗?当我们调用对象方法和属性时,首先在对象上查找,对象上查找不到的话就在对象原型对象上进行查找。内置对象重置了toString()和valueOf()方法,因此不会调用Object原型上的方法,下面就是对一些内置对象做一个总结。如下表所示:
| 类型 | toString()方法 | valueOf方法 |
|---|---|---|
| Object | 返回"[object objectName]" | 对象本身 |
| String | String对象的值,字符串 | 字符串本身 |
| Number | 字符串表示的数值,字符串 | 数字本身 |
| Boolean | “true”或者“false”,字符串 | Boolean值 |
| Array | 每个项用“,”连接起来,字符串 | 数组本身 |
| Date | 日期的文本表示,字符串 | 从1970年1月1日的毫秒数 |
| function | 函数本身,字符串 | 函数本身 |
看到这里,出几个题给大家,下面会输出什么呢?
console.log({}.toString())
console.log({}.valueOf())
console.log([].toString())
console.log([].valueOf())
答案:[object,object],{},'',[]
大家答对了吗?首先第一个{}表示一个空对象,对象的toString()和valueOf()方法直接调用Object原型上的方法,返回的[object,object],对象本身{};[]表示是一个空的数组,数组是内置的对象,重写了对象的toString()和valueOf()方法,返回的分别是空值和数组本身[]。
3、强制类型转换
强制类型转换指的是调用Number(),String(),Boolean()构造方法对数据类型进行强制转换。强制类型转换分为两种,一种是调用构造方法对基本数据类型进行强制类型转换;一种是构造方法对复杂类型进行强制类型转换。那么该如何进行转化呢,还记得我们上面学的方法吗?按照以下顺序进行执行转化:
- 当对对象进行类型转化时,首先调用
Symbol.toPrimitive()方法,判断这个方法提示什么,- 如果是string,就调用对象的
toString()方法,输出基本数据类型就输出,是复杂数据类型就调用对象的valueOf(),输出基本数据类型就输出,是复杂数据类型就报错;- 如果是number或者default,就调用对象的
valueOf()方法,输出的是基本数据类型就输出,是复杂数据类型就调用对象的toString()方法,输出基本数据类型就输出,如果是复杂数据类型就报错;
下面我们改写一下内置的Symbol.toPrimitive()方法,看会输出什么?
let user = {
name: "John",
money: 1000,
[Symbol.toPrimitive](hint) {
return hint == "string" ? `{name: "${this.name}"}` : this.money;
}
};
alert(user); // {name: "John"}
alert(+user); // 1000
alert(user + 500); // 1500
怎么会这样呢,进行分析一下,首先会调用这个对象我们重写的Symbol.toPrimitive()方法,第一问hint的值是string,输出响应的name属性;第二问hint的值是number,输出相应的1000,第三问hint的值是default,输出相应的1500。我们可以对Symbol.toPrimitive()方法进行改写,输出我们想要的值。
如果没有改写Symbol.toPrimitive()会发生什么呢?下面对这几种情况进行分析:
(1)、转换成字符串
基本数据类型强制转换成字符串
let abc=123,und=undefined,nu=null,sym=Symbol('str');
console.log(String(abc)) //'123'
console.log(String(und)) //undefined
console.log(String(true)) //true
console.log(String(nu)) //null
console.log(String(sym)) //Symbol(str)
基本数据类型的转化不需要调用Symbol.toPrimitive()方法,比较简单。
复杂数据类型强制转化为字符串就比较复杂,按照下面四步进行:
- 首先调用
Symbol.toPrimitive()方法,判断这个方法输出的是string- 继续调用对象的
toString()方法,如果返回基本数据类型,就用String()继续转换。- 如果
toString()方法返回的不是基本数据类型,则调用对象的valueOf()方法,如果返回的是基本数据类型的值,就使用String()这个继续转换。- 如果
valueOf()方法返回的也不是基本数据类型,就抛出错误。
按照上面四条我们一块做几个题吧。
第一题:
let obj={
name:'song',
age:18,
sayHi(){
console.log('hi')
}
}
console.log(String(obj));
答案是:[object,object]
这个题我们一块分析一下,我们没有改写Symbol.toPrimitive()方法,首先调用对象内部Symbol.toPrimitive()方法,判断这个方法输出的是string,这个对象内部没有toString()方法,也不是内置对象,因此会调用Object.prototype.toString()方法,返回的是[object object],这个字符串是基本数据类型,再调用String()方法,最后返回[object,object]。这个题没什么毛病,那我们再改一下题。
第二题:
let obj={
name:'song',
age:18,
sayHi(){
console.log('hi')
},
toString(){
return 1
}
}
console.log(String(obj));
答案是:'1'
这个题会输出什么呢,我们没有改写Symbol.toPrimitive()方法,首先调用对象内部Symbol.toPrimitive()方法,判断这个方法输出的是string,这个对象内部有一个toString()方法,我们直接会进行调用返回的是数值1,对这个基本数据类型进行String()方法转换,最后的到'1'这个字符串,是不是很有趣。没错,我们还要对这个题目进行更改。
第三题:
let obj={
name:'song',
age:18,
sayHi(){
console.log('hi')
},
toString(){
return {a:1}
},
valueOf(){
return 2
}
}
console.log(String(obj));
答案是:'2'
这个题会输出什么呢,我们没有改写Symbol.toPrimitive()方法,首先调用对象内部Symbol.toPrimitive()方法,判断这个方法输出的是string,这个对象内部有一个toString()方法,我们直接对其进行调用,返回的是一个对象,所以调用这个对象的valueOf()方法,返回的是数值2。返回值是基本数据类型,对这个数值1进行String()进行类型转换,最后的到字符串'2',这个题就结束了。当然还可以改题。
第四题:
let obj={
name:'song',
age:18,
sayHi(){
console.log('hi')
},
toString(){
return {}
}
}
console.log(String(obj));
答案是:报错
这个题会输出什么呢,我们没有改写Symbol.toPrimitive()方法,首先调用对象内部Symbol.toPrimitive()方法,判断这个方法输出的是string,继续调用对象内部的toString()方法,返回对象{},不是基本数据类型,然后调用对象的valueOf()方法,返回对象本身,也不是基本数据类型,报错误。再改一下题目
第五题:
let obj={
name:'song',
age:18,
toString(){
return '1';
},
valueOf(){
return '2'
},
[Symbol.toPrimitive](hint) {
if(hint==='string'){
return '3'}
}
}
console.log(String(obj));
答案是:'3'
大家做对了吗?我们改写了Symbol.toPrimitive()方法,调用改写 的Symbol.toPrimitive()方法,首先判断这个方法输出的是string,直接输出'3'。
大家学会了吗?
(2)、转化成数值
基本数据类型强制转化为数值
console.log(Number('123')); //123
console.log(Number('123b')); //NaN
console.log(Number('abv')); //NaN
console.log(Number(undefined); //NaN
console.log(Number(null)); //0
console.log(Number(true)); //1
console.log(Number(false)); //0
console.log(Number(Symbol('abc'))); //报错
这个也比较简单就不说了
复杂数据类型强制转化为数值
这个就比较复杂,按照下面四步进行:
- 首先调用
Symbol.toPrimitive()方法,判断这个方法输出的是number- 继续调用对象的
valueOf()方法,如果返回基本数据类型,就用Number()继续转换。- 如果
valueOf()方法返回的不是基本数据类型,则调用对象的toString()方法,如果返回的是基本数据类型的值,就使用Number()这个继续转换。- 如果
toString()方法返回的也不是基本数据类型,就抛出错误。 注:调用Symbol.toPrimitive()方法,判断这个方法输出的是default,也是按照上面的步骤进行。
废话不说,上题
第一题:
let obj={
name:'song',
age:18,
sayHi(){
console.log('hi')
}
}
console.log(Number(obj));
答案是:NaN
对这个题进行分析,我们没有改写Symbol.toPrimitive()方法,首先调用对象内部Symbol.toPrimitive()方法,判断这个方法输出的是number,调用对象的valueOf()方法,返回的是这个对象本身,是复杂数据类型,然后调用这个对象的toString()属性,返回的是字符串[object,object],对返回内容进行强制转换数据类型Number(),返回的就是NaN。改一下这个题目。
第二题:
let obj={
name:'song',
age:18,
sayHi(){
console.log('hi')
},
toString(){
return 1
}
}
console.log(Number(obj));
答案是:1
对这个题进行分析,我们没有改写Symbol.toPrimitive()方法,首先调用对象内部Symbol.toPrimitive()方法,判断这个方法输出的是number,调用对象的valueOf()方法,返回的是这个对象本身,是复杂数据类型,然后调用这个对象的toString()属性,返回的是1,对返回内容进行强制转换数据类型Number(),返回的就是1。继续改一下。
第三题:
let obj={
name:'song',
age:18,
sayHi(){
console.log('hi')
},
toString(){
return 1
},
valueOf(){
return false
}
}
console.log(Number(obj));
答案是: 0
对这个题进行分析,我们没有改写Symbol.toPrimitive()方法,首先调用对象内部Symbol.toPrimitive()方法,判断这个方法输出的是number,调用对象的valueOf()方法,返回的false,简单数据类型,对false进行Numbwe()强制转换,返回的就是0。
(3)、转换成布尔值
转换成布尔值有以下几种情况
- 返回false的几种情况:空字符串,+0,-0,0,null,undefined,NaN
- 其他的值返回true(所有对象返回的都是true) 如下图所示:
console.log(Boolean([])); //true
console.log(Boolean({})); //true
console.log(Boolean('')); //false
console.log(Boolean('false')); //true
console.log(Boolean(null)) //false
console.log(Boolean(undefined)) //false
console.log(Boolean(NaN)); //false
console.log(Boolean(Symbol('s'))); //true
4、隐性类型转换
隐形类型转换是系统自动进行的,不需要人为干预,用户感受不到,首先调用对象的[Symbol.toPrimitive](hint)这个方法,判断这个方法输出什么,如果是string,就尝试使用toString()和valueOf();如果是number或者default;就尝试使用valueOf()和toString(),因此被称为隐形类型转换。这个和显性类型转换的规则一样,这里不再赘述,仅对[Symbol.toPrimitive](hint)返回的值进行分析。分为如下几种情况:
- 当需要对字符串的对象进行操作时,为string,如
alert(),String(),模版字符串;- 当在进行数学运算时,为number,如
Number(),运算符-,>;- 在极少数的情况下,不确定所期望的类型时,为default;比如+可以同时使用字符串和数字,对象==与字符串,数字或符号进行比较也不知道是哪种转换,默认为default。
上面已经对类型转换的情况都进行了介绍,下面会对相关的其他知识点进行补充,因为做题会用到
运算符优先级
为什么要讲运算符优先级呢,因为数据类型转换往往夹杂着各种运算符,知道他们的优先级,能够使我们更加理解。废话不多说,直接上大图。
| 运算符 | 描述 |
|---|---|
| . [] () | 字段访问、数组下标、函数调用 |
| ++ - ~ ! typeof new void delete | 一元运算符、返回数据类型、对象创建、未定义值 |
| * / ? | 乘法 除法 取模 |
| + - + | 加法 减法 字符串连接 |
| << >> >>> | 移位 |
| < <= > >= | 小于、小于等于、大于、大于等于 |
| == != === !== | 等于、不等于、恒等、不恒等 |
| & | 按位与 |
| ^ | 按位异或 |
| | | 按位或 |
| && | 逻辑与 |
| || | 逻辑或 |
| ?: | 三元运算符 |
| = | 赋值 |
| , | 多重求值 |
上面的的运算符从上到下,从左到右,权重依次减少。
==和===有什么不同
“===”指的是严格相等,左右两边不仅值要相等,类型也要相等;“==”指的是不严格相等,相同类型的直接进行比较,值相等返回true,否则返回false;不同类型先进行类型转换再进行比较,下面是转换的规则:
- 如果有一个是布尔型,布尔值需要转化为数值
- 如果一个是字符串,一个是数值,需要把字符串变成数值
- 如是一个是对象,需要调用对象的valueOf()方法,得到基本类型后再运用前面规则
- null和undefined是相等的,且他们不能转化为任何值
- NaN和任何都不相等
- 如果两个都是对象,比较是不是同一个对象
看完以上的内容,下面开始解决题目了
仔细想一想下面题应该怎么做
(1) console.log([]==![]);
(2) console.log(“A” - “B” + “2”);
(3) console.log(“A” - “B” + 2);
(4) console.log(1<2<3);
(5) console.log([]+true);
(6) console.log({}+true);
(7) console.log({}+[]);
(8) console.log([]+[]);
(9) console.log({}+{});
(10) console.log([]+null);
(11) console.log({}+null)
(12)console.log(!document.all) //刚遇到的题,2020年4月27日
答案是:
(1)、‘!’运算符优于‘==','!'指的是逻辑非,后面的值会返回一个布尔值,'[]'的布尔值为true,所以‘![]’为false,‘[]==false’的值是什么呢?首先会将false值变为数字0,也就变成了'[]==0','[]'需要调用valueOf()转化为简单数据类型,但是‘[]’的调用结果还是'[]',再次调用‘[]’的toSring()方法得到‘“”==0’,字符串和数值比较,首先将字符串转化为数值,最后'0==0'所以答案为:true。
(2)、这个题就比较简单了,首先计算‘“A”-“B”’,基本数据类型使用Number()强制转换为NaN,NaN-NaN还是NaN,接下来是NaN+‘2’,两个基本数据类型‘+’看作一个连接字符串的符号,所以答案为:‘NaN2’
(3)、这个题也是比较简单,首先计算‘“A”-“B”’,基本数据类型使用Number()强制转换为NaN,NaN-NaN还是NaN,接下来计算NaN+2,两个数值相加,NaN和别人相加,还是NaN,而且这个typeof NaN 还是number类型哦,所以答案为:NaN
(4)、这个题也是很有意思的题,'1<2<3',首先按照顺序,会对1<2进行比较,比较结果为true,true<3进行比较,true可以转化为1,所以答案为true。还有一种情况‘3>2>1’,猜猜会输出什么,答案为false
(5)、[]+true,一个是复杂数据类型,一个是基本数据类型,进行“+”运算时,对象的[Symbol.toPrimitive](hint)输出的是default,因此调用对象的valueOf()方法,返回的是[]本身,是复杂数据类型,调用对象的toString(),返回的是“”,"+"在这里起到的作用是字符串的拼接,返回的是true,而且这个tre是字符串形式,答案是:‘true’
(6)、{}+true,这个题和上面的题类似,进行“+”运算时,对象的Symbol.toPrimitive输出的是default,因此调用对象的valueOf()方法,返回的是{}本身,是复杂数据类型,调用对象的toString(),返回的是'[object object]',"+"在这里起到的作用是字符串的拼接,返回的是'[object object]true',所以答案是:'[object object]true'
(7)、这个题和上面两问有关系,首先两个复杂数据类型,进行“+”运算时,对象的Symbol.toPrimitive输出的是default,因此调用对象的valueOf()方法,{}返回{},[]返回[],都是复杂数据类型,调用对象的toString(),{}返回的是'[object object]',[]返回的是"","+"在这里起到的作用是字符串的拼接,所以最后返回'[object object]'。答案是:'[object object]'
下面的几道题我觉得也没必要分析了,不明白的同学可以试一下哦!
(12)、这个题是刚遇到的,还是要将一下,我觉得挺有意思的,先来说一个概念的意思吧,了解到下面的定义后,这道题就容易解了,‘!’会把对象强制转化为布尔值,而假值对象转化布尔值为false,‘!document.all’就为‘true’
假值对象:浏览器在某些特定情况下,在常规 JavaScript 语法基础上自己创建了一些外来值,这些就是“假值对象”。假值对象看起来和 普通对象并无二致(都有属性,等等),但将它们强制类型转换为布尔值时结果为 false 最常见的例子是 document.all,它 是一个类数组对象,包含了页面上的所有元素,由 DOM(而不是 JavaScript 引擎)提供给 JavaScript 程序使用。
如何检测数据类型
1、typeof
对于基本数据类型来说,检测null返回object,其他类型都可以显示正确的类型;对于复杂数据类型来说,函数返回的是function,其他的都是object。这是因为,不同对象在底层都表示成二进制,二进制的前三位为零的话,返回object,而null的前四位都是0,因此返回object,凡是返回object的类型都会判断是否实现了call方法,实现的话返回functon;typeof不能很好的区分复杂数据类型,因此采用typeof检测复杂数据类型是不合适的。
typeof 1 //number
typeof 'str' //string
typeof undefined //undefined
typeof true //boolean
typeof Symbol() //symbol
typeof null // object
typeof {}//object
typeof []//object
typeof function fn(){} //function
2、instanceof
instanceof指的是一个对象在其原型链上是否存在一个构造函数的prototype属性,可以用来判断一个实例是否属于这个一个类型;数组属于object也是对的,可以检测自定义的实例,存在的问题不能区分基本的数据类型,不可以检测null和undefined,基本数据类型转化为复杂数据类型,可以检测;
[] instanceof Array // true
[] instanceof Object // true
'str' instanceof String //false
new String('str') instanceof String //true
function Person(name){
this.name=name;
}
let person=new Person('song');
console.log(person instanceof Person); //true
3、constructor
对象的原型的constructor指向对象的构造函数,因为原型的属性constructor可以被对象访问到,所以可以对对象的类型进行判断,但不能判断null和undefined。这种方法虽然能准确检测引用类型和基本类型,但是可以手动对对象的constructor进行设置,因此准确性不高。
("1").constructor === String; //true
(1).constructor === Number; //true
(true).constructor === Boolean; //true
([]).constructor === Array; //true
(function() {}).constructor === Function; //true
({}).constructor === Object; //true
4、Object.prototype.toString.call()
上面也讲过toString()这个方法,返回的是反映这个对象类型的字符串,但是内部对象都重写了这个属性,因此我们可以通过调用Object.prototype.toString.call()对对象类型进行检测。
var test = Object.prototype.toString;
console.log(test.call("str")); //[object String]
console.log(test.call(1)); //[object Number]
console.log(test.call(true)); //[object Boolean]
console.log(test.call(null));//[object Null]
console.log(test.call(undefined)); //[object Undefined]
console.log(test.call([])); //[object Array]
console.log(test.call(function() {})); //[object Function]
console.log(test.call({})); //[object Object]
最后再封装一个检测类型的函数
function checkType(target){
let tar=typeof target;
let template = {
"[object Array]": "array",
"[object Object]":"object",
"[object Number]":"number",
"[object Boolean]":"boolean",
"[object String]":'string'
};
if(target===null){
return 'null'
}
else if(tar==='object'){
return template[Object.prototype.toString(target)];
}
else {
return tar;
}
}
完毕,谢谢大家