JS 数据类型、赋值、深拷贝和浅拷贝

js 数据类型

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

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

var fn = new Function (‘a‘, ‘b‘, ‘return a + b‘)

typeof fn // function

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

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

基本数据类型

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

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

var a = ‘string‘
a[0] = ‘a‘
console.log(a)  // string

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

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

引用数据类型

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

var person1 = {name:‘jozo‘};
var person2 = {name:‘xiaom‘};
var person3 = {name:‘xiaoq‘};

引用类型的值是可变的:

person1[‘name‘] = ‘muwoo‘

console.log(person1) // {name: ‘muwoo‘}

传值与传址

了解了基本数据类型与引用类型的区别之后,我们就应该能明白传值与传址的区别了。

在我们进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中。例如:

var a = 10;
var b = a;

a ++ ;
console.log(a); // 11
console.log(b); // 10

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

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

var a = {}; // a保存了一个空对象的实例
var b = a;  // a和b都指向了这个空对象

a.name = ‘jozo‘;
console.log(a.name); // ‘jozo‘
console.log(b.name); // ‘jozo‘

b.age = 22;
console.log(b.age);// 22
console.log(a.age);// 22

console.log(a == b);// true

浅拷贝

先来看一段代码的执行:

var obj = {a: 1, b: {c: 2}}
var obj1 = obj
var obj2 = shallowCopy(obj);
function shallowCopy(src) {
    var dst = {};
     for (var prop in src) {
         if (src.hasOwnProperty(prop)) {
             dst[prop] = src[prop];
          }
      }
     return dst;
}

var obj3 = Object.assign({}, obj)

obj.a = 2
obj.b.c = 3

console.log(obj) // {a: 2, b: {c: 3}}
console.log(obj1) // {a: 2, b: {c: 3}}
console.log(obj2) // {a: 1, b: {c: 3}}
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来为其分配另一个存储地址,这样可以解决内存地址指向同一个的问题:

var obj = {a: {b: 1}}
var copy = JSON.parse(JSON.stringify(obj))

obj.a.b = 2
console.log(obj) // {a: {b: 2}}
console.log(copy) // {a: {b: 1}}

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

var target = {
    a: 1,
    b: 2,
    hello: function() {
            console.log("Hello, world!");
    }
};
var copy = JSON.parse(JSON.stringify(target));
console.log(copy);   // {a: 1, b: 2}
console.log(JSON.stringify(target)); // "{"a":1,"b":2}"

2. 遍历实现属性复制

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

function extend (source) {
  var target
  if (typeof source === ‘object‘) {
    target = Array.isArray(source) ? [] : {}
    for (var key in source) {
      if (source.hasOwnProperty(key)) {
        if (typeof source[key] !== ‘object‘) {
          target[key] = source[key]
        } else {
          target[key] = extend(source[key])
        }
      }
    }
  } else {
    target = source
  }
  return target
}

var obj1 = {a: {b: 1}}
var cpObj1 = extend(obj1)
obj1.a.b = 2
console.log(cpObj1) // {a: {b: 1}}

var obj2 = [[1]]
var cpObj2 = extend(obj2)
obj2[0][0] = 2
console.log(cpObj2) // [[1]]

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

    // 内部方法:用户合并一个或多个对象到第一个对象
    // 参数:
    // target 目标对象  对象都合并到target里
    // source 合并对象
    // deep 是否执行深度合并
    function extend(target, source, deep) {
        for (key in source)
            if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
                // source[key] 是对象,而 target[key] 不是对象, 则 target[key] = {} 初始化一下,否则递归会出错的
                if (isPlainObject(source[key]) && !isPlainObject(target[key]))
                    target[key] = {}

                // source[key] 是数组,而 target[key] 不是数组,则 target[key] = [] 初始化一下,否则递归会出错的
                if (isArray(source[key]) && !isArray(target[key]))
                    target[key] = []
                // 执行递归
                extend(target[key], source[key], deep)
            }
            // 不满足以上条件,说明 source[key] 是一般的值类型,直接赋值给 target 就是了
            else if (source[key] !== undefined) target[key] = source[key]
    }

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

后记

更多前端日记请参考这里:

Vue 源码解读

axios 源码解读

JS 高级部分

参考资料

js 深拷贝 vs 浅拷贝

原文地址:https://www.cnblogs.com/tiedaweishao/p/9042641.html

时间: 2024-10-12 12:49:19

JS 数据类型、赋值、深拷贝和浅拷贝的相关文章

浅谈js中的深拷贝和浅拷贝

1. 如果是基本数据类型(String,Number,Boolean,Null,undefined),名字和值都会储存在栈内存中.栈内存中的数据每一次赋值都会产生一个新的内存,每条数据之间不相互影响, var a = 1; b = a; // 栈内存会开辟一个新的内存空间,此时b和a都是相互独立的 b = 2; console.log(a); // 1 2. 如果是引用数据类型(Object),名字存在栈内存中,值存在堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值. 例如:var a=

javascript实现引用数据类型的深拷贝和浅拷贝详解

关于引用类型值的详解,请看另一篇随笔 https://www.cnblogs.com/jinbang/p/10346584.html 深拷贝和浅拷贝,也就是引用数据类型栈和堆的知识点.深浅拷贝的原型都是Object,深拷贝指向的栈内存不一样,浅拷贝指向的栈内存一样): 如何区分深拷贝与浅拷贝,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B没有发生变化,说明是深拷贝.如果B也跟着发生了变化,说明是浅拷贝. let obj = { name: "jin", arr: [&quo

JS的 new 原理,call原理,基本数据类型,引用数据类型,深拷贝,浅拷贝

在用构造函数 new 出来一个对象时的思考,以及逐步解决理清思路的过程. https://blog.csdn.net/zhouziyu2011/article/details/60143385  new 的分步详情 https://blog.csdn.net/qq_28978893/article/details/79272422   深浅拷贝 https://blog.csdn.net/zhouziyu2011/article/list/1?t=1 原文地址:https://www.cnblo

js 中的 深拷贝与浅拷贝

js在平时的项目中,赋值操作是最多的:比如说: 1 var person1 = { 2 name:"张三", 3 age:18, 4 sex:"male", 5 height:180, 6 weight:14012 } 13 var person2 = person1; 14 console.log(person2) 15 person2.name = "李四"; 16 console.log(person1,person2); 这段代码,con

js中的深拷贝和浅拷贝

深复制和浅复制只针对像 Object, Array 这样的复杂对象的.简单来说,浅复制只复制一层对象的属性,而深复制则递归复制了所有层级. 深浅拷贝 的主要区别就是:复制的是引用(地址)还是复制的是实例. 所谓 深浅拷贝: 对于仅仅是复制了引用(地址),换句话说,复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响,为 浅拷贝. 而如果是在堆中重新分配内存,拥有不同的地址,但是值是一样的,复制后的对象与原来的对象是完全隔离,互不影响,为 深拷贝. 下面是一个简单的浅复制实现:

js 中的深拷贝和浅拷贝

 Shallow copy && Deep copy 对于字符串类型,浅复制是对值的复制,对于对象来说,浅复制是对对象地址的复制,并没 有开辟新的栈,也就是复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变, 举个栗子 var arr = [1,2,3]; var arrCopy = arr; arrCopy[0] = 100; // 修改新数组的第0个值: arr[0]===arrCopy[0]; //true 原来的数组四不四也变了 而很多数情况下我

js中的深拷贝与浅拷贝

对于字符串类型,浅拷贝是对值的拷贝,对于对象来说,浅拷贝是对对象地址的拷贝,并没有开辟新的栈,也就是拷贝的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变,而深拷贝则是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性. 深拷贝的实现方法: b = JSON.parse( JSON.stringify(a) ) 这样做的局限性: 无法复制函数 原型链没了,对象就是object,所属的类没了. 另: jquery.extend()也

js的命名空间 && 单体模式 && 变量深拷贝和浅拷贝 && 页面弹窗设计

说在前面:这是我近期开发或者看书遇到的一些点,觉得还是蛮重要的. 一.为你的 JavaScript 对象提供命名空间 <!DOCTYPE html> <html> <head> <title>为自己的js对象提供命名空间</title> </head> <body> <div>封装自己的数据和函数,防止和其他的库搞混了</div> <script> var jscbObject = {

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

一.堆内存与栈内存 堆和栈都是内存中划分出来的用来存储的区域,栈为自动分配的内存空间,它由系统自动释放,堆为动态分配的内存,大小不定也不会自动释放. 二.js基本数据类型与引用类型的不同 基本数据类型(boolean,undefined,null,string,number) 1.基本数据类型存放在栈内存中 是存放在栈中的简单数据段,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问. 2.基本数据类型值不可变 js中给基本类型赋值或操作基本类型数据时,并没有改变基本类型的原