基本类型和引用类型
基本类型
undefined, null, boolean, number, string, symbol
引用类型
object
保存方式的不同
- 基本类型的值是按值访问的,可以操作保存在变量中的实际的值。
- 引用类型的值是保存在内存中的对象,实际上在操作对象的引用,而不是实际的对象。
复制变量值不同
基本类型:如果从一个变量向另外一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上,而且它们的值互不影响。
var num1 = 10;
var num2 = num1;
num1 = 20;
console.log(num2); // 没有受到 num1 影响,10
引用类型:当从一个变量复制引用类型的值时,同样也会将储存在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象,复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另外一个变量。
var obj1 = {
name: 'bob'
}
var obj2 = obj1;
obj2.name = 'peter';
console.log(obj1.name); // 受 obj2 影响,输出 peter
传递参数
ECMAScript 中所有函数的参数都是按值传递的。
- 访问变量有按值和按引用两种方式,而参数只能按值传递。
- 在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(命名参数或者arguments对象),里面的值修改了,外部也不会受影响。
- 在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。引用类型传入函数参数时,也是值传递的,即使在内部重新指向了新的对象,但它也不会改变,因为指向的新对象是内部的局部变量。
// 值类型
function add(num) {
return num + 10;
}
var num = 10;
add(num);
console.log(num); // 10
// 引用类型
function getName(obj) {
obj.name = "js";
// 赋值新对象
obj = {};
obj.name = 'css';
//
}
var p = {
name: 'bob'
}
getName(p);
console.log(p.name); // 'js' 没有被函数内部的新对象受印象
实际上,当在函数内部重写 obj 时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。
检测类型
- 检测基本类型:
typeof
- 检测引用类型:
instanceof
typeof null
是返回"object"
typeof 函数
是返回"function"
typeof undefined // "undefined"
typeof 123 // "number"
typeof "str" // "string"
typeof true // "object"
typeof {name: "js"} // "object"
typeof alert // "function"
instanceof
是根据原型链来判断的,检测它的__proto__
是否对应构造函数的 prototype
let arr = [];
console.log(arr instanceof Array) // true
// 实际上上面的代码等同于以下的代码判断
console.log(arr.__proto__ === Array.prototype) // true
但是 instanceof
会有断链的风险,比如“手动”修改了它的“原型链”:
// 声明了 obj 为一个对象,“手动”它的一个属性__proto__指向数组的原型对象
let obj = {__proto__: Array.prototype }
console.log(arr instanceof Array) // 输出是true,但obj明明就是一个对象啊!?
解决方法1:
- 使用
Object.prototype
上的原生toString()
方法判断
console.log(Object.prototype.toString.call(obj) === '[object Array]') // false
console.log(Object.prototype.toString.call(obj) === '[object Object]') // true
解决方法2:
- 使用 ES6 的数组判断方法
- 其实
Array.isArray()
也是根据Object.prototype.toString
方法判断数组的 - 区别就是浏览器兼容性的问题
console.log(Array.isArray(obj)) // false
总结
- 基本类型在内存中占据固定的大小空间,保存在栈内存中。
- 从一个变量向另外一个变量复制基本类型的值,会创建这个值的一个副本。
- 引用类型的值是对象,保存在堆内存中。
- 包含引用类型值的变量实际上包含的并不是对象,而是一个指向对象的指针。
- 从一个变量向另外一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象。
- 确定一个值是那种基本类型可以使用 typeof 操作符,而确定一个值是哪种引用类型可以使用 instanceof 操作符。
参考资料
- JavaScript高级程序设计(第三版), by Nicholas C.Zakas (作者), 李松峰 , 曹力 (译者)