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 也算一種基本類型?ios

基本類型 和 引用數據類型 的相關區別

基本數據類型

咱們來看一下 MDN 中對基本數據類型的一些定義:git

除 Object 之外的全部類型都是不可變的(值自己沒法被改變)。例如,與 C 語言不一樣,JavaScript 中字符串是不可變的(譯註:如,JavaScript 中對字符串的操做必定返回了一個新字符串,原始字符串並無被改變)。咱們稱這些類型的值爲「原始值」。github

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

咱們一般狀況下都是對一個變量從新賦值,而不是改變基本數據類型的值。在 js 中是沒有方法是能夠改變布爾值和數字的。卻是有不少操做字符串的方法,可是這些方法都是返回一個新的字符串,並無改變其原有的數據。好比:json

  • 獲取一個字符串的子串可經過選擇個別字母或者使用 String.substr().
  • 兩個字符串的鏈接使用鏈接操做符 (+) 或者 String.concat().

引用數據類型

引用類型(object)是存放在堆內存中的,變量其實是一個存放在棧內存的指針,這個指針指向堆內存中的地址。每一個空間大小不同,要根據狀況開進行特定的分配,例如。axios

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

因此說,基本類型的賦值的兩個變量是兩個獨立相互不影響的變量。函數

可是引用類型的賦值是傳址。只是改變指針的指向,例如,也就是說引用類型的賦值是對象保存在棧中的地址的賦值,這樣的話兩個變量就指向同一個對象,所以二者之間操做互相有影響。例如:post

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 淺拷貝

相關文章
相關標籤/搜索