数据类型
ps : 本人学习笔记
类型
- 空值( null )
- 未定义( undefined )
- 布尔值( boolean )
- 数字( number )
- 字符串( string )
- 对象( object )
- 符号( symbol , ES6 中新增
注意null
typeof null === "object"; // true
检测null类型
var a = null;
(!a && typeof a === "object"); // true
function
很多人搞不清函数和Object的关系,查阅规范可以知道,它实际上是 object 的一个 “ 子类型 ” 。具体来说,函数是 “ 可调用对象 ” ,它有一个内部属性 [[Call]] ,该属性使其可以被调用。函数不仅是对象,还可以拥有属性。
function fn(a,b){}
console.log(fn.length)// 2
函数对象的 length 属性是其声明的参数的个数
数组也属于object的一个子类型
数组
数组通过数字进行索引,但有趣的是它们也是对象,所以也可以包含字符串键值和属性(但这些并不计算在数组长度内)
var a = []
a[0] = 1;
a['asda'] = 'asd'
console.log(a.length) //1
如果字符串键值能够被强制类型转换为十进制数字的话,它就会被当作数字索引来处理
var a = [ ];
a["13"] = 42;
a.length; // 14
类数组
一些DOM操作返回的集合 就是类数组
通过arguments 对象(类数组)将函数的参数当作列表来访问(从 ES6 开始已废止)。
function foo() {
var arr = Array.prototype.slice.call(arguments);
arr.push("bam");
console.log(arr);
}
foo("bar", "baz"); // ["bar","baz","bam"]
ES6 中的内置工具函数 Array.from(..) 也能实现同样的功能
var arr = Array.from( arguments );
字符串
字符串和数组的确很相似,它们都是类数组,都有 length 属性以及 indexOf(..) (从 ES5 开始数组支持此方法)和concat(..) 方法:
var a = "foo";
var b = ["f","o","o"];
a.length; // 3
b.length; // 3
a.indexOf( "o" ); // 1
b.indexOf( "o" ); // 1
var c = a.concat( "bar" ); // "foobar"
var d = b.concat( ["b","a","r"] ); // ["f","o","o","b","a","r"]
a === c; // false
b === d; // false
a; // "foo"
b; // ["f","o","o"]
a[1] = "O";
b[1] = "O";
a; // "foo"
b; // ["f","O","o"]
JavaScript 中字符串是不可变的,而数组是可变的。并且 a[1] 在 JavaScript 中并非总是合法语法,在老版本的 IE 中就不被允许(现在可以了)。 正确 的方法应该是 a.charAt(1) 。
var a = 'foo'
a[0] = 'h'
console.log(a[0]) // f
许多数组函数用来处理字符串很方便。虽然字符串没有这些函数,但可以通过 “ 借用 ” 数组的非变更方法来处理字符串
数字
特别大和特别小的数字默认用指数格式显示,与 toExponential() 函数的输出结果相同
var a = 5E10;
a; // 50000000000
a.toExponential(); // "5e+10"
var b = a * a;
b; // 2.5e+21
var c = 1 / a;
c; // 2e-11
由于数字值可以使用 Number 对象进行封装(参见第 3 章),因此数字值可以调用 Number.prototype 中的方法。例如, tofixed(..) 方法可指定小数部分的显示位数
var a = 42.59;
a.toFixed( 0 ); // "43"
a.toFixed( 1 ); // "42.6"
a.toFixed( 2 ); // "42.59"
a.toFixed( 3 ); // "42.590"
a.toFixed( 4 ); // "42.5900"
toPrecision(..) 方法用来指定 有效数位 的显示位数
var a = 42.59;
a.toPrecision( 1 ); // "4e+1"
a.toPrecision( 2 ); // "43"
a.toPrecision( 3 ); // "42.6"
a.toPrecision( 4 ); // "42.59"
a.toPrecision( 5 ); // "42.590"
a.toPrecision( 6 ); // "42.5900"
数字常量还可以用其他格式来表示,如二进制、八进制和十六进制。
当前的 JavaScript 版本都支持这些格式:
0xf3; // 243 的十六进制
0Xf3; // 同上
0363; // 243 的八进制
计算问题
二进制浮点数中的 0.1 和 0.2 并不是十分精确,它们相加的结果并非刚好等于 0.3 ,而是一个比较接近的数字0.30000000000000004
整数检测
要检测一个值是否是整数,可以使用 ES6 中的 Number.isInteger(..) 方法
要检测一个值是否是 安全的整数 ,可以使用 ES6 中的 Number.isSafeInteger(..) 方法
void 运算符
undefined 是一个内置标识符(除非被重新定义,见前面的介绍),它的值为 undefined ,通过 void 运算符即可得到该值。
var a = 42;
console.log( void a, a ); // undefined 42
特殊的数字
不是数字的数字 NAN
NAN : 不是数字 将它理解为 “ 无效数值 ”“ 失败数值 ” 或者 “ 坏数值 ” 可能更准确些
var a = 2 / "foo";
isNaN( a ); // true
很明显 "foo" 不是一个数字 ,但是它也不是 NaN 。这个 bug 自 JavaScript 问世以来就一直存在,至今已超过 19 年
从 ES6 开始我们可以使用工具函数 Number.isNaN(..)
无穷数
var a = 1 / 0; // Infinity
var b = -1 / 0; // -Infinity
特殊等式
如前所述, NaN 和 -0 在相等比较时的表现有些特别。由于 NaN 和自身不相等,所以必须使用 ES6 中的Number.isNaN(..) (或者 polyfill )。而 -0 等于 0 (对于 === 也是如此),因此我们必须使用isNegZero(..) 这样的工具函数。
ES6 中新加入了一个工具方法 Object.is(..) 来判断两个值是否绝对相等,可以用来处理上述所有的特殊情况
var a = 2 / "foo";
var b = -3 * 0;
Object.is( a, NaN ); // true NaN 只和自身不相等
Object.is( b, -0 ); // true
Object.is( b, 0 ); // false
基本类型和引用类型
基本类型 : 总是 通过值复制的方式来赋值 / 传递,包括 null 、 undefined 、字符串、数字、布尔和 ES6 中的 symbol 。
引用类型 : 当复制保存着对象的某个变量时,操作的是对象的引用,但在为对象添加属性时,操作的是实际的对象。
在复制变量值时,基本类型会在变量对象上创建一个新值,再复制给新变量。此后,两个变量的任何操作都不会影响到对方;而引用类型是将存储在变量对象的值复制一份给新变量,但是两个变量的值都指向存储在堆中的一个对象,也就是说,其实他们引用了同一个对象,改变其中一个变量就会影响到另一个变量。
//基本类型值
var a = 'a';
var b = a;
a = 'b';
console.log(b); //a
//引用类型值,以数组为例
//1.对其中一个变量直接赋值不会影响到另一个变量(并未操作引用的对象)
var a = [1,2,2,6,4]
var b = a;
a = [4,5,6,7]
console.log(b) //[ 1, 2, 2, 6, 4 ]
//2.使用push(操作了引用的对象)
var a = [1,2,3];
var b = a;
a.push(4);
console.log(a);//1,2,3,4
console.log(b); //1,2,3,4
对象参数传递
var a = [1,2,3,4]
function pusha(arr) {
arr.push(5)
}
pusha(a)
console.log(a)
a的值会被传递到pusha里,此时传的是a的地址,pusha执行的时候其实是在操作a。
内部属性 [[class]]
所有 typeof 返回值为 "object" 的对象(如数组)都包含一个内部属性 [[Class]]
一般通过 Object.prototype.toString(..) 来查看
Object.prototype.toString.call( [1,2,3] ); // "[object Array]"
Object.prototype.toString.call( /regex-literal/i ); // "[object RegExp]"
封装对象包装
封装对象( object wrapper )扮演着十分重要的角色。由于基本类型值没有 .length 和 .toString() 这样的属性和方法,需要通过封装对象才能访问,此时 JavaScript 会自动为基本类型值 包装一个封装对象
var a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"
Object.prototype.toString.call( a ); // "[object String]"
js引擎会自动帮我们封装,在我们使用这些属性的时候,所以我们不必提前的将他们对象化,否则会降低执行效率。
封装对象释疑
使用封装对象时有些地方需要特别注意。比如 Boolean :
var a = new Boolean(false);
if (!a) {
console.log("Oops"); // 执行不到这里
}
我们为 false 创建了一个封装对象,然而该对象是真值( “truthy” ,即总是返回 true),所以这里使用封装对象得到的结果和使用 false 截然相反。如果想要自行封装基本类型值,可以使用 Object(..) 函数(不带 new 关键字):
var a = "abc";
var b = new String( a );
var c = Object( a );
typeof a; // "string"
typeof b; // "object"
typeof c; // "object"
b instanceof String; // true
c instanceof String; // true
Object.prototype.toString.call( b ); // "[object String]"
Object.prototype.toString.call( c ); // "[object String]"
拆封
如果想要得到封装对象中的基本类型值,可以使用 valueOf() 函数
var a = new String( "abc" );
var b = new Number( 42 );
var c = new Boolean( true );
a.valueOf(); // "abc"
b.valueOf(); // 42
c.valueOf(); // true