数据类型及转换

Posted by Youzi Blog on July 24, 2019

关于JS数据类型和强制类型转换的一些点

从一些例题来引入话题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var bar = true;
console.log(bar + 0); // 1
console.log(bar + 'xyz'); // truexyz
console.log(bar + true); // 2
console.log(bar + false); // 1
console.log('1' > bar); // false
console.log(1 + '2' + false); // 12false
console.log('2' + ['koala', 1]); // 2koala,1
var obj1 = {
  a: 1,
  b: 2
};
console.log('2' + obj1); // 2[object, Object]
var obj2 = {
  toString: () => {
    return 'a';
  }
};
console.log('2' + obj2); // 2a

var b = 1;
const outer = () => {
  var b = 2;
  console.log(b);
  var b = 3;
};

数据类型

  1. 基本类型
    • string
    • boolean
    • number
    • null
    • undefined
    • symbol(ES6)
  2. 对象类型
    • 对象类型也叫引用类型,array, function都是对象的子类型,对象在逻辑上是属性的无序集合,是存放值的容器,对象本身存储的是引用地址。

前五种类型称为原始类型(Primitive),表示不能再细分下去的基本类型,symbol是ES6新增的类型,表示唯一值,直接由let s = Symbol()生成,和string, number, boolean一样,不要用new生成,不然得到的是对象。

另外null, undefined是两种特殊类型,值唯一,

1
2
3
4
null == undefined; // true
null === undefined; // false
typeof null; // object
typeof undefined; // undefined

弱类型语言和强制类型转换

JS是弱类型语言,声明变量时可以不指定变量类型,变量类型就是值的类型,且当值改变类型时,变量类型也会改变;let val = 'str'; val = 1;string类型转换成number

强制类型转换规则

ToPrimitive针对引用类型(object),其目的是将引用类型转换成原始类型。

1
2
3
4
5
/**
* @obj 需要转换的对象
* @type 期望转换为的原始数据类型,可选
*/
ToPrimitive(obj,type)

针对不同type,处理方式有所不同。

  • string
    1. 调用obj.toString(),如果结果是原始类型则return,否则第二步
    2. 调用obj.valueOf(),如果是原始类型则return,否则第三步
    3. 抛出TypeError异常
  • number
    1. 调用obj.valueOf()return or next step
    2. 调用obj.toString()return or next step
    3. 抛出TypeError异常
  • type为空
    1. 该对象为Date,则type被设置为String
    2. 否则,type被设置为Number

Object.prototype.toString()

对于这个方法,MDN给出的描述:每个对象都有一个toString()方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖(Array, Function的toString方法都被重写了),toString() 返回 "[object type]",其中type是对象的类型。

提炼出两点:对象的toString()方法,在对象被当做文本值或字符串方式引用时,被自动调用;如果对象原型上的toString()方法没有被重写或者覆盖,就会返回"[object type]"

Object.prototype.valueOf()

JavaScript调用valueOf方法将对象转换为原始值。你很少需要自己调用valueOf方法;当遇到要预期的原始值的对象时,JavaScript会自动调用它。

不同类型对象的valueOf方法的返回值:

  • Array: 返回对象本身
  • Boolean: 布尔值
  • Date: 时间戳,即毫秒数
  • Function: 函数本身
  • Number: 数字值
  • Object: 默认是对象本身
  • String: 字符串值
  • Math和Error对象没有valueOf方法

看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var ary = new Array(1, 2, 3);
console.log(ary.valueOf()); // [1, 2, 3]

var str = new String('123');
console.log(str.valueOf());//123

var num = new Number(123);
console.log(num.valueOf());//123

var date = new Date();
console.log(date.valueOf()); //1526990889729

var bool = new Boolean('123');
console.log(bool.valueOf());//true

var obj = new Object({valueOf:()=>{
    return 1
}})
console.log(obj.valueOf());//1

Number

Number的转换规则:调用Number()

  • null => 0
  • undefined => NaN
  • true => 1; false => 0
  • 字符串转换时遵循数字常量规则,失败返回NaN

String

String转换规则:

  • null => 'null'
  • undefined => 'undefined'
  • true => 'true'; false => 'false'
  • 数字转换就遵循通用规则,极大极小的数字使用指数形式
1
2
3
4
5
6
7
8
9
10
11
12
13
String(null)                 // 'null'
String(undefined)            // 'undefined'
String(true)                 // 'true'
String(1)                    // '1'
String(-1)                   // '-1'
String(0)                    // '0'
String(-0)                   // '0'
String(Math.pow(1000,10))    // '1e+30'
String(Infinity)             // 'Infinity'
String(-Infinity)            // '-Infinity'
String({})                   // '[object Object]'
String([1,[2,3]])            // '1,2,3'
String(['koala',1])          //koala,1

转换规则的应用场景

  • 转换成string类型
    • 非对象类型下,字符串的加法运算时,如果其中一个值是字符串,则另一个值会被转换成字符串;
    • 对象类型进行加法运算时,根据前面的toPrimitive方法且type = number的规则进行转换();这里可能会有疑问,为什么是type = number不是string呢,原因是加法运算符会优先把对象转换成数字类型,注意是对象,非对象类型的基本类型不会先做这种转换,会保持他们原有的类型。
    • 关于如何验证会优先将对象转换成数字类型,下面提供了一个方法,可以看到会先去调用valueOf方法,这和先转换成number类型的步骤一致。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
        let obj = {
        toString: () => {
            return 'toPrimitive(obj, string)';
        },
        valueOf: () => {
            return 'toPrimitive(obj, number)';
        }
        }
        console.log('result: ' + obj);
        // result: toPrimitive(obj, number)
      

    一些加法运算符对象转字符的例子,都是先调用valueOf,如果调用返回的不是原始类型,会接着调用toString

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
      '2' + 1 // '21'
      '2' + true // "2true"
      '2' + false // "2false"
      '2' + undefined // "2undefined"
      '2' + null // "2null"
    
      //toString的对象
      var obj2 = {
              toString:function(){
                      return 'a'
              }
      }
      console.log('2'+obj2)
      //输出结果2a
    
      //常规对象
      var obj1 = {
          a:1,
          b:2
      }
      console.log('2'+obj1);
      //输出结果 2[object Object]
    
      //几种特殊对象
      '2' + {} // "2[object Object]"
      '2' + [] // "2"
      '2' + function (){} // "2function (){}"
      '2' + ['koala',1] // 2koala,1
    
  • 转换成number类型
    • 有加法,但没用string类型的时候,会优先转成number;
    • 除了加法运算符,其他运算符会转成数值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
      true + 0 // 1
      true + true // 2
      true + false //1
      '5' - '2' // 3
      '5' * '2' // 10
      true - 1  // 0
      false - 1 // -1
      '1' - 1   // 0
      '5' * []    // 0
      false / '5' // 0
      'abc' - 1   // NaN
      null + 1 // 1
      undefined + 1 // NaN
    
      //一元运算符(注意点)
      +'abc' // NaN
      -'abc' // NaN
      +true // 1
      -false // 0
    
  • 判断等号

==运算符与其他符号不同,是Number优先的,意思是会将等号两边的值转换成number类型再去比较。

  1. 两边都是number,直接比较。 1 == 2 //false
  2. 如果有对象,调用toPrimitive(obj, number)进行转换,再比较。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var obj1 = {
    valueOf:function(){
        return '1'
    }
}
1 == obj1  //true
//obj1转为原始值,调用obj1.valueOf()
//返回原始值'1'
//'1'toNumber得到 1 然后比较 1 == 1

[] == ![] //true
//[]作为对象ToPrimitive得到 ''
//![]作为boolean转换得到0
//'' == 0
//转换为 0==0 //true
  1. 存在布尔值,按Number()方法将boolean转换成1或0,再进行比较。
1
2
3
4
5
//boolean 先转成number,按照上面的规则得到1
//3 == 1 false
//0 == 0 true
3 == true // false
'0' == false //true
  1. 如果一个值是string,一个是number,前面都有这种情况,会先把string转成number,不举例子了。
  • 布尔值转换

两种情况会发生转换成布尔值,一是布尔值比较,二是if, while, 三元运算符等包含布尔值条件的,还有我们经常看到这种写法,!!expression,把表达式转换成布尔值。

判断数据类型

typeof

typeof能判断大部分类型,常用来判断基本类型和函数,对于数组和null,只能判断出object

1
2
3
4
5
6
7
8
9
10
typeof 'seymoe'    // 'string'
typeof true        // 'boolean'
typeof 10          // 'number'
typeof Symbol()    // 'symbol'
typeof null        // 'object' 无法判定是否为 null
typeof undefined   // 'undefined'

typeof {}           // 'object'
typeof []           // 'object'
typeof(() => {})    // 'function'

instanceof

instanceof可以通过测试构造函数的prototype是否出现在被检测对象的原型链上来判断。

1
2
3
4
5
let arr = []
let obj = {}
arr instanceof Array    // true
arr instanceof Object   // true
obj instanceof Object   // true

很明显数组实例的原型链上是有Array构造函数的,又因为ArrayObject的子类型,arr.__proto__ === Array.prototype; Array.__proto__ === Object.prototype,所以导致了数组实例既属于数组,又属于对象类型。

Object.prototype.toString()

终极方法Object.prototype.toString()来判断数据类型,前面也提到过了,会返回'[object type]'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Object.prototype.toString.call({})              // '[object Object]'
Object.prototype.toString.call([])              // '[object Array]'
Object.prototype.toString.call(() => {})        // '[object Function]'
Object.prototype.toString.call('seymoe')        // '[object String]'
Object.prototype.toString.call(1)               // '[object Number]'
Object.prototype.toString.call(true)            // '[object Boolean]'
Object.prototype.toString.call(Symbol())        // '[object Symbol]'
Object.prototype.toString.call(null)            // '[object Null]'
Object.prototype.toString.call(undefined)       // '[object Undefined]'

Object.prototype.toString.call(new Date())      // '[object Date]'
Object.prototype.toString.call(Math)            // '[object Math]'
Object.prototype.toString.call(new Set())       // '[object Set]'
Object.prototype.toString.call(new WeakSet())   // '[object WeakSet]'
Object.prototype.toString.call(new Map())       // '[object Map]'
Object.prototype.toString.call(new WeakMap())   // '[object WeakMap]'

该方法简单明了,本质是由Object.prototype.toString()方法得到对象内部属性[[Class]],传入原始类型却能够判定输出结果是因为对值进行了包装,null, undefined是JS内部实现有做相应的处理。

NaN

NaN是一种特殊的Number类型,通常在一些特殊运算时会返回;

  • 无穷大除以无穷大
  • 负数开平方
  • 算术运算符与无法转换成数字类型的操作数一直使用
  • 字符串解析成数字时,无法解析,返回NaN

String(), toString()

  1. toString()可以将除null, undefined以外的数据转换成字符串。
  2. toString()的参数可选,表示进制,范围从2-36

  3. String()可以将null, undefined转为字符串,但不能进行进制转换。