“ 本文正在参加 「金石计划」 ”
JS的数据类型有哪些?
注意:没有数组,函数,日期这些,这些都是类,不是类型
数据类型如何进行区分?
因为ECMAScript的类型是松散的(所以其实太自由也不好,类型不安全,后来引入了TypeScript),所以需要一种手段来确定任意变量的数据类型 这个时候就出现了操作符typeof,以下是变量输出typeof的判断依据
- ”undefined“ 表示未定义;
- ”boolean“表示布尔值;
- ”string“表示字符串;
- ”number“表示数值;
”object“表示值为对象(而不是函数)或者null
”function“表示值为函数
- ”symbol“表示值为符号
其中比较特殊的已经标志出来了
问题一
:为什么null明明是一个简单数据类型,但使用typeof来判断的时候会输出object呢?
这是因为特殊值null会被认为是一个对空对象的引用 (其实这也是null和undefined的区别,我们一般用null来初始化一个对象)
一般来说不会显式的给某个变量赋值undefined,它在ECMA-262第三版之前是不存在的,增加这个特殊值的目的就是为了正式明确空对象指针(null)和未初始变量的区别——js红宝书
问题二
:为什么”function“表示值为函数
严格意义上来说,函数在ECMAScript中被认为是对象,并不代表一种数据类型,可是,函数也有自己特殊的属性,为此,就有必要通过typeof操作符来区分函数和其他对象——js红宝书
(typeof belike:既然你这么特殊,我就给你个单独的名份function吧)
这两种数据类型有什么区别?
首先原始值
和引用值
的定义方式很类似,都是创建一个变量然后赋值
但是在赋值后,能对这个变量做什么就很不同了
第一个区别:动态属性
对引用值
而言可以随时添加,修改和删除属性和方法
原始值
不能有属性,即使尝试给原始值添加属性也不会报错
总结:只有引用值可以动态添加后面可以使用的属性(其实和他们存储方式是有关联的~)
let person = new Object();
person.name = "fyyy";
console.log(person.name); //"fyyy"
let person = "fyyy";
person.age = 18;
console.log(person.age) //undefined
第二个区别:复制值
原始值复制值
原始值
在复制值的时候是重新在栈区申请了一块空间,变量之间互不干扰
let num1 = 5;
let num2 = num1;
引用值复制值
let object1 = new Object();
let Object2 = object1; //其实c++中相当于变量取别名地址传递的感觉
new之后发生了什么?至少会在堆内存中开辟一块内存对吧,也就是说new之后返回了一个操作这块儿内存的指针(我们暂时称为temp),但我们知道在js中不像cpp能直接操作对象所在的内存空间,所以这块个temp是对应保存在object1所在的栈区的,我们就通过object1间接去按引用访问,而非实际的对象本身temp
那如果我想实现和原始值一样的赋值,都有一个单独的空间,互不打扰,怎么办呢?深拷贝和浅拷贝
,这个地方obj1和obj都指向同一块堆内存就是浅拷贝
而深拷贝会重新开辟一块堆内存给ojb2
第三个区别:传递参数
变量有按值和按引用访问,但是在ECMAScript中传参只有按值传递,为什么这么说呢?
我们来看下面一段代码:
在传递原始值
的时候,如果是按照引用传递那么count就会变成30,所以原始值是按值传递进行传参的这没什么疑问
function addTen(num)
{
num += 10;
return num;
}
let count = 20;
let result = addTen(count);
console.log(count);//还是20 并不会被影响
console.log(result);//30
但是在传递引用值
的时候呢?
function setName(obj)
{
obj.name = "fyyyy";
}
let person = new Object(); //创建对象
setName(person);//把这个对象传给setName ,并被复制到参数obj中,这个时候obj和person指向同一个对象
console.log(person.name) //"fyyyy"
结果就是即使是按照值传递的方式把person放入了函数,obj也会通过引用访问对象,使得person身上有了name的属性并且值为fyyyy,那我们传递引用值就是按照引用传递的吗?
不是的,还是刚才提到的,在js中,传参只会按值传递
那是什么原因使得person添加了name呢
这是因为obj指向的对象person保存在全局作用域的栈内存上,而不是因为引用值就是按照引用传递
如果引用值是按照引用传递,那么下面这段代码
function setName(obj)
{
obj.name = "fyyyy";
obj = new Object();//obj指向的对象改变了
obj.name = "dkkkk";//局部变量 用完就销毁了
}
let person = new Object(); //创建对象
setName(person);//把这个对象传给setName ,并被复制到参数obj中,这个时候obj和person指向同一个对象
console.log(person.name) //还是输出"fyyyy"
第四个区别:确定类型
typeof
操作符最适合用来判断一个变量是否为原始值,(上面已经提到特殊的两个就是object和null用typeof判断的时候都是object)
typeof
对引用值的用处不是很大,因为我们通常关心的不是这个值是不是对象,而是这个对象是什么类型的对象,这个时候就需要instanceof
XXX instanceof 引用类型
最适合用来判断一个变量是否为引用值
(引用类型由原型链决定,可以参考我之前写的对象的原型与原型链那篇文章: 对象的原型与原型链 )
console.log(xiaoming instanceof Person) //true
console.log(xiaoming instanceof Object) //true
按照定义 所有引用值都是Object的实例(js一切皆对象说法的来源),如果用instanceof
来检测原始值则会始终输出false
最后总结
原始值与引用值的区别就是:
- 存储位置不同:原始值存储在栈内存中,引用值存储在堆内存中。
- 传递方式不同:原始值通过值传递的方式进行传递,引用值通过引用传递的方式进行传递。(深浅拷贝)
- 比较方式不同:原始值通过值的比较进行比较,引用值通过引用的比较进行比较。(引用值的比较就是看是不是指向同一块堆内存)
- 原始值不可变:原始值在被创建后无法修改,只能重新赋值。而引用值可以修改其属性和方法。(动态属性)