最近刚好有空重温JS基础知识,于是我便把脑海里的内容写了出来。内容准确性应该没什么问题,大部分都是看黄皮书学到的。如果你是一个初学者,你不能指望通过本文寥寥几百字去掌握数据类型这一块内容,最好还是通过红皮书去系统性学习,结合黄皮书去查缺补漏。
黄皮书:《你不知道的JavaScript》
红皮书:《JavaScript高级程序编程》
数据类型
和主流语言一样,JavaScript将类型分为 原始类型 和 引用类型
基本数据类型:
- number
- string
- boolean
- undefined
- null
- symbol
引用数据类型:
- Array
- Object
- Function
- Date
我们注意到null被归类于基本类型,但是有很多人把它当作对象类型,这其实是语言本身的一个bug
对null执行typeof null返回的结果是object,实际上,null本身就是基本类型
万物皆对象
我们常听人说”JavaScript中万物皆对象”,实际上这句话几乎正确,遗憾的是null和undefined并不是对象
null和undefined是两个特殊类型,不是对象这很好理解,那为什么说原始类型是对象呢?
实际上,数据类型里面的门门道道复杂得多,头疼~
除了那两个特殊基本类型,其他基本类型都有对应的特殊包装类型
包装类型
JavaScript中有许多包装类型,它们除了首字母大小写跟基本类型有区别,其他没有什么区别,例如:
- String
- Number
- Boolean
- Array
- Symbol
那么,对象子类型该如何去声明和使用呢?如下所示:
var foo = new String('Hello World!');
var bar = new Number(2.22);
console.log(foo.toString()); // Hello World!
console.log(bar.toFixed(1)); // 2.2
我们看到这里,其实明白了,这些子对象类型就是在原基本类型的基础上去把它封装成了一个对象,对象里内置了许多我们常用的方法
那我们实际开发过程中怎么去选择哪一种去使用呢?
实际上,我们并不需要去刻意的使用new去构造对象子类型,程序会自动帮我们装包、拆包。如下所示:
var foo = 2.2;
foo.toFixed(0); // 这行代码编译器会自动装包、拆包
console.log(foo); // 2
总结,我们在实际开发过程中,尽量使用原始数据类型,不仅从语法上更简洁,性能上包装类型会带来额外的开销
函数是一等公民
前面我们说到,JavaScript除了null和undefined万物皆对象,没错,函数也是一个对象!震惊!
你可以把函数当作一个对象去使用
var foo = function() {
console.log(this.name)
}
foo.name = 'Karl';
foo(); // Karl
但是,我们并不建议在实际开发中去给函数赋值,这并不是一个好的最佳实践
总结,函数就是对象的一个子类型(从技术角度来说就是”可调用的对象”),JavaScript中的函数就是”一等公民”,因为它们本质上和普通的对象一样(只是可以调用),所以可以像操作其他对象一样操作函数(比如当作另一个函数的参数)
堆和栈
简单介绍数据类型后,我们看一下,实际内存里,这些变量是怎么去存储的
堆
堆:没有结构,存放引用类型复杂数据
| 栈区 | 堆 | ||
|---|---|---|---|
| person1 | 堆内存地址 | —> | object1 |
| age | 堆内存地址 | —> | object2 |
栈
栈:有结构,存放基本数据类型变量的值以及指向堆中的数组或者对象的地址
| 标识符 | 值 |
|---|---|
| name | 张三 |
| age | 20 |
栈的特殊性
存在栈种的数据可以共享
var a = 1;
var b = a;
编译器在处理 var a = 1 的时候,首先会在栈内存创建一个标识符为 a 的新的内存空间将值设置为 1,处理 b = a 的时候,首先在栈内存中创建一个标识符为 b 内存空间,然后将 a 的值拷贝过来。
| 标识符 | 值 |
|---|---|
| a | 1 |
| b | 1 |
数据拷贝
理解了引用类型和原始数据类型的存储方式不同后,我们在实际开发过程中,经常会去拷贝变量
对于原始数据类型来说,就是将值直接拷贝过来,而对于引用类型来说,等于号赋值是将对象在堆内存里的指针复制过来了,这并不是我们期望发生的事情
而JavaScript提供了一个官方的方法去解决这个问题,如下所示:
var a = [1, 2, 3];
var b = Object.assign([], a);
但是,问题到这里并没有完全解决,当对象里嵌套了对象,Object.assign 就失效了,泪目…
也就是,到现在为止,JavaScript并没有给我们一个官方的完美解决办法,因为深拷贝要考虑循环引用等一系列奇奇怪怪的问题
所以,我们实际开发中,要么去用loadsh包里的深拷贝方法,或者就是用JSON去序列化对象了
总结
和主流语言一样,JS分为原始数据类型和引用数据类型
在JS中,万物皆对象是不对的,null和undefined就不是
原始数据类型有对应的包装类型,不过我们很少用,一般情况都是用原始数据类型
原始数据类型存栈中,引用数据类型存堆中,在栈中存的是指向堆地址的指针
深拷贝没有好的解决办法,一般用第三方库实现的方法或JSON序列化