内置类型
JS 中分为七种内置类型又分为两大类型:基本类型和对象(Object)。
基本类型有六种: null,undefined,boolean,number,string, symbol(ES6引入的原始数据类型)。
number
JS 的数字类型是浮点类型,并且浮点类型基于 IEEE 754标准实现,在使用中会遇到某些 Bug。
如
var num1 = 0.1;
var num2= 0.2;
// 0.1 + 0.2 不等于 0.3
console.log(num1 + num2); // 0.30000000000000004
浮点数不如整数精准,所以一般不要使用浮点数进行计算
NaN 是 not a number 的简写,即非数字。它是一个特殊的值,这个数值用于表示一个本来要返回数值的操作数,结果未返回数值的情况。
NaN 有两个不同寻常的特点:
- 任何涉及 NaN 的操作都会返回 NaN
- NaN 值与任何值都不相等,包括本身。
Symbol
1.Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
2.注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
3.由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。
例子:
//例1
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
//例2
let s = Symbol();
let obj = {
[s]: function (arg) { ... }
};
obj[s](123);
//例3
let obj = {
[s](arg) { ... }
};
//注意,Symbol 值作为对象属性名时,不能用点运算符。
const mySymbol = Symbol();
const a = {};
a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"
Typeof
typeof 对于基本类型,除了 null 都可以显示正确的类型
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof b // b 没有声明,但是还会显示 undefined
typeof 对于对象,除了函数都会显示 object
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
对于 null 来说,虽然它是基本类型,但是会显示 object,这是一个存在很久了的 Bug
typeof null // 'object'
如果我们想获得一个变量的正确类型,可以通过 Object.prototype.toString.call(xx)。这样我们就可以获得类似 [object Type] 的字符串。
let a
// 我们也可以这样判断 undefined
a === undefined
// 但是 undefined 不是保留字,能够在低版本浏览器被赋值
let undefined = 1
// 这样判断就会出错
// 所以可以用下面的方式来判断,并且代码量更少
// 因为 void 后面随便跟上一个组成表达式
// 返回就是 undefined
a === void 0
类型转换
转Boolean
在条件判断时,除了 undefined, null, false, NaN, ‘’, 0, -0,其他所有值都转为 true,包括所有对象。
对象转基本类型
1.对象在转换基本类型时,会调用 valueOf 和 toString。并且这两个方法你是可以重写的。
2.调用哪个方法,我们可以看看以下例子:
以alert(obj)为例,我们知道,alert(obj)会优先调用obj的toString方法,其内部实现机制是这样的:
- alert调用该引用的GetValue方法,返回它所指向的对象
- alert该值的基础上调用toString方法(这个跟对象的toString方法不同)
- toString在该值上调用toPrimitive方法,传递hint参数为string(是toPrimitive方法的一个参数)
- toPrimitive调用对象的内部方法[[DefaultValue]],并传递hint参数为string
- [[DefaultValue]]调用对象(obj)的toString方法,并把this指向该对象
- toString的结果是一个基本数据类型,返回该值 结果是一个字符串类型,将其返回该alert alert展现这个值
我们注意到,最开始的GetValue和toString其实是alert调用的方法,对象转化的核心方法其实是toPrimitive,这个方法获取一个任意值,并返回一个基础数据类型,如果传入的参数不是一个基础数据类型,将会调用对象内部的 [[DefaultValue]]方法获取其default value(默认值)。
[[DefaultValue]]是每个对象都有的一个内部方法,这个方法可以接收一个形参hint,这个形参的值要么是string,要么是number,如果为空,则默认为number,除非该对象是Date,Date的该方法默认传入的是string。之后会决定是调用对象的toString方法还是调用对象的valueOf方法。如果hint是number,则优先调用valueOf方法,否则优先调用toString方法。
具体过程如下(我们把优先调用的方法叫做方法一):
- 如果方法一存在,而且可以被调用,则调用它获取结果,否则跳到第3步
- 如果1的结果是基本类型,返回它
- 如果方法二存在,而且可以被调用,则调用它获取结果,否则跳到第5步
- 如果第三步的结果是基础数据类型,返回它
- 抛出TypeError异常
也就是说,toPrimitive方法返回的一定是一个基本数据类型,否则就抛出类型错误异常
var obj = {
toString () {
console.log('toString')
return 'string'
},
valueOf () {
console.log('valueOf')
return 'value'
}
}
alert(obj) // string
console.log(1 + obj) // 1value
如果重写了 toString 方法,而没有重写 valueOf 方法,则会调用 toString 方法
var obj = {
toString () {
return 'string'
}
}
console.log(1 + obj) // 1string
3.调用上述两个方法的时候,需要 return 原始类型的值 (primitive value)
如果在调用 valueOf 的时候,返回的不是原始类型的值,就会去调用 toString
var obj = {
toString () {
console.log('toString')
return 'string'
},
valueOf () {
console.log('valueOf')
return {}
}
}
console.log(1 + obj)
// 依次打印出
valueOf
toString
1string
如果返回还不是原始的值,就会报错
var obj = {
toString () {
console.log('toString')
return {}
},
valueOf () {
console.log('valueOf')
return {}
}
}
console.log(1 + obj)
// 报错。无法将一个对象转换为原始类型的值
Uncaught TypeError: Cannot convert object to primitive value
4.如果有 Symbol.toPrimitive 属性的话,会优先调用,它的优先级最高
var obj = {
toString () {
console.log('toString')
return {}
},
valueOf () {
console.log('valueOf')
return {}
},
[Symbol.toPrimitive] () {
console.log('primitive')
return 'primi'
}
}
console.log(1 + obj) // 1primi
同样只能 return 原始类型的值,否则会报和上面所说一样的错。
5.对于加操作的困惑
var obj = {
toString () {
console.log('toString')
return '1'
},
valueOf () {
console.log('valueOf')
return 2
}
}
console.log(1 + obj)
console.log('1' + obj)
// 依次输出
valueOf
3
valueOf
12
按理来说’1’ + obj的’+'是倾向于转为转换为字符串类型的(也就是调用 toString);但是,它最后却是调用 valueOf。
要理解该过程,就该理解加操作的计算过程:
- 计算加号左边,获取其值
- 计算加号右边,获取其值
- 两边同时调用toPrimitive方法,hint参数为空
- 如果任意一个类型为string,跳到第7步
- 将两个值转化为数字(调用ToNumber)
- 返回两个值之和
- 将两个值转化为字符串(调用ToString)
- 返回两个值的组合
可以看到,hint参数为空,而如果对象不是Date对象,则hint默认为number,即调用valueOf方法
四则运算符
只有当加法运算时,其中一方是字符串类型,就会把另一个也转为字符串类型。其他运算只要其中一方是数字,那么另一方就转为数字。并且加法运算会触发三种类型转换:将值转换为原始值,转换为数字,转换为字符串。
1 + '1' // '11'
2 * '2' // 4
[1, 2] + [2, 1] // '1,22,1'
// [1, 2].toString() -> '1,2'
// [2, 1].toString() -> '2,1'
// '1,2' + '2,1' = '1,22,1'
对于加号需要注意这个表达式 ‘a’ + + ‘b’
'a' + + 'b' // -> "aNaN"
// 因为 + 'b' -> NaN
// 你也许在一些代码中看到过 + '1' -> 1
== 操作符
上图中的 toPrimitive 就是对象转基本类型。
这里来解析一道题目 [] == ![] // -> true,下面是这个表达式为何为 true 的步骤
// [] 转成 true,然后取反变成 false
[] == false
// 根据第 8 条得出
[] == ToNumber(false)
[] == 0
// 根据第 10 条得出
ToPrimitive([]) == 0
// [].toString() -> ''
'' == 0
// 根据第 6 条得出
0 == 0 // -> true
比较运算符
- 如果是对象,就通过 toPrimitive 转换对象
- 如果是字符串,就通过 unicode 字符索引来比较
文章部分内容引用自:
https://yuchengkai.cn/docs/frontend
http://www.cnblogs.com/strayling/p/3752795.html
https://segmentfault.com/a/1190000013407859