原生 JS 数据类型, 存储以及拷贝
1, 类型
值类型 (基本类型): 字符串 (String), 数字 (Number), 布尔 (Boolean), 对空 (Null), 未定义 (Undefined),Symbol(ES6 引入)
- const a =null; // null
- const b = undefined; // undefined
- const c = 123; // number
- const d = `hello world ` // string
- const e = true; // boolean
引用数据类型: 对象 (Object), 数组 (Array), 函数 (Function).
- const obj = {
- name:'jack'
- };// Object
- const arr = [1,2,3]; // Array
- const fn = function(){
- console.log('这是一个函数')
- } // Function
2, 存储
基本数据类型存储在栈中, 占用空间较小;
复杂数据类型存储在堆中, 占用空间较大;
引用数据类型都是保存在堆内存中的, 然后再在栈内存中保存一个对堆内存中实际对象都引用, 所以 JS 中对引用数据类型的操作都是操作对象的引用, 而不是操作实际对象.
3, 拷贝
基本数据类型拷贝的是栈中的地址, 拷贝后的变量跟被拷贝变量的值的改变互不影响;
复杂数据类型, 又叫引用类型, 它的拷贝区分浅拷贝以及深拷贝: 浅拷贝拷贝的是引用的地址, 也就是只是拷贝了对象在堆中存在的指针, 改变了浅拷贝后的对象以及原对象, 改变的对应属性的值都会互相影响; 而深拷贝就是为了区分以及改变这一问题而来的. 深拷贝后的对象数据改变, 跟原对象数据改变互不影响;
4, 拷贝方式
浅拷贝只需要直接将原变量赋值给另外一个变量;
const a = 123; const b = a;a = 456;console.log(a,b); //456 123
深拷贝:
- const obj = {
- a:123
- };
- const copyA = obj;
- const copyB = deepClone(obj);
- obj.a = 456;
- console.log(copyA, copyB); // {
- a:456
- } {
- a:123
- }
看上面的输出, 就很容易知道为啥要实现深拷贝了.
下面看实现深拷贝思路:
既然要实现深拷贝, 我们首先要知道变量的数据类型, 在 JS 中获取数据类型, 我们通常会使用 typeof 变量来获取, 但是这在获取引用类型的时候, 会发现返回的都是 Object, 在 MDN 中找了一遍, 终于找到这样一段描述
- // 使用 toString() 检测对象类型
- // 可以通过 toString() 来获取每个对象的类型. 为了每个对象都能通过 //Object.prototype.toString() 来检测, 需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用, 传递要检查的对象作为第一个参数, 称为 thisArg.
- var toString = Object.prototype.toString;
- toString.call(new Date); // [object Date]
- toString.call(new String); // [object String]
- toString.call(Math); // [object Math]
- //Since JavaScript 1.8.5
- toString.call(undefined); // [object Undefined]
- toString.call(null); // [object Null]
所以, 利用
Object.prototype.toSting()
方法来检测对象类型, 通过
Object.prototype.toSting.call(obj)
可以返回对象类型的描述, 看下面代码.
- function getType(obj) {
- //tostring 会返回对应不同的标签的构造函数
- var toString = Object.prototype.toString;
- var map = {
- '[object Boolean]': 'boolean',
- '[object Number]': 'number',
- '[object String]': 'string',
- '[object Function]': 'function',
- '[object Array]': 'array',
- '[object Date]': 'date',
- '[object RegExp]': 'regExp',
- '[object Undefined]': 'undefined',
- '[object Null]': 'null',
- '[object Object]': 'object'
- };
- if (obj instanceof Element) {
- return 'element';
- }
- return map[toString.call(obj)];
- }
通过这个函数, 我们可以获取变量的最终类型.
获取类型后, 我们就可以实现最终深拷贝函数了
- function deepClone(data) {
- var type = getType(data);
- var obj;
- if (type === 'array') {
- obj = [];
- } else if (type === 'object') {
- obj = {};
- } else {
- // 不再具有下一层次
- return data;
- }
- if (type === 'array') {
- for (var I = 0, len = data.length; I < len; i++) {
- obj.push(deepClone(data[i]));
- }
- } else if (type === 'object') {
- for (var key in data) {
- obj[key] = deepClone(data[key]);
- }
- }
- return obj;
- }
思路是: 在深拷贝函数中, 先获取数据类型, 并且声明一个变量, 开辟新的内存空间, 然后根据不同的类型赋值给内部的新对象: 如果是简单类型, 直接返回该数据, 完成拷贝; 如果不是, 针对 Object 和 Array 递归获取对应的值或者键值对并复制给内建变量, 最终完成深拷贝.
当然, 在 ES5 + 中实现深拷贝还是有别的方式, 比如常用的 JSON.parse(JSON.stringify(obj)); 和... 扩展运算符, Object.assign() 都可以实现简单的深拷贝, 但是都会有一定的缺陷, 至于缺陷是啥, 就要大家自己去发现了, 本文不做深入讨论.
最后总结一下基础数据类型和引用数据类型的区别:
堆比栈大, 栈比堆速度快.
基础数据类型比较稳定, 而且相对来说占用的内存小.
引用数据类型大小是动态的, 而且是无限的.
堆内存是无序存储, 可以根据引用直接获取.
来源: http://www.jianshu.com/p/f8249a9f02d5