一个变量可以存放两种类型的值,基本类型的值(primitive values)和引用类型的值(reference values)。
ES6 引入了一种新的原始数据类型 Symbol
,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:Undefined
、Null
、布尔值(Boolean)
、字符串(String
)、数值(Number)
、对象(Object)
。
基本类型
JavaScript 中共有 6 种基本数据类型:Undefined
、Null
、Boolean
、Number
、String
、Symbol (new in ES 6)
!
约定:基本数据类型与原始数据类型等意。
基本数据类型的值是按值访问的。
-
基本类型的值是不可变的
var str = "123hello321"; str.toUpperCase(); // 123HELLO321 console.log(str); // 123hello321
-
基本类型的比较是它们的值的比较
var a = 1; var b = true; console.log(a == b); // true console.log(a === b); // false
上面 a 和 b 的数据类型不同,但是也可以进行值的比较,这是因为在比较之前,自动进行了数据类型的
隐式转换
。==
: 只进行值的比较===
: 不仅进行值得比较,还要进行数据类型的比较
-
基本类型的变量是存放在栈内存(Stack)里的
var a,b; a = "zyj"; b = a; console.log(a); // zyj console.log(b); // zyj a = "呵呵"; // 改变 a 的值,并不影响 b 的值 console.log(a); // 呵呵 console.log(b); // zyj
图解如下:栈内存中包括了变量的标识符
和变量的值
引用类型
除过上面的 6 种基本数据类型外,剩下的就是引用类型了,统称为 Object 类型。细分的话,有:Object 类型
、Array 类型
、Date 类型
、RegExp 类型
、Function 类型
等。
引用类型的值是按引用访问的。
-
引用类型的值是可变的
var obj = {name:"zyj"}; // 创建一个对象 obj.name = "percy"; // 改变 name 属性的值 obj.age = 21; // 添加 age 属性 obj.giveMeAll = function(){ return this.name + " : " + this.age; }; // 添加 giveMeAll 方法 obj.giveMeAll();
-
引用类型的比较是引用的比较
var obj1 = {}; // 新建一个空对象 obj1 var obj2 = {}; // 新建一个空对象 obj2 console.log(obj1 == obj2); // false console.log(obj1 === obj2); // false
因为 obj1 和 obj2 分别引用的是存放在堆内存中的2个不同的对象,故变量 obj1 和 obj2 的值
(引用地址)
也是不一样的! -
引用类型的值是保存在堆内存(Heap)中的对象(Object)
与其他编程语言不同,JavaScript 不能直接操作对象的内存空间(堆内存)。
var a = {name:"percy"}; var b; b = a; a.name = "zyj"; console.log(b.name); // zyj b.age = 22; console.log(a.age); // 22 var c = { name: "zyj", age: 22 };
图解如下:
-
栈内存中保存了
变量标识
符和指向堆内存中该对象的指针
-
堆内存中保存了
对象的内容
-
检测类型
-
typeof:通常检测 基本类型。
1.对于基本类型,除
null
以外,均可以返回正确的结果。 2.对于引用类型,除function
以外,一律返回object
类型。 3.对于null
,返回object
类型。 4.对于function
返回function
类型。
var a; typeof a; // undefined
a = null; typeof a; // object
a = true; typeof a; // boolean
a = 666; typeof a; // number
a = "hello"; typeof a; // string
a = Symbol(); typeof a; // symbol
a = function(){} typeof a; // function
a = []; typeof a; // object a = {}; typeof a; // object a = /aaa/g; typeof a; // object
- [instanceof](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof):通常检测 **引用类型**,**判断 `A` 是否为 `B` 的实例**
```javascript
({}) instanceof Object // true
([]) instanceof Array // true
(/aa/g) instanceof RegExp // true
(function(){}) instanceof Function // true
-
Object.prototype.toString:通常检测 基本类型 & 引用类型 。 可以准确识别对象值属于哪种内置类型,它比 typeof & instanceof 更加准确。
[[Class]]
是任意内置对象的内部属性,值为一个类型字符串,可以用来判断值的类型。在 JavaScript 代码里,唯一可以访问该属性的方法就是通过
Object.prototype.toString
,通常方法如下:Object.prototype.toString.call(value)
Object.prototype.toString.call(123) // '[object Number]' Object.prototype.toString.call('123') // '[object String]' Object.prototype.toString.call(null) // '[object Null]' Object.prototype.toString.call(undefined) // '[object Undefined]' Object.prototype.toString.call(Math) // '[object Math]' Object.prototype.toString.call({}) // '[object Object]' Object.prototype.toString.call([]) // '[object Array]' ({}).toString() // [object Object]
类型转换
因为 JS 是弱类型语言,所以类型转换发生非常频繁,大部分我们熟悉的 运算 都会先进行类型转换。
幸好的是,实际上大部分类型转换规则是非常简单的,如下表所示:
在这个里面,较为复杂的部分是 Number 和 String 之间的转换,以及对象跟基本类型之间的转换。我们分别来看一看这几种转换的规则。
1. StringToNumber
字符串到数字的类型转换,存在一个语法结构,类型转换支持十进制、二进制、八进制和十六进制,比如:
- 30;// 十进制 => 30
- 0b111;// 二进制 => 7
- 0o13;// 八进制 => 11
- 0xFF;// 十六进制 => 255
- 1e3; // 科学计数法 => 1000
- -1e-2;// 科学计数法 => -0.01
其余类型 => NaN
2. NumberToString
在较小的范围内,数字到字符串的转换是完全符合你直觉的十进制表示。
console.log(1e3 + 'ok'); // 1000ok
3. 装箱转换 (基本类型 => object)
每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。
注意!装箱机制会频繁产生临时对象,在一些对性能要求较高的场景下,我们应该尽量避免对基本类型做装箱转换。
比如下面的number并未发生改变,只会生成numberObj临时对象。
方案一:我们可以利用一个函数的 call 方法来强迫产生装箱。
var number = 123;
var numberObj = (function(){ return this; }).call(number);
numberObj instanceof Number; // true
number instanceof Number; // false
方案二:使用内置的 Object 函数,我们可以在 JavaScript 代码中显式调用装箱能力。
var number = 123;
var numberObj = Object(number);
numberObj instanceof Number; // true
number instanceof Number; // false
4. 拆箱转换 (object => 基本类型)
在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换(即,拆箱转换)。
拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOf 和 toString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。
var o = {
valueOf : () => {console.log("valueOf"); return {}},
toString : () => {console.log("toString"); return {}}
}
o * 2 / o + ''
// valueOf
// toString
// TypeError
可以看到,一般的运算都会先执行了 valueOf,接下来是 toString,最后抛出了一个 TypeError,这就说明了这个拆箱转换失败了。
而到 String 的拆箱转换会优先调用 toString。我们把刚才的运算从 o*2 换成 String(o),那么你会看到调用顺序就变了。
String(o)
// toString
// valueOf
// TypeError
在 ES6 之后,还允许对象通过显式指定 @@toPrimitive Symbol 来覆盖原有的行为。
var o = {
valueOf : () => {console.log("valueOf"); return {}},
toString : () => {console.log("toString"); return {}}
}
o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}
console.log(o + "")
// toPrimitive
// hello
问答时间
-
为什么有的编程规范要求用 void 0 代替 undefined?
因为 JavaScript 的代码 undefined 是一个变量,而并非是一个关键字,这是 JavaScript 语言公认的设计失误之一,所以,我们为了避免无意中被篡改,我建议使用 void 0 来获取 undefined 值。
-
0.1 + 0.2 不是等于 0.3 么?为什么 JavaScript 里不是这样的?
根据浮点数的定义,非整数的 Number 类型无法用 ==(=== 也不行) 来比较:
console.log( 0.1 + 0.2 == 0.3); // false
实际上,这里错误的不是结论,而是比较的方法,正确的比较方法是使用 JavaScript 提供的最小精度值:
console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON); // true
-
为什么给对象添加的方法能用在基本类型上?
. 运算符 提供了装箱操作,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。
Number.prototype.hello = () => console.log('hello'); (123).hello(); // hello
-
判断一个变量是不是数组?
var arr = [1, 2, 3] Array.isArray(arr) arr instanceof Array arr.constructor === Array Object.prototype.toString.call(arr) === '[object Array]'