前言

笔者最近整理了一些前端技术文章,如果有兴趣可以参考这里:muwoo blogs。接下来我们进入正片:

js 数据类型

  1. 六种 基本数据类型:
  • Boolean. 布尔值,true 和 false.
  • null. 一个表明 null 值的特殊关键字。 JavaScript 是大小写敏感的,因此 null 与 Null、NULL或其他变量完全不同。
  • undefined. 变量未定义时的属性。
  • Number. 表示数字,例如: 42 或者 3.14159。
  • String. 表示字符串,例如:"Howdy"
  • Symbol ( 在 ECMAScript 6 中新添加的类型).。一种数据类型,它的实例是唯一且不可改变的。
  1. 以及 Object 对象引用数据类型

大多数情况下,我们可以通过typeof属性来判断。只不过有一些例外,比如:

  1. var fn = new Function ('a', 'b', 'return a + b')
  2. typeof fn // function

关于function属不属于js的数据类型,这里也有相关的讨论JavaScript 里 Function 也算一种基本类型?

基本类型 和 引用数据类型 的相关区别

基本数据类型

我们来看一下 MDN 中对基本数据类型的一些定义:

除 Object 以外的所有类型都是不可变的(值本身无法被改变)。例如,与 C 语言不同,JavaScript 中字符串是不可变的(译注:如,JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。我们称这些类型的值为“原始值”。

  1. var a = 'string'
  2. a[0] = 'a'
  3. console.log(a) // string

我们通常情况下都是对一个变量重新赋值,而不是改变基本数据类型的值。在 js 中是没有方法是可以改变布尔值和数字的。倒是有很多操作字符串的方法,但是这些方法都是返回一个新的字符串,并没有改变其原有的数据。比如:

  • 获取一个字符串的子串可通过选择个别字母或者使用 String.substr().
  • 两个字符串的连接使用连接操作符 (+) 或者 String.concat().

引用数据类型

引用类型(object)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配,例如。

  1. var person1 = {name:'jozo'};
  2. var person2 = {name:'xiaom'};
  3. var person3 = {name:'xiaoq'};

引用类型的值是可变的:

  1. person1['name'] = 'muwoo'
  2. console.log(person1) // {name: 'muwoo'}

传值与传址

了解了基本数据类型与引用类型的区别之后,我们就应该能明白传值与传址的区别了。
在我们进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中。例如:

  1. var a = 10;
  2. var b = a;
  3. a ++ ;
  4. console.log(a); // 11
  5. console.log(b); // 10

所以说,基本类型的赋值的两个变量是两个独立相互不影响的变量。

但是引用类型的赋值是传址。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。例如:

  1. var a = {}; // a保存了一个空对象的实例
  2. var b = a; // a和b都指向了这个空对象
  3. a.name = 'jozo';
  4. console.log(a.name); // 'jozo'
  5. console.log(b.name); // 'jozo'
  6. b.age = 22;
  7. console.log(b.age);// 22
  8. console.log(a.age);// 22
  9. console.log(a == b);// true

浅拷贝

先来看一段代码的执行:

  1. var obj = {a: 1, b: {c: 2}}
  2. var obj1 = obj
  3. var obj2 = shallowCopy(obj);
  4. function shallowCopy(src) {
  5. var dst = {};
  6. for (var prop in src) {
  7. if (src.hasOwnProperty(prop)) {
  8. dst[prop] = src[prop];
  9. }
  10. }
  11. return dst;
  12. }
  13. var obj3 = Object.assign({}, obj)
  14. obj.a = 2
  15. obj.b.c = 3
  16. console.log(obj) // {a: 2, b: {c: 3}}
  17. console.log(obj1) // {a: 2, b: {c: 3}}
  18. console.log(obj2) // {a: 1, b: {c: 3}}
  19. console.log(obj3) // {a: 1, b: {c: 3}}

这段代码可以说明赋值得到的对象 obj1 只是将指针改变,其引用的仍然是同一个对象,而浅拷贝得到的的 obj2 则是重新创建了新对象。但是,如果原对象obj中存在另一个对象,则不会对对象做另一次拷贝,而是只复制其变量对象的地址。这是因为浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据。
对于数组,更长见的浅拷贝方法便是slice(0)concat()
ES6 比较常见的浅拷贝方法便是 Object.assign

深拷贝

通过上面的这些说明,相信你对深拷贝大致了解了是怎样一个东西了:深拷贝是对对象以及对象的所有子对象进行拷贝。那么如何实现这样一个深拷贝呢?

1. JSON.parse(JSON.stringify(obj))

对于常规的对象,我们可以通过JSON.stringify来讲对象转成一个字符串,然后在用JSON.parse来为其分配另一个存储地址,这样可以解决内存地址指向同一个的问题:

  1. var obj = {a: {b: 1}}
  2. var copy = JSON.parse(JSON.stringify(obj))
  3. obj.a.b = 2
  4. console.log(obj) // {a: {b: 2}}
  5. console.log(copy) // {a: {b: 1}}

但是 JSON.parse()JSON.stringify也存在一个问题,JSON.parse() 和J SON.stringify()能正确处理的对象只有Number、String、Array等能够被 json 表示的数据结构,因此函数这种不能被 json 表示的类型将不能被正确处理。

  1. var target = {
  2. a: 1,
  3. b: 2,
  4. hello: function() {
  5. console.log("Hello, world!");
  6. }
  7. };
  8. var copy = JSON.parse(JSON.stringify(target));
  9. console.log(copy); // {a: 1, b: 2}
  10. console.log(JSON.stringify(target)); // "{"a":1,"b":2}"

2. 遍历实现属性复制

既然浅拷贝只能实现非object第一层属性的复制,那么遇到object只需要通过递归实现浅拷贝其中内部的属性即可:

  1. function extend (source) {
  2. var target
  3. if (typeof source === 'object') {
  4. target = Array.isArray(source) ? [] : {}
  5. for (var key in source) {
  6. if (source.hasOwnProperty(key)) {
  7. if (typeof source[key] !== 'object') {
  8. target[key] = source[key]
  9. } else {
  10. target[key] = extend(source[key])
  11. }
  12. }
  13. }
  14. } else {
  15. target = source
  16. }
  17. return target
  18. }
  19. var obj1 = {a: {b: 1}}
  20. var cpObj1 = extend(obj1)
  21. obj1.a.b = 2
  22. console.log(cpObj1) // {a: {b: 1}}
  23. var obj2 = [[1]]
  24. var cpObj2 = extend(obj2)
  25. obj2[0][0] = 2
  26. console.log(cpObj2) // [[1]]

我们再来看一下 Zepto 中深拷贝的代码:

  1. // 内部方法:用户合并一个或多个对象到第一个对象
  2. // 参数:
  3. // target 目标对象 对象都合并到target里
  4. // source 合并对象
  5. // deep 是否执行深度合并
  6. function extend(target, source, deep) {
  7. for (key in source)
  8. if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
  9. // source[key] 是对象,而 target[key] 不是对象, 则 target[key] = {} 初始化一下,否则递归会出错的
  10. if (isPlainObject(source[key]) && !isPlainObject(target[key]))
  11. target[key] = {}
  12. // source[key] 是数组,而 target[key] 不是数组,则 target[key] = [] 初始化一下,否则递归会出错的
  13. if (isArray(source[key]) && !isArray(target[key]))
  14. target[key] = []
  15. // 执行递归
  16. extend(target[key], source[key], deep)
  17. }
  18. // 不满足以上条件,说明 source[key] 是一般的值类型,直接赋值给 target 就是了
  19. else if (source[key] !== undefined) target[key] = source[key]
  20. }

内部实现其实也是差不多。

参考资料

js 深拷贝 vs 浅拷贝

一篇文章理解JS数据类型、深拷贝和浅拷贝的更多相关文章

  1. 一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends

    说实在话,以前我只需要知道"寄生组合继承"是最好的,有个祖传代码模版用就行.最近因为一些事情,几个星期以来一直心心念念想整理出来.本文以<JavaScript高级程序设计&g ...

  2. 从JS的深拷贝与浅拷贝到jq的$.extend()方法

    一.堆内存与栈内存 堆和栈都是内存中划分出来的用来存储的区域,栈为自动分配的内存空间,它由系统自动释放,堆为动态分配的内存,大小不定也不会自动释放. 二.js基本数据类型与引用类型的不同 基本数据类型 ...

  3. 【js】深拷贝和浅拷贝区别,以及实现深拷贝的方式

    一.区别:简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,如果B没变,那就是深拷贝. 此篇文章中也会简单阐述到栈堆,基本数据类型与引用数据类型,因为这 ...

  4. js引用类型深拷贝、浅拷贝方法封装

    引用类型的深拷贝.浅拷贝在前端领域一直是个很重要的知识点,不仅在业务中频繁使用,也是面试官们喜欢考的的知识点之一.本篇将封装引用类型的深拷贝.浅拷贝方法,并解决在封装过程中出现的问题. 一.浅拷贝 浅 ...

  5. JS实现深拷贝,浅拷贝的方法

    在 JS 中,函数和对象都是浅拷贝(地址引用):其他的,例如布尔值.数字等基础数据类型都是深拷贝(值引用). 深拷贝 JSON.parse(JSON.stringify(src)):这种方法有局限性, ...

  6. js的深拷贝和浅拷贝

    一.数组的深浅拷贝 在使用JavaScript对数组进行操作的时候,我们经常需要将数组进行备份,事实证明如果只是简单的将它赋予其他变量,那么我们只要更改其中的任何一个,然后其他的也会跟着改变,这就导致 ...

  7. js实现深拷贝和浅拷贝

    浅拷贝: 思路----------把父对象的属性,全部拷贝给子对象,实现继承. 问题---------如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,不会开辟新栈,不是 ...

  8. js对象深拷贝与浅拷贝

    浅拷贝 把a赋值给b,a与b指向相同的内存,修改b值,a也会跟着改变. var a = "aa"; var b = a; b = "bb"; 这个时候a也变成了 ...

  9. 基础知识《十二》一篇文章理解Cookie和Session

    理解Cookie和Session机制 会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话.常用的会话跟踪技术是Cookie与Session.Cookie通过在客户端记录信息确定 ...

随机推荐

  1. 【zabbix】zabbix 高可用架构的实现

    https://www.jianshu.com/p/249d47b089b4?utm_campaign=maleskine&utm_content=note&utm_medium=se ...

  2. leetcode-mid-Linked list-2 Add Two Numbers

    mycode 87.22% # Definition for singly-linked list. # class ListNode(object): # def __init__(self, x) ...

  3. 【洛谷P1983 车站分级】

    这题好像是个蓝题.(不过也确实差不多QwQ)用到了拓扑排序的知识 我们看这些这车站,沿途停过的车站一定比未停的车站的级别高 所以,未停靠的车站向已经停靠的车站连一条边,入度为0的车站级别就看做1 然后 ...

  4. Python笔记(二十二)_魔法方法_基本魔法方法

    __init__(self[,...]) __init__和__new__组成python的构造器,但__init__更多的是负责初始化操作,相当于一个项目中的配置文件,__new__才是真正的构造函 ...

  5. 【ABAP系列】SAP ABAP中将字符格式的金额转换为数值的函数

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列]SAP ABAP中将字符格式的金 ...

  6. 链表两数相加(add two numbers)

    问题 给出两个 非空 的链表用来表示两个非负的整数.其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字. 如果,我们将这两个数相加起来,则会返回一个新的链表来表示它 ...

  7. 压缩图片工具类,压缩100KB以内拿走直接用

    最近遇到自拍上传图片过大问题,很烦恼,所以自己写了一个压缩图片的工具类使用,自测效果很不错,可以压缩到KB以内,像素还可以分辨清晰 下面Java代码奉上: import lombok.extern.s ...

  8. Python内建函数enumerate()用法及在for循环应用

    Python 内建函数enumerate() 由于这个单纯很长,不容易记住,用法还是比较广泛的,下面讲述Python内建函数enumerate()用法. 1,实例 enumerate(sequence ...

  9. Jquery实例链接

    jquery学习笔记 jquery实现全选,反选,取消的操作 左侧菜单收缩的实现(包括,筛选器,addclass.removeclass.绑定事件,链式编程) 模态对话框实现增加删除表格里面的内容 j ...

  10. 通过利用immutability的能力编写更安全和更整洁的代码

    通过利用immutability的能力编写更安全和更整洁的代码 原文:Write safer and cleaner code by leveraging the power of "Imm ...