概述
一个变量可以存放两种类型的值,基本类型的值(primitive values)和引用类型的值(reference values)
基本数据类型值指的是简单的数值段,而引用数据类型值指那些可能由多个值构成的对象
基本类型的特点:
- 基本类型是保存在栈内存(stack)中的
- 基本类型的值是不可变的
- 基本类型的比较是值的比较
引用类型的特点:
- 而引用类型是保存在堆内存中的
- 引用类型的值是可变的
- 引用类型的比较是引用的比较
Javascript 中有 7 种基本数据类型:
- Undefined
- Null
- Boolean
- Number
- String
- BigInt
- Symbol(ECMAScript 2016新增)
除上面的基本数据类型外,剩下的就是引用类型了,统称为 Object
类型。细分的话,有:Object
类型、Array
类型、Date
类型、RegExp
类型、Function
类型等
基本类型
基本类型是保存在栈内存中的,所以可以操作保存在变量中的实际的值
**
假设有以下几个基本类型的变量:
var name = 'jozo';
var city = 'guangzhou';
var age = 22;
那么它的存储结构如下图:
栈区包括了 变量的标识符和变量的值
示例:
var a = 10;
var b = a; b = 20;
console.log(a); // 10
上面示例中,b 获取值是 a 值的一份拷贝,虽然两个变量的值是相等,但是两个变量保存两不同的基本数据类型值。b 只是保存了 a 复制的一个副本,所以当 b 的值改变时,a 的值依然是 10
下图演示了基本数据类型赋值的过程:
基本类型的值是不可变的
var name = "zs";
name.toUpperCase(); // ZS
console.log(name); // zs
会发现原始的name并未发生改变,而是调用了 toUpperCase()
方法后返回的是一个新的字符串
基本类型虽然也可以添加属性和方法,也不会报错,但是添加之后却是无法访问的
var a = 12;
a.name = "myname";
a.method = function() {};
console.log(a.name); // undefined
console.log(a.method); // undefined
基本类型的比较是值的比较,只有在它们的值相等的时候它们才相等
**
var a = 1;
var b = true;
console.log(a == b); // true
console.log(a === b); // false
上面 a 和 b 的数据类型不同,但是也可以进行值的比较,这是因为在 ==
比较之前,自动进行了数据类型的隐式转换
引用类型
引用数据类型是保存在堆内存中的对象,JavaScript 不允许访问内存中的位置,在操作对象时,实际上是在操作对象的引用而不是实际的对象,而这个引用和存储该对象的变量都保存在栈区。通过这个引用地址可以快速查找到保存在堆内存中的对象。但在为对象添加属性时,操作的是实际的对象
*注:在很多语言中,字符串以对象的形式来表示,因此被认为是引用类型。ECMAScript 放弃了这一传统
假设有几个引用类型的变量:
var person1 = {name:'jozo'};
var person2 = {name:'xiaom'};
var person3 = {name:'xiaoq'};
则这三个对象的在内存中保存的情况如下图:
示例:
var obj1 = new Object();
var obj2 = obj1;
obj2.name = "zs";
console.log(obj1.name); // zs
上面示例中,当把 obj1 赋值给 obj2 时,实际上是把 obj1 的引用(堆内存地址)赋值给了 obj2,所以它们同时指向了一个相同的对象。此时操作 obj2 相当于操作 obj1
下图演示了引用数据类型赋值过程:
引用类型的值是可变的
var person = {};//创建个控对象 --引用类型
person.name = 'jozo';
person.age = 22;
person.sayName = function(){console.log(person.name);}
person.sayName();// 'jozo'
delete person.name; //删除person对象的name属性
person.sayName(); // undefined
上面代码说明引用类型可以拥有属性和方法,并且是可以动态改变的
引用类型的比较是引用的比较
var person1 = {};
var person2 = {};
console.log(person1 == person2); // false
person1 和 person2 在堆内存中地址是不同的,所以结果是 false
传递参数
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部,示例:
function addTen(num) {
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
alert(count); //20,没有变化
alert(result); //30
使用引用类型值时:
function setName(obj) {
obj.name = "Nicholas";
}
var person = new Object();
setName(person);
alert(person.name); // "Nicholas"
有很多开发人员错误地认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。为了证明对象是按值传递的,示例:
function setName(obj) {
obj.name = "Nicholas";
// 修改了以下部分
obj = new Object();
obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); // "Nicholas"
实际上,当在函数内部重写 obj 时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁
检测类型
typeof 操作符经常用来检测一个变量是不是基本数据类型
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 运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上
简单说就是判断一个引用类型的变量具体是不是某种类型的对象
({}) instanceof Object // true
([]) instanceof Array // true
(/aa/g) instanceof RegExp // true
(function(){}) instanceof Function // true
如果使用 instanceof 操作符检测基本类型的值,则该操作符始终会返回 false,因为基本类型不是对象。
参考文章
- javascript中基本类型和引用类型的区别分析
- js 基本类型与引用类型的区别
- 《JavaScript高级程序设计(第3版)》
- 原始数据