前端常见面试题(五)赋值&浅拷贝&深拷贝

一、数据类型与堆栈的关系

a、基本类型与引用类型

  • 基本类型:undefined,null,Boolean,String,Number,Symbol

  • 引用类型:Object,Array,Date,Function,RegExp等

b、存储方式

  • 基本类型:基本类型值在内存中占据固定大小,保存在栈内存中(不包含闭包中的变量)
 
  • 引用类型:引用类型的值是对象,保存在堆内存中。而栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址(引用),引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

  

注意:

  1. 闭包中的变量并不保存在栈内存中,而是保存在堆内存中。这一点比较好想,如果闭包中的变量保存在了栈内存中,随着外层中的函数从调用栈中销毁,变量肯定也会被销毁,但是如果保存在了堆内存中,内存函数仍能访问外层已销毁函数中的变量。看一段对应代码理解下:
      
  1. 本篇所讲的浅拷贝和深拷贝都是对于引用类型的,对于基础类型不会有这种操作。

 

二、赋值操作

a、基本数据类型复制

基本数据类型复制配图:

结论:在栈内存中的数据发生数据变化的时候,系统会自动为新的变量分配一个新的之值在栈内存中,两个变量相互独立,互不影响的。

 

b、引用数据类型复制

 

引用数据类型复制配图:

结论:引用类型的复制,同样为新的变量b分配一个新的值,栈中只是一个地址指针。两个变量地址指针相同,指向堆内存中的对象,因此b.x发生改变的时候,a.x也发生了改变。


三、浅拷贝

 

a. 浅拷贝定义

新的对象复制已有对象中非对象属性的值和对象属性的引用。换一种说法,一个新的对象直接拷贝已存在的对象的对象属性的引用,即浅拷贝。

 
如: 
Array.prototype.slice,它可以实现原数组的浅拷贝
 
//第一段代码:

var a = [ 1, 3, 5, { x: 1 } ];
var b = Array.prototype.slice.call(a);
b[0] = 2;
console.log(a); // [ 1, 3, 5, { x: 1 } ];
console.log(b); // [ 2, 3, 5, { x: 1 } ];
//复制代码从输出结果可以看出,浅拷贝后,数组a[0]并不会随着b[0]改变而改变,说明a和b在栈内存中引用地址并不相同。

//第二段代码

var a = [ 1, 3, 5, { x: 1 } ];
var b = Array.prototype.slice.call(a);
b[3].x = 2;
console.log(a); // [ 1, 3, 5, { x: 2 } ];
console.log(b); // [ 1, 3, 5, { x: 2 } ];
//复制代码从输出结果可以看出,浅拷贝后,数组中对象的属性会根据修改而改变,说明浅拷贝的时候拷贝的已存在对象的对象的属性引用。

b. 浅拷贝实例

 1、JSON.parse(JSON.stringify(target, ...sources))

 
    ES6中拷贝对象的方法,接受的第一个参数是拷贝的目标target,剩下的参数是拷贝的源对象sources(可以是多个)
 
let target = {};
let source = {a:'koala',b:{name:'程序员成长指北'}};
Object.assign(target ,source);
console.log(target); // { a: 'koala', b: { name: '程序员成长指北' } }

source.a = 'smallKoala'; source.b.name = '程序员' console.log(source); // { a: 'smallKoala', b: { name: '程序员' } } console.log(target); // { a: 'koala', b: { name: '程序员' } } //从打印结果可以看出,Object.assign是一个浅拷贝,它只是在根属性(对象的第一层级)创建了一个新的对象,但是对于属性的值是对象的话只会拷贝一份相同的内存地址。
Object.assign注意事项

1、只拷贝源对象的自身属性(不拷贝继承属性)
2、它不会拷贝对象不可枚举的属性
3、属性名为Symbol 值的属性,可以被Object.assign拷贝。
4、undefined和null无法转成对象,它们不能作为Object.assign参数,但是可以作为源对象

        

2、 Array.prototype.slice

3、Array.prototype.concat

4、...扩展运算符

C. 自己实现一个浅拷贝

(代码省略...)


四、深拷贝

a、深拷贝定义

深拷贝会另外拷贝一份一个一模一样的对象,从堆内存中开辟一个新的区域存放新对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

b、深拷贝实例

JSON.parse(JSON.stringify())

JSON.stringify()是前端开发过程中比较常用的深拷贝方式。原理是把一个对象序列化成为一个JSON字符串,将对象的内容转换成字符串的形式再保存在磁盘上,再用JSON.parse()反序列化将JSON字符串变成一个新的对象


JSON.stringify()实现深拷贝注意点
1、拷贝的对象的值中如果有函数,undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失 2、无法拷贝不可枚举的属性,无法拷贝对象的原型链 3、拷贝Date引用类型会变成字符串 4、拷贝RegExp引用类型会变成空对象 5、对象中含有NaN、Infinity和-Infinity,则序列化的结果会变成null 6、无法拷贝对象的循环应用(即obj[key] = obj)

C、自己实现一个简单深拷贝

深拷贝,主要用到的思想是递归,遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。

(代码省略...)

d、第三方深拷贝库

该函数库也有提供_.cloneDeep用来做 Deep Copy(lodash是一个不错的第三方开源库,有好多不错的函数,也可以看具体的实现源码)

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false

总结


参考地址:


https://juejin.im/post/5d235d1ef265da1b855c7b5d

https://juejin.im/post/5b10ba336fb9a01e66164346

https://juejin.im/post/59ac1c4ef265da248e75892b

原文地址:https://www.cnblogs.com/catherLee/p/13093037.html