学习JavaScript中的“类型”和“值”

325 阅读9分钟

JavaScript中的“类型”

JavaScript有七种数据类型,六种基本类型和一种复杂数据类型:

  • 数值(Number)
  • 字符串(String)
  • 布尔值(Boolean)
  • 空值(Null)
  • 未定义(Undefined)
  • 符号(Symbol)
  • 对象(Object)

typeof操作符

我们可以使用typeof来检查值得类型,它返回的是类型的字符串值。

类型 结果
Number "number"
String "string"
Boolean "boolean"
Null "object"
Undefined "undefined"
Symbol "symbol"
Object "object"
Function "function"
typeof 2019 === "number";
typeof "tom" === "string";
typeof true === "boolean";
typeof null === "object";
typeof undefined === "undefined";
typeof Symbol() === "symbol";
typeof {name:"tom"} === "object";
typeof function foo(){} === "function";

typeof null === "object":

JavaScript最初的实现中,JavaScript中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于null代表的是空指针(大多数平台下值为0x00),因此,null的类型标签也成为了 0,typeof null就错误的返回了"object"。(mdn官方typeof有说明)

typeof function foo(){} === "function":

函数实际上object的一个“子类型”,函数是“可调用的对象”。它有一个内部属性[[Call]],该属性使其可以被调用。函数不仅是对象,理所当然可以给函数添加属性,例如:

function foo(name,age) {};
foo.job = "code";

console.log(foo.length);  //2   因为该函数声明了两个命名参数
console.log(foo.job);    //"code"

类型与值

JavaScript中的变量是没有类型的,只有值才有。变量可以随时持有任何类型的值。

var foo = 2019;
typeof foo === "number";//true

foo = "tom";
typeof === "string"; //true

在对变量执行typeof操作时,得到的结果并不是该变量的类型,而是该变量持有的值的类型。

undefined

变量在未持有值的时候为undefined。此时typeof返回"undefined":

var foo;

typeof foo;  //"undefined"

var bar = "cat";

var x;

bar = x;

typeof bar;  // "undefined"
typeof x; // "undefined"

当变量在作用域中还没有声明,我们有时会当它是undeclared

var foo;

foo; //"undefined"

bar; //Uncaught ReferenceError: bar is not defined

浏览器对一个没有声明的变量抛出bar is not defined,我们就会理解为undefined,如果浏览器能抛出bar is not found可能更准确。

更让人意外的是typeof处理未声明的变量,例如:

var foo;

typeof foo;// "undefined"

typeof bar; // "undefined"

对于一个未声明的变量(bar),typeof返回"undefined"。但这次执行typeof bar浏览器没有抛出异常,这是因为typeof有一个特殊的安全防范机制。

这个安全机制能帮助我们在运行JavaScript代码的时候避免一些异常情况的发生。对于不存在的变量及时做出处理。

例如有一个全局变量globalcommon.js中,如果我们不小心没有加载就会导致使用异常。

if(global){
    // ...其他代码
}

if(typeof global !== "undefined"){
    // ...其他代码
}

JavaScript中的“值”

数字

JavaScript中的数字类型是基于IEEE754标准来实现的,该标准通常也被称为“浮点数”。JavaScript使用的是“双精 度”格式(即 64 位二进制)。

语法

默认情况下大部分数字都以十进制显示,小数部分最后面的 0 被省略。

 var a = 11;
 var b = 11.1;
 var c = 0.11;
 var d = .11;
 var e = 11.0;
 var f = 11.;

特别大和特别小的数字默认用指数格式显示,与toExponential()函数的输出结果相同。

var a = 9e11;
console.log(a); //900000000000
a.toExponential(); //"9e+11"

var b = 1 / a;
console.log(b); //1.1111111111111112e-12

由于数字值可以使用 Number 对象进行封装,因此数字值可以调用 Number.prototype 中的方法。

var a = 11.11;

a.toFixed(1);
console.log(a); //11.1

a.toPrecision(2); 
console.log(a);  //"11"

上面的方法不仅适用于数字变量,也适用于数字常量。不过对于.运算符需要给予特别注 意,因为它是一个有效的数字字符,会被优先识别为数字常量的一部分,然后才是对象属 性访问运算符。

11.toFixed(2); //Uncaught SyntaxError: Invalid or unexpected token

(11).toFixed(2); // "11.00" 
0.11.toFixed(2); // "0.11" 
11..toFixed(2); // "11.00"
11 .toFixed(2); // "11.00"

11.toFixed(2)抛出异常时因为.被视为与常量是一个整体11.所以没有.属性访问运算符来调用tofixed方法。11..toFixed(2)则没有问题。

数值问题

由于JavaScript遵循IEEE 754规范的语言,所以会出现如下情况:

0.1 + 0.2 = 0.30000000000000004

0.1 + 0.2 === 0.3; //false 

从数学的角度,上面的结果应该为0.3。二进制浮点数中的0.10.2并不是十分精确,它们相加的结果并非刚好等于0.3,而是一个比较接近的数字0.30000000000000004,所以条件判断结果为false

JavaScript在处理带有小数的数字时需要特别注意。最常见的方法是设置一个误差范围值,通常称为“机器精度”,对JavaScript的数字来说,这个值通常是2^-52(2.220446049250313e-16)。

function numberEqual(x, y){
    return Math.abs(x - y) < Math.pow(2,-52)
};

var foo = 0.1 + 0.2;
var bar = 0.3;
numberEqual(foo, bar);

在新的语法ES6中Number.EPSILON可以取代Math.pow(2,-52)

整数安全范围

JavaScript中最大的整数2^53-19007199254740991,ES6使用Number.MAX_SAFE_INTEGER,最小则是-9007199254740991,ES6使用Number. MIN_SAFE_INTEGER

整数检测

检测一个值是否是整数,如下:

function isInteger(num) {
    return typeof num == "number" && num % 1 == 0;
};

//ES6中的Number.isInteger(..) 

isInteger(12231);
Number.isInteger(123111); 

检测一个值是否是安全的整数,如下:

function isInteger(num) {
    return typeof num == "number" && num % 1 == 0;
};

function isSafeInteger(num){
    return isInteger(num) && Math.abs(num) <= (Math.pow(2,53) - 1);
};

isSafeInteger(Math.pow(2, 53 ));   //false

//ES6中的Number.isSafeInteger(..) 
Number.isSafeInteger(Math.pow(2, 53 ) - 1);  //true

字符串

JavaScript中的字符串由零个或多个16位Unicode字符组成的字符序列。

var foo = "foo";

String拥有丰富的API,也是日常开发我们使用最多的一种类型。

字符串字面量

字面量 含义
\0 空字符
\' 单引号
\" 双引号
\\ 反斜杠
\n 换行
\r 回车
\v 垂直制表符
\t 水平制表符
\b 退格
\f 换页
\uXXXX unicode 码

这些字符字面量可以出现在字符串任意位置,例如:

var foo = "hello world /n/r";

字符串转换为数组

var foo = "abcdefg";

var arr = foo.split(""); //["a", "b", "c", "d", "e", "f", "g"]

其他类型转换为字符串

var a = 123;
a.toString(); // "123"

var b = [1,2,3];
b.join(''); //"123"

var c = true;
c.toString(); //"true"

在不知道转换的值是不是nullundefined的情况下,还可以使用String()

  • 如果值有toString()方法,则调用改方法
  • 如果值是null,则返回"null"
  • 如果值是undefined,则返回"undefined"
var v1 = 123123;
var v2 = true;
var v3 = null;
var v4;

console.log(v1);  //"123123"
console.log(v2);  //"true"
console.log(v3);  //"null"
console.log(v4);  //"undefined"

布尔值

布尔值只有2种值:truefalse

Boolean类型的truefalse区分大小写。

我们还可以使用Boolean()来进行类型转换。

var foo = "hello";
Boolean(foo);  //true

其他类型转换成布尔值

数据类型 转换为true 转换为false
String 任何非空字符串 ""(空字符串)
Number 任何非零数值 0和NaN
Object 任何对象 null
Undefined undefined

特殊值

undefined和null

undefined类型只有一个值,即undefinednull类型也只有一个值,即null。它们的名 称既是类型也是值。

undefinednull通常用来表示“空”或“不是值”。二者有一点细微的差别。

  • null指空值(曾赋过值,但是目前没有值)
  • undefined指没有值(从未赋值)

在非严格模式下,我们可以为全局标识符undefined赋值:

undefined = 2; 

undefined是一个内置标识符,它的值为undefined, 通过void运算符即可得到该值。

void并不改变表达式的结果,只是让表达式不返回值

var foo = "hello";

console.log(void foo);   //undefined

特殊数字

  1. 不是数字

如果数学运算的操作数不是数字类型(或者无法解析为常规的十进制或十六进制数字), 就无法返回一个有效的数字,这种情况下返回值为 NaN

var a = 2019 / "hello";   //NaN

typeof a === "number";   //true

结果出人意料,a的值是NaN,但是a的类型还是numberNaN是一个特殊值,用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。

如何检测一个变量的值是否为NaN,判断有些特殊,例如:

var b = 123 / "abc";

a == NaN;   // false
a === NaN;  // false
NaN === NaN;  // false
NaN != NaN;  //true
isNaN(NaN);  //true

可以看出NaN,它和自身不相等,而NaN!=NaNtrue,可以使用全局(window)工具函数isNaN(..) 来判断一个值是否是NaN。但是这个函数有一个严重的缺陷,例如:

var a = 11 / "ab";
var b = "foo";

window.isNaN(a);  //true
window.isNaN(b);  //true

很明显"foo"不是一个数字,但是它也不是NaN。ES6我们可以使用工具函数Number.isNaN(..)

var a = 11 / "ab";
var b = "foo";

Number.isNaN(a);  //true
Number.isNaN(b);  //false

ES6 之前的浏览器的 polyfill 如下:

if (!Number.isNaN) {
    Number.isNaN = function (n) {
        return (
            typeof n === "number" &&
            window.isNaN(n)
        );
    };
}

if (!Number.isNaN) {
    Number.isNaN = function (n) {
        return n !== n;
    };
}
  1. 无穷数

JavaScript,有一些计算结果出乎意料。

var a = 123 / 0;   //Infinity
var b = -123 / 0;  //-Infinity

ES规范规定:Division of a nonzero finite value by a zero results in a signed infinity. The sign is determined by the rule already stated above.(将零非零有限值除以零会产生有符号无穷大)

由于JavaScript采用IEEE754所以和纯粹的数学运算不同,JavaScript的运算结果有可能溢出,此时结果为 Infinity 或者 -Infinity。例如:

var max = Number.MAX_VALUE;  //1.7976931348623157e+308
var a = max + max; // Infinity
var b = Math.pow( 2, 970 ) + max; // Infinity
var c = Math.pow( 2, 969 ) + max; //1.7976931348623157e+308

这个结果也比较意外。规范规定,如果数学运算(如加法)的结果超出处理范围,则由IEEE 754规范中的“就近取整”模式来决定最后的结果。这里推荐一篇文章介绍了这里的细节(点击)

JavaScript有一个常规的 0(也叫作 +0)和一个 -0。加法和减法运算不会得到负零。

var a = 0 / 123; // 0

var b = 0 * -123; // -0

对负零进行字符串化会返回 "0"

var a = 0 / -123;

a.toString();   // "0"
a + "";         // "0"
String(a);    // "0"

如果反过来将其从字符串转换为数字,得到的结果是准确的:

 +"-0";                 // -0
 Number("-0");          // -0
 JSON.parse("-0");      // -0

负零转换为字符串的结果令人费解:

 var a = 0;
 var b = 0 / -123;

a == b;      //true
-0 == 0;     //true

a === b;     //true
-0 === 0;    //true
0 > -0;      //false    
a > b;      //false

区分 -00

function isPositiveZero(n) {
    n = Number(n);
    return (n === 0) && (1 / n === Infinity);
}

isPositiveZero(-0)   //false
isPositiveZero(0)    //true

参考