《javascript高級程序設計》筆記:值類型與引用類型

基本數據類型是按值訪問的,由於能夠操做保存在變量中的實際的值;
引用類型的值是保存在內存中的對象,在操做對象時,其實是在操做對象的引用而不是實際的對象;javascript

值類型

若是一個變量存儲的是值的自己那麼就是一個值類型number / string / Boolean / Null / Undefined —值類型的變量自己就是含有賦予給它的數值的,它的變量自己及保存的數據都存儲在棧的內存塊當中,當聲明一個值類型時,必須對它初始化(給變量賦值)才能使用java

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

將值類型複製給另一個值時(num2=num1),也就是num2從新再棧上開闢了一塊空間,而後將num1中的內容複製一份放在num2中,當改變其中一個變量的值時,不會影響另一個變量的值面試

clipboard.png

引用類型

若是一個變量存儲的是引用(地址),那麼就是一個引用類型object—引用類型的值的存儲與值類型不一樣,它分別存儲在內存的堆和棧中,棧中存放的是指向堆中內容的地址,堆中存放的引用類型的地址(鍵值對)segmentfault

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 = opost

clipboard.png

(3)在局部做用域內新建對象,並賦值相同的屬性值性能

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

clipboard.png

(4)foo()執行完畢,局部做用域出棧,obj聲明週期結束
此時,新建的對象依然存在,等待下一次內存自動回收機制將堆中的無引用對象銷燬spa

clipboard.png

4. 變量的深淺拷貝(重點)

什麼是深淺拷貝?在mac電腦中咱們能夠對某個文件夾建立替身或者複製粘貼某個文件/文件夾,這兩種方式都實現了對某個文件/文件夾的拷貝,可是,前者在文件中修改文件內容時,源文件也會修改,然後者的操做在修改文件內容時不會對源文件有影響code

上面的例子,「建立替身」 ==> 淺拷貝,「複製粘貼」 ==> 深拷貝

從內存角度說明:淺拷貝只會在棧內存中開闢空間存放指向源文件的變量,而深拷貝會在堆內存也拷貝文件

深淺拷貝僅是針對於數組和對象而言,不嚴謹的說法,基本類型的拷貝都屬於深拷貝

(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 的深複製

相關文章
相關標籤/搜索