6

基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值;
引用类型的值是保存在内存中的对象,在操作对象时,实际上是在操作对象的引用而不是实际的对象;

值类型

如果一个变量存储的是值的本身那么就是一个值类型number / string / Boolean / Null / Undefined —值类型的变量本身就是含有赋予给它的数值的,它的变量本身及保存的数据都存储在栈的内存块当中,当声明一个值类型时,必须对它初始化(给变量赋值)才能使用

var num1 = 123,
    num2 = num1;
num1 = 456;
console.log(num2);// 123

将值类型复制给另外一个值时(num2=num1),也就是num2重新再栈上开辟了一块空间,然后将num1中的内容复制一份放在num2中,当改变其中一个变量的值时,不会影响另外一个变量的值

clipboard.png

引用类型

如果一个变量存储的是引用(地址),那么就是一个引用类型object—引用类型的值的存储与值类型不同,它分别存储在内存的堆和栈中,栈中存放的是指向堆中内容的地址,堆中存放的引用类型的地址(键值对)

var obj1 = {name: "xyc"};
var obj2 = obj1;
obj1.name = "lxy";
console.log(obj2.name); // "lxy"

obj2=obj1表示的是将栈上的地址复制一份给另一个对象,他们同时指向堆中的内容,当修改内容时,两个对象中的值都会发生改变

clipboard.png

一个面试题

var o = new Object();
function foo(obj) {
  obj.name = "xyc";
  obj = new Object();
    obj.name = "lxy";
}
foo(o);
console.log(o.name); // ???

图解:
(1)新建对象var o = new Object();

clipboard.png

(2)在foo的环境下执行obj.name = "xyc"
由于是参数传递,在局部作用域内相当于执行了obj = o

clipboard.png

(3)在局部作用域内新建对象,并赋值相同的属性值

obj = new Object();
obj.name = "lxy";

clipboard.png

(4)foo()执行完毕,局部作用域出栈,obj声明周期结束
此时,新建的对象依然存在,等待下一次内存自动回收机制将堆中的无引用对象销毁

clipboard.png

4. 变量的深浅拷贝(重点)

什么是深浅拷贝?在mac电脑中我们可以对某个文件夹创建替身或者复制粘贴某个文件/文件夹,这两种方式都实现了对某个文件/文件夹的拷贝,但是,前者在文件中修改文件内容时,源文件也会修改,而后者的操作在修改文件内容时不会对源文件有影响

上面的例子,“创建替身” ==> 浅拷贝,“复制粘贴” ==> 深拷贝

从内存角度说明:浅拷贝只会在栈内存中开辟空间存放指向源文件的变量,而深拷贝会在堆内存也拷贝文件

深浅拷贝仅是针对于数组和对象而言,不严谨的说法,基本类型的拷贝都属于深拷贝

(1)浅拷贝(最为常见)

var obj1 = {name: "xyc"};
var obj2 = obj1;
obj1.name = "lxy";
console.log(obj2.name); // "lxy"

仅将栈内存复制,堆内存中的指向依然相同,obj2对象改变后,会影响obj1对象

(2)拷贝对象及对象下一层的属性和方法

var shallowCopy = function(obj) {
  // 只拷贝对象
  if (typeof obj !== 'object') return;
  // 根据obj的类型判断是新建一个数组还是对象
  var newObj = obj instanceof Array ? [] : {};
  // 遍历obj,并且判断是obj的属性才拷贝
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}
var obj1 = {
  name: "xyc",
  features: {
    say: "hello",
    eat: "something"
  }
}
var obj2 = shallowCopy(obj1);
obj2.features.eat = "anything";
console.log(obj1.features.eat); // "anything"

console.log(obj1 === obj2); // false
console.log(obj1.features === obj2.features); // true

上面这个例子可以看出上述方式的复制在对象内嵌套对象是不能够实现“类深拷贝”的,下面有一个进阶型的

(3)递归调用对象中嵌套的对象

var deepCopy = function(obj) {
  // 只拷贝对象
  if (typeof obj !== 'object') return;
  // 根据obj的类型判断是新建一个数组还是对象
  var newObj = obj instanceof Array ? [] : {};
  // 遍历obj,并且判断是obj的属性才拷贝
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 当obj中嵌套对象时,再次调用该方法
      newObj[key] = typeof obj[key] !== 'object' ? obj[key] : deepCopy(obj[key]);
    }
  }
  return newObj;
}

(4)一维数组技巧性的拷贝

通过slice()concat()来实现

var arr = ['old', 1, true, null, undefined];

var new_arr = arr.concat();
// var new_arr = arr.slice();

new_arr[0] = 'new';

console.log(arr) // ["old", 1, true, null, undefined]
console.log(new_arr) // ["new", 1, true, null, undefined]

一如上面的对象嵌套,多维数组使用上面的方式拷贝不彻底

(5)粗暴的拷贝方式

通过JSON.parse( JSON.stringify() )实现

var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]

var new_arr = JSON.parse( JSON.stringify(arr) );

console.log(new_arr === arr);// false
console.log(new_arr[3] === arr[3]);// false

但是这种方式无法实现函数的拷贝

var arr = [function(){
    console.log(a)
}, {
    b: function(){
        console.log(b)
    }
}]

var new_arr = JSON.parse(JSON.stringify(arr));

console.log(new_arr);// [null, object]

函数通过这个方式会被转换成null

(6)补全深拷贝

var deepCopy = function(obj){
    var str, newobj = obj.constructor === Array ? [] : {};
    if(typeof obj !== 'object'){
        return;
    } else if(window.JSON){
        str = JSON.stringify(obj), //系列化对象
        newobj = JSON.parse(str); //还原
    } else {
        for(var i in obj){
            newobj[i] = typeof obj[i] === 'object' ? 
            cloneObj(obj[i]) : obj[i]; 
        }
    }
    return newobj;
};

彻底的深拷贝理论上是将对象的整个原型链拷贝(无论原型属性是否为enumerable均应拷贝),遍历的次数越多,性能消耗越大。因此,出于性能的考虑,在拷贝的方式选择上,应该结合具体的业务环境来进行选择

参考:
JavaScript专题之深浅拷贝
深入剖析 JavaScript 的深复制


同梦奇缘
4.1k 声望1.1k 粉丝

生于忧患死于安乐