基本数据类型
基本数据类型有6种
null
undefined
string
number
boolean
symbol(ES6新增)
复杂数据类型
复杂数据类型就一种
object
null是对象吗?
先看下MDN上的解释:
值 null 特指对象的值未设置。它是 JavaScript 基本类型 之一。
由此可见,null并不是一个对象,虽然typeof null返回的值为"object"。
《你不知道的JavaScript(上卷)》这本书说过:
null 有时会被当做一种对象类型,但是这其实只是语言本身的一个bug,即对null执行typeof null时会返回字符串"object"。实际上,null本身是基本类型。
原理是这样的,不同的对象在底层都表示为二进制,在JavaScript中二进制前三位都为0的话会被判断为object类型,null的二进制表示是全0,自然前三位也是0,所以执行typeof时会返回“object”。
思考:为什么typeof null会返回"object"而不是"null"?
大家可以看下MDN上的解释:
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null的类型标签也成为了 0,typeof null就错误的返回了"object"。
ECMAScript提出了一个修复(通过opt-in),但被拒绝。这将导致typeof null === 'null'。
基本数据和复杂数据类型有什么区别?
1.声明变量时不同的内存分配
基本数据类型:存储在栈中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。这是因为它们占据的空间是固定的,所以可将它们存储在较小的内存区域-栈。这样存储便于迅速查寻变量的值。
复杂数据类型:存储在堆中的对象,栈中存储的变量的值是一个指针,指向堆中的引用地址。这是因为复杂数据类型的值是会改变的,所以不能把它放在栈中,否则会降低变量查寻的速度。
2.不同的访问机制
在JavaScript中,是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得对象中的值。这就是传说中的按引用访问。
而基本数据类型的值是可以直接访问得到的,即按值访问。
3.复制变量时的不同
基本数据类型:在将保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,不会相互影响,只是它们拥有相同的value而已。
看下面的例子:
var a = 10;
var b = a;
b = 20;
console.log(a); // 10
上面的代码说明:b获取的是a值的一份拷贝,虽然两个变量的值相等,但是两个变量保存了两个不同的基本数据类型,它们之间不会相互影响。
复杂数据类型:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,也就是说两个变量都指向了堆内存中d的同一个对象,它们中任何一个作出的改变都会反映在另一个身上。(这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象的指针罢了)
看下面的例子:
var a = {
name: 'dazhi'
}
var b = a;
b.name = 'dazhi_fe';
console.log(a.name); // "dazhi_fe"
上面的代码说明:a和b都指向了同一个堆中的对象,所以对其中一个作出改变,另一个也会跟着改变。
4.参数传递的不同
首先我们应该明确一点:ECMAScript中所有函数的参数都是按值来传递的。这也是我们容易疑惑的地方,因为访问变量有按值和按引用两种方式,而参数只能按值传递。这一点等下我们举例来说明。
基本数据类型:拷贝的是值
看例子:
function addTen(num) {
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
console.log(count); // 20,没有变化
console.log(result); // 30
只是把变量的值传递给参数,之后参数和这个变量互不影响。
复杂数据类型:拷贝的是引用地址
看例子:
function setName(obj) {
obj.name = "Nicholas";
}
var person = {
name: 'jack'
}
setName(person);
console.log(person.name); // "Nicholas"
以上代码创建了一个对象,并将其保存在了变量person中。然后这个变量被传递到setName()函数之中就被复制给了oobj。在这个函数内部,ojb和person引用的是同一个对象。于是在函数内部修改了name属性后,函数外部的person也会有所反映。所以我们会错误的认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。
为了证明对象是按值传递的,我们再来看一个例子:
var obj1 = {
value: '111'
}
var obj2 = {
value: '222'
}
function changeStuff(obj) {
obj.value = '333';
obj = obj2;
return obj.value;
}
var foo = changeStuff(obj1);
console.log(foo); // 222 参数obj指向了新的对象obj2
console.log(obj1.value); // 333
obj1仍然指向原来的对象,之所以value改变了,是因为changeStuff里的第一条语句,这个时候obj是指向obj1的;如果是按引用传递的话,这个时候obj1.value应该是等于'222'的。
实际上,在函数内部重写obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即销毁。
参考: