众所周知javaScript是一门“极像java的语言”,既然与java那么像,甚至可以说就是套用了java的语法,我在上篇文章《javaScript的基本语法大全》中也列出了很多例子,许多语法和java的一模一样。然而,比较java主要作用于后台开发,而javaScript主要作用于前端开发,其应用场景不一致,所面临的问题也不一样,这也就导致了javaScript的发展历史中又衍生出了许多属于自己的个性。
目前JavaScript 的数据类型,共有七种。分别是:
- 数值(number):整数和小数。
- 字符串(string):文本(比如
“
Hello World”
)。 - 布尔值(boolean):表示真伪的两个特殊值,即
true
(真)和false
(假)。 null
:表示空值。undefined
:表示“未定义”或不存在,常用于区分“未申明”的变量;- 对象(object):各种值组成的集合,包括了数组等复合型数据集合。
- Symbol :该数据类型是ES6 新增的。
判断数据类型
虽然javaScript定义了这7种数据类型,但是申明时都是统一用var表示的,所以无法区分变量具体是什么类型的。而实际开发中有时候却需要判断数据类型,所以javaScript也提供了判断数据类型的方法。以下是几种判断方法
typeof
运算符 ,返回一个值的数据类型。instanceof
运算符,返回一个值的数据类型,并且可以区分数组和普通对象。Object.prototype.toString
方法。
a.typeof
运算符
typeof 123 // "number"
typeof '123' // "string"
typeof false // "boolean"
typeof undefined // "undefined"
function f() {}
typeof f // "function"
上面可以看出typeof运算符不仅可以判定基本数据类型,还可以判定函数。利用这写特性,可用于判定一个值是否是有效值,从而避免报错。
// 错误的写法
if (v) {
// ...
}
// 报错:ReferenceError: v is not defined
// 正确的写法
if (typeof v === "undefined") {
// ...
}
b.instanceof
运算符
instanceof
运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
var v = new Vehicle();
v instanceof Vehicle // true
instanceof
运算符的左边是实例对象,右边是构造函数。它会检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上。因此,下面两种写法是等价的。
v instanceof Vehicle
// 等同于
Vehicle.prototype.isPrototypeOf(v)
由于instanceof
检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true
。
var d = new Date();
d instanceof Date // true
d instanceof Object // true
上面代码中,d
同时是Date
和Object
的实例,因此对这两个构造函数都返回true
。你可以这么理解Date也是一个对象,也属性Object类型。
instanceof
的原理是检查右边构造函数的prototype
属性,是否在左边对象的原型链上。有一种特殊情况,就是左边对象的原型链上,只有null
对象。这时,instanceof
判断会失真。
var obj = Object.create(null);
typeof obj // "object"
Object.create(null) instanceof Object // false
上面代码中,Object.create(null)
返回一个新对象obj
,它的原型是null
(Object.create
的详细介绍见本平台后续文章)。右边的构造函数Object
的prototype
属性,不在左边的原型链上,因此instanceof
就认为obj
不是Object
的实例。但是,只要一个对象的原型不是null
,instanceof
运算符的判断就不会失真。
因为instanceof
是通过构造函数来比较的,因此它可以区分数组和对象。
var o = {};
var a = [];
o instanceof Array // false
a instanceof Array // true
注意,instanceof
运算符只能用于对象,不适用原始类型的值。
var s = 'hello';
s instanceof String // false
c.Object.prototype.toString
方法。
Object.prototype.toString
能够打印出变量或者数据类型,并且能返回这个类型。
var obj = {};
obj.toString() //"[object Object]"
Object.prototype.toString(obj) //"[object Object]"
var test = Object.prototype.toString(obj)
test // "[object Object]"
下面我为大家逐一介绍以上的七种数据类型:
1.数值
JavaScript 内部,所有数字都是以64位浮点数形式储存,所以JavaScript 语言的底层是没有整数的。需要注意的是某些运算只有整数才能完成,此时 JavaScript 会自动把64位浮点数,先转成32位整数,然后再参与运算。
由于浮点数存在不精确的问题,所以在javaScript小数的比较和运算时会存在误差,这点需要特别小心留意。
0.1 + 0.2 === 0.3
// false
0.3 / 0.1
// 2.9999999999999996
(0.3 - 0.2) === (0.2 - 0.1)
// false
1.1数值精度问题:
根据国际标准 IEEE 754,JavaScript 浮点数的64个二进制位,精度最多只能到53个二进制位,这意味着,绝对值小于2的53次方的整数,即-9007199254740992到9007199254740992,都可以精确表示。超过这个范围就无法精确表示了。
Math.pow(2, 53) // 9007199254740992
Math.pow(2, 53) + 1 // 9007199254740992
Math.pow(2, 53) + 2 // 9007199254740994
Math.pow(2, 53) + 3 // 9007199254740996
Math.pow(2, 53) + 4 // 9007199254740996
上面代码中,大于2的53次方以后,整数运算的结果开始出现错误。所以,大于2的53次方的数值,都无法保持精度。由于2的53次方是一个16位的十进制数值,所以简单的法则就是,JavaScript 对15位的十进制数都可以精确处理。
Math.pow(2, 53)
// 9007199254740992
// 多出的三个有效数字,将无法保存
9007199254740992111
// 9007199254740992000
上面示例表明,大于2的53次方以后,多出来的有效数字(最后三位的111
)都会无法保存,变成0。
1.2数值范围
根据国际标准所定制的二进制规则, JavaScript 能够表示的数值范围为正负2的1024次方(开区间),超出这个范围的数无法表示。如果一个数大于等于2的1024次方,那么就会发生“正向溢出”,即 JavaScript 无法表示这么大的数,这时就会返回Infinity
。
Math.pow(2, 1024) // Infinity
如果一个数小于等于2的-1075次方(指数部分最小值-1023,再加上小数部分的52位),那么就会发生为“负向溢出”,即 JavaScript 无法表示这么小的数,这时会直接返回0。
Math.pow(2, -1075) // 0
JavaScript 提供Number
对象的MAX_VALUE
和MIN_VALUE
属性,返回可以表示的具体的最大值和最小值。
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324(小数点后的0>5个, 就自动转为科学计数法)
1.3数据的全局方法:
- parseInt():将字符串转为整数,如果参数不是字符串会先转成字符串再转为整数,
parseInt
方法还可以接受第二个参数(2到 36之间),表示被解析的值的进制,返回该值对应的十进制数。parseInt
的第二个参数默认为10,即默认是十进制转十进制。 parseFloat
():将一个字符串转为浮点数。isNaN():
判断一个值是否为NaN
- isFinite():返回一个布尔值,表示某个值是否为正常的数值。
2.字符串
字符串就是零个或多个排在一起的字符,放在单引号或双引号之中。字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始),也可以使用length属性。
var s = 'hello';
s[0] // "h"
s[1] // "e"
s[4] // "o"
// 直接对字符串使用方括号运算符
'hello'[1] // "e"
s.length //5
s.length = 3;
s.length //5
从上面可以看出,字符串取坐标跟数组一样,也能使用length得出长度,但是不可以改变它的长度值。通过这个特征便可以使用for循环遍历字符串了。
3.null 空值
null表示不存在,与java中的null如出一辙,本身就是引用了java中null的规则。
var a = null;
a //null
根据C语言传传统规则,null在参与数字运算时可以转为0。
Number(null) // 0
5 + null // 5
我们知道在java中null可能是情况是调用方法传参数的时候传的值是空值,也可能是申明了值,却并未赋值,因此null可以表示为一个空对象。但还有一种情况,就是并非申明的值,在java中对未申明的值编译的时候就会报错,但如果是创建了一个空对象就不会报错。如何去区分这两种不同的情况呢,况且null还可以转成0,可以回干扰误导计算。这时候就需要用到“undefined”了。
4.undefined 未定义
undefined和null
一样都可以表示“没有”,含义非常相似。将一个变量赋值为undefined
或null
,老实说,语法效果几乎没区别,几乎相等。
var a = undefined;
// 或者
var a = null;
null == undefined //true
null === undefined //false
从上面可以看出null与undefined相等,但用===比较又不相等,说明他们值相等,但类型不一致。
既然含义与用法都差不多,为什么要同时设置两个这样的数据类型,这不是无端增加复杂度,令初学者困扰吗?说到这里这就与历史原因有关了。
1995年 JavaScript 诞生时,最初像 Java 一样,只设置了null
表示"无"。但是,JavaScript 的设计者 Brendan Eich,觉得这样做还不够。其次,那时的 JavaScript 不包括错误处理机制,Brendan Eich 觉得,如果null
自动转为0,很不容易发现错误。
因此,他又设计了一个undefined
。区别是这样的:null
是一个表示“空”的对象,转为数值时为0
;undefined
是一个表示"此处无定义"的原始值,转为数值时为NaN
。
Number(undefined) // NaN
5 + undefined // NaN
用法和含义
对于null
和undefined
,大致可以像下面这样理解。
null
表示空值,即该处的值现在为空。调用函数时,某个参数未设置任何值,这时就可以传入null
,表示该参数为空。比如,某个函数接受引擎抛出的错误作为参数,如果运行过程中未出错,那么这个参数就会传入null
,表示未发生错误。
undefined
表示“未定义”,下面是返回undefined
的典型场景。
// 变量声明了,但没有赋值
var i;
i // undefined
// 调用函数时,应该提供的参数没有提供,该参数等于 undefined
function f(x) {
return x;
}
f() // undefined
// 对象没有赋值的属性
var o = new Object();
o.p // undefined
// 函数没有返回值时,默认返回 undefined
function f() {}
f() // undefined
5.布尔值 Boolean
布尔值代表“真”和“假”两个状态。“真”用关键字true
表示,“假”用关键字false
表示。布尔值只有这两个值。
下列运算符会返回布尔值:
- 前置逻辑运算符:
!
(Not) - 相等运算符:
===
,!==
,==
,!=
- 比较运算符:
>
,>=
,<
,<=
- 并且,或运算:&&,|| (这两个运算符一般搭配前面的运算符一起参与运算)
如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为false
,其他值都视为true
。
undefined
null
false
0
NaN
""
或''
(空字符串)
在实际开发中可利用值转布尔值的规则,结合三元运算符一起使用。例如:当值为空串时显示空,不为空串时再判断类型,取对象中的属性。
var x = {};
function add(x){
return !x:''? x.p!='' : x.p ? x ;
}
上面的代码中,函数add接收一个参数x,如果x是'',那么!x就会转成 true,就会显示'',如果不是空串,就会继续运行后面的三元运算符。
注意,空数组([]
)和空对象({}
)对应的布尔值,都是true
。
if ([]) {
console.log('true');
}
// true
if ({}) {
console.log('true');
}
// true
6.对象Object
对象(object)是 JavaScript 语言的核心概念,也是最重要的数据类型。对象是一种无序的复合数据集合,通过“键值对”(key-value)的来表达具体的数据类型。
var obj = {
foo: 'Hello',
bar: 'World'
};
上面代码中,大括号就定义了一个对象,它被赋值给变量obj
,所以变量obj
就指向一个对象。该对象内部包含两个键值对(又称为两个“成员”),第一个键值对是foo: 'Hello'
,其中foo
是“键名”(成员的名称),字符串Hello
是“键值”(成员的值)。键名与键值之间用冒号分隔。第二个键值对是bar: 'World'
,bar
是键名,World
是键值。两个键值对之间用逗号分隔。
注意:对象的键名只能是字符串,默认就是字符串,所以申明的时候可以省略引号(如果键名保护不符合规范的字符还是需要加引号的),而值可以是任意的数据类型,甚至是函数都可以。
对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。
var obj = {
p: function (x) {
return 2 * x;
}
};
obj.p(1) // 2
6.1属性的操作
6.1.1.读取:
读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。
var obj = {
p: 'Hello World'
};
obj.p // "Hello World"
obj['p'] // "Hello World"
注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理。
6.1.2.属性的赋值:
点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。
var obj = {};
obj.foo = 'Hello';
obj['bar'] = 'World';
上面代码中,分别使用点运算符和方括号运算符,对属性赋值。
JavaScript 允许属性的“后绑定”,也就是说,你可以在任意时刻新增属性,没必要在定义对象的时候,就定义好属性。
6.1.3.属性的查询与遍历
查看一个对象本身的所有属性,可以使用Object.keys
方法,或者for in循环
var obj = {
key1: 1,
key2: 2
};
Object.keys(obj);
// ['key1', 'key2']
for...in
循环用来遍历一个对象的全部属性。
var obj = {a: 1, b: 2, c: 3};
for (var i in obj) {
console.log('键名:', i);
console.log('键值:', obj[i]);
}
// 键名: a
// 键值: 1
// 键名: b
// 键值: 2
// 键名: c
// 键值: 3
for...in
循环有两个使用注意点。
- 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
- 它不仅遍历对象自身的属性,还遍历继承的属性,但如果改属性设置为了不可遍历,就不会被遍历到。
举例来说,对象都继承了toString
属性,但是for...in
循环不会遍历到这个属性。
var obj = {};
// toString 属性是存在的
obj.toString // toString() { [native code] }
for (var p in obj) {
console.log(p);
} // 没有任何输出
如果继承的属性是可遍历的,那么就会被for...in
循环遍历到。但是,一般情况下,都是只想遍历对象自身的属性,所以使用for...in
的时候,应该结合使用hasOwnProperty
方法,在循环内部判断一下,某个属性是否为对象自身的属性。
var person = { name: '老张' };
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// name
6.1.4.属性的删除:delete 命令
delete
命令用于删除对象的属性,删除成功后返回true
。
var obj = { p: 1 };
Object.keys(obj) // ["p"]
delete obj.p // true
obj.p // undefined
Object.keys(obj) // []
上面代码中,delete
命令删除对象obj
的p
属性。删除后,再读取p
属性就会返回undefined
,而且Object.keys
方法的返回值也不再包括该属性。
注意,删除一个不存在的属性,delete
不报错,而且返回true
。
var obj = {};
delete obj.p // true
上面代码中,对象obj
并没有p
属性,但是delete
命令照样返回true
。因此,不能根据delete
命令的结果,认定某个属性是存在的。
只有一种情况,delete
命令会返回false
,那就是该属性存在,且不得删除。
var obj = Object.defineProperty({}, 'p', {
value: 123,
configurable: false
});
obj.p // 123
delete obj.p // false
上面代码之中,对象obj
的p
属性是不能删除的,所以delete
命令返回false
。
另外,需要注意的是,delete
命令只能删除对象本身的属性,无法删除继承的属性。
var obj = {};
delete obj.toString // true
obj.toString // function toString() { [native code] }
上面代码中,toString
是对象obj
继承的属性,虽然delete
命令返回true
,但该属性并没有被删除,依然存在。这个例子还说明,即使delete
返回true
,该属性依然可能读取到值。
6.1.5.属性是否存在:in 运算符
in
运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true
,否则返回false
。它的左边是一个字符串,表示属性名,右边是一个对象。
var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true
in
运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。就像上面代码中,对象obj
本身并没有toString
属性,但是in
运算符会返回true
,因为这个属性是继承的。
这时,可以使用对象的hasOwnProperty
方法判断一下,是否为对象自身的属性。
var obj = {};
if ('toString' in obj) {
console.log(obj.hasOwnProperty('toString')) // false
}
6.2数组
数组属于一种特殊的对象。typeof
运算符会返回数组的类型是object。
数组可以和普通对象一样除了在定义时赋值,也可以和对象一样先定义后赋值。同样的任何类型的数据,都可以放入数组。普通对象所具有的特性,数组都具备。
数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。
var arr = ['a', 'b', 'c'];
上面代码中的a
、b
、c
就构成一个数组,两端的方括号是数组的标志。a
是0号位置,b
是1号位置,c
是2号位置。
var arr = [
{a: 1},
[1, 2, 3],
function() {return true;}
];
arr[0] // Object {a: 1}
arr[1] // [1, 2, 3]
arr[2] // function (){return true;}
上面数组arr
的3个成员依次是对象、数组、函数。
如果数组的元素还是数组,就形成了多维数组。
var a = [[1, 2], [3, 4]];
a[0][1] // 2
a[1][1] // 4
数组同样也有length属性,也可以使用in判断里面是否存在值,几乎普通对象有的方法都可以作用于数组,这里就不一一举例了。
7.Symbol
7.1.定义:ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。
7.2.Symbol值通过Symbol
函数生成。对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
7.3.注意,Symbol
函数前不能使用new
命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
7.4.Symbol
函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。Symbol值作为对象属性名时,不能用点运算符。在对象的内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中。
7.5、Symbol实例:消除魔术字符串
魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,该由含义清晰的变量代替。
function getArea(shape, options) {
var area = 0;
switch (shape) {
case 'Triangle': // 魔术字符串
area = .5 * options.width * options.height;
break;
/* ... more code ... */
}
return area;
}
getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串
上面代码中,字符串“Triangle”就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。
常用的消除魔术字符串的方法,就是把它写成一个变量。
var shapeType = {
triangle: 'Triangle'
};
function getArea(shape, options) {
var area = 0;
switch (shape) {
case shapeType.triangle:
area = .5 * options.width * options.height;
break;
}
return area;
}
getArea(shapeType.triangle, { width: 100, height: 100 });
上面代码中,我们把“Triangle”写成shapeType
对象的triangle
属性,这样就消除了强耦合。
如果仔细分析,可以发现shapeType.triangle
等于哪个值并不重要,只要确保不会跟其他shapeType
属性的值冲突即可。因此,这里就很适合改用Symbol值。
const shapeType = {
triangle: Symbol()
};
上面代码中,除了将shapeType.triangle
的值设为一个Symbol,其他地方都不用修改。
摘自http://es6.ruanyifeng.com/#docs/symbol
7.6、有一个Object.getOwnPropertySymbols
方法,可以获取指定对象的所有 Symbol 属性名。
Object.getOwnPropertySymbols
方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
另一个新的API,Reflect.ownKeys
方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
7.7、有时,我们希望重新使用同一个Symbol值,Symbol.for
方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。
Symbol.keyFor
方法返回一个已登记的 Symbol 类型值的key
。