0%

原始值与引用值及确定类型

原始值和引用值

ECMAScript变量可以包含两种不同类型的数据:原始值和引用值,原始值就是最简单的数据:Undefined,Null,Boolean,Number,String,Symbol,保存原始值的变量是按值访问的,因为我们操作的就是存储在变量中的实际值。

引用值是保存在内存中的对象,JavaScript不允许直接访问内存位置,操作对象时,实际上操作的对该对象的引用,而非实际的对象本身,因此,保存引用值的变量是按引用访问的。

区别

动态属性

引用值可以随时添加,修改删除属性。

而原始值不能有属性,原始值的初始化只使用原始字面量形式,如果使用new关键字则JavaScript会创建一个Object类型的实例,

1
2
3
4
5
6
7
8
let name1="Nicholas";
let name2=new String("Matt");
name1.age=27;
name2.age=26;
console.log(name1.age);//undefined
console.log(name2.age);//26
console.log(typeof name1);//string
console.log(typeof name2);//object

复制值

通过变量把一个原始值赋值到另一个变量时,原始值会被复制得到新变量的位置。

1
2
let num1=5;
let num2=num1;

num1和num2相互独立,num2是num1的副本

当把引用值从一个变量赋值给另一个变量时,存储在变量中的值也会被复制到新变量所在位置,区别在于:这里复制的值时一个指针,它指向存储在堆内存中的对象。两个变量实际上指向同一个对象。

1
2
3
4
let obj1=new Object();
let obj2=obj1;
obj1.name="Nic";
console.log(obj2.name);//Nic

传递参数

ECMAScript中所有函数的参数都是按值传递,这意味着函数外的值会被复制到函数内部参数中,就像一个变量赋值到另一个变量一样,如果是原始值,那么就跟原始值变量的复制一样,如果是引用值就和引用值的变量复制一样。

1
2
3
4
5
6
7
8
9
function addTen(num){//count的值被复制到参数num,函数内部num+10但不会影响函数外部的原始变量count
num+=10;
return num;

}
let count=20;
let res=addTen(count);
console.log(count);//20,没有变化
console.log(res);//30

如果是对象:

1
2
3
4
5
6
7
8
function setName(obj){
obj.name="Nic"//此时多了一个obj指针指向person,将name改为Nic
obj=new Object();//又创建一个指针,指向内存其他位置
obj.name="Greg";//函数调用结束,obj被销毁
}
let person=new Object();
setName(person);
console.log(person.name);//'Nic'

typeof

typeof用来判断一个变量是否为原始类型,即判断一个变量是否为字符串,数字,布尔值或undefined的最好方式,值为对象null,那么typeof返回Object

typeof虽然对原始值很有用,但是对引用值用处不大,我们通常关心一个值是不是对象,而是想知道它是什么类型的对象。

instanceof

如果变量是给定引用类型的实例,则instanceof操作符返回true

1
console.log(person instanceof Object);//变量person是Object吗

按照定义所有引用值都是Object的实例,因此通过instanceof检测的任何引用值和Object构造函数都会返回true。如果,使用instanceof检测原始值,则始终会返回false,因为原始值不是对象。

对象标识和相等判定

ES6之前:

1
2
console.log(+0===-0);//true
cosole.log(NAN===NAN);//false

ES6中的object.is()与===很像,但同时考虑了上述边界情形,这个方法接收两个参数:

1
2
3
4
console.log(Object.is(true,1));//false
console.log(Object.is({},{}));//false
console.log(Object.is(+0,-0));//false
console.log(Object.is(NAN,NAN));//true

检查超过两个值是否相等

1
2
3
4
function recursiveEqual(x,...rest){
return Object.is(x,rest[0])||(rest.length<2||recursiveEqual(...rest));

}

constructor:

constructor两个作用,

  • 判断数据的类型
  • 对象实例通过prototype对象上的constructor访问它的构造函数,因此如果改变prototype对象,constructor就不能用来判断数据类型
1
2
3
4
5
6
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
1
2
3
4
5
6
7
8
function Fn(){};

Fn.prototype = new Array();

var f = new Fn();

console.log(f.constructor===Fn); // false
console.log(f.constructor===Array); // true

Object.prototype.toString.call()

Object.prototype.toString.call()使用Object对象的原型方法toString来判断数据类型

1
2
3
4
5
6
7
8
9
10
var a = Object.prototype.toString;

console.log(a.call(2));
console.log(a.call(true));
console.log(a.call('str'));
console.log(a.call([]));
console.log(a.call(function(){}));
console.log(a.call({}));
console.log(a.call(undefined));
console.log(a.call(null));

注意:同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是因为toString是Object的原型方法,而Array,function等类型作为Object的实例,都重写了toString()方法,不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString()方法,(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。