大多數狀況下,咱們能夠經過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
引用類型(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
經過上面的這些說明,相信你對深拷貝大體瞭解了是怎樣一個東西了:深拷貝是對對象以及對象的全部子對象進行拷貝。那麼如何實現這樣一個深拷貝呢?
對於常規的對象,咱們能夠經過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}"
既然淺拷貝只能實現非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] }
內部實現其實也是差很少。
更多前端日記請參考這裏: