ES6學習筆記-內存分配&解構詳解
-目錄-
-- 扯dan
-- JavaScript 中的數據類型與分類
-- 數組的解構
-- 數組的解構成功與完全解構
-- 數組解構當中默認值問題
-- 對象的解構
-- 對象解構當中默認值問題
-- 字元串解構
-- 扯dan+1
溫馨提示:閱讀此文大約需要7、8、9、10...分鐘
記得第一次創業時,曾經有幸和GameLoft的幾個老員工一起開發遊戲,在上地三街的一個狹小陰暗的廠房裡,對面就是當時火遍大江南北的"JJ鬥地主",夢想著有一天能像對面公司一樣牛X。雖然後期有些不愉快,但並不影響我對主程能力的崇拜,那位主程跟我說過一句至今都覺得很經典的話"程序寫的好不好,就看內存玩的好不好",當時的項目是用C++寫的,過去的事情好像啰嗦的有點多了…"收!"
(華麗又尷尬的轉折來了)那麼我們接下來先聊一聊Js中的數據類型在內存中的分配規則。
JavaScript中的數據類型大致有哪些,怎麼分類?
應該大致分為兩類:基礎類型、引用類型
Undefined、
Null、
Boolean、
Number、
String、
Symbol(ES6中新增的一種基本類型,表示獨一無二的值)
這些都是基礎類型,而
Function
Object
這兩個可以說是引用類型。
內存中是分為棧內存和堆內存的,棧的概念就像是一個過去家裡燒火用的蜂窩煤爐子,每個棧內存的大小都是固定的,所以針對於基礎類型足夠使用了,好比如每次從爐子上面放入一個蜂窩煤,每一個蜂窩煤的大小都是固定的,所蘊含的能量也是固定的,而對象則不是,對象中的屬性是不斷添加的,所以對象需要保存在堆內存中,棧中只是保存了對象在堆內存的地址索引,其實說白了也就是C++中的指針。下面這個圖可以很好地說明:
看完圖,我們來看個例子加深下理解:
// 只改變的是棧內存的
namespace x {
let a = "xxx";
let b = a;
a = "yyy"
console.log(b); // xxx
}
// b是一個棧內存,指向的是a在堆內存的地址,也可以理解為b是a的一個引用,同a指向了同一片內存區域,a和b在棧中存的內容是一樣的
namespace x1 {
let a = {
name: "xxx",
}
let b = a;
a.name = "yyy"
console.log(b.name); // yyy
}
再來看一個例子:
var age = "12"
var people = {
name: "llp"
}
function Hello(num,obj) {
num = "18"
obj.name = "kjj"
console.log(num, obj)
}
Hello(age, people)
console.log(age, people)// 12 kjj
在說明這個之前,我們可以先回顧一下上一篇提到的塊級作用域,函數Hello就是一個塊級作用域,那麼塊級作用域里的變數只在對應的作用域里生效,出了作用域,沒有被引用的變數就會被回收,這裡的函數參數num、obj是形式參數,實際調用時傳入的實際參數為兩個棧內存的地址,這兩個形式參數的棧內存會被兩個實際參數的棧內存給賦值,所以Hello方法內改變num僅僅只是改變了函數內的值,而沒有改變外部的age,相反,形式參數obj所引用的實際堆內存地址發生變動,就影響了外部的變數,所以輸出的內容為people是會發生變化的,算了,知道你們肯定懶得去敲,那我幫你敲一遍驗證一下:
以上都是按照我的理解儘可能的通俗闡述,不知道你看懂了么?當然,以上內容並不是我們最近要總結的ES6的主線內容,接下來的解構賦值卻是,而且可以說是相當好玩。
ES6的解構方式
解構(Destructuring)顧名思義,將一種結構的數據解析成另一種結構,兩種結構可以也相同可以(一定約定下)不同,而我所總結的目的也就是想整理和闡述下怎麼個「解法」,都有哪些個「約定」。開始進入主題~
先來看定義:「ES6允許按照一定的模式,從數組和對象中提取值,對變數進行賦值,這被稱為解構」。那麼我們就重點從數組和對象這兩個主要部分來進行區分整理。
數組
解構成功與完全解構(這兩個不是對立的關係,是子集的關係)
我們先來看看解構成功情況下的例子:
例1:
let[a, b, c] = [1, 2, 3];
例2:
let[a, [[b], c]] = ["aa", [["bb"], "cc"]];
console.log(a,b, c);// aa bb cc
例3:
let [ , , c] =["aa", "bb", "cc"];
console.log(c);// cc
例4:
let [a, ...b] = [1,2, 3, 4];
console.log(a,b);//1 [2,3,4];
相必你一定還會有很多疑問,別急,以上你看到的只是完全解構的,對應的還有"不完全解構的",即為"="左側的內容只有部分能夠匹配右邊的部分,這樣的解構依然是有效且成功的
例5:
let [x, y, ...z] =["a"];
console.log(x);// a
console.log(y);// undefined
console.log(z);// []
那麼未成功被解構賦值的變數則為undefined,更多的不完全解構的例子如:
例6:
let[a] = ["aa","bb"];
console.log(a);// aa
例7:
let [a, [b], d] =[1, [2, 3], 4];
console.log(a,b,d);// 1 2 4
有沒有覺得看得雲里霧裡,有就對了,例子的變種千千萬,但存在必有道理,數組的解構道理在於什麼呢?綜上例可以看出,數組的解構,是一種對格式要求極其嚴謹的賦值操作,專業講,這叫「模式匹配」,即左右兩邊均為相同的模式(注意對順序也是一個模式中的影響因素),即可進行解構,只是解構結果完全不完全的區別,但如果對於數組解構中,等號右側是一個非可遍歷的解構,那麼就會解構失敗,如
例8:
let [a] = 1;
let [a] = false;
let [a] = NaN;
let [a] = undefined;
let [a] = null;
let [a] = {};
數組解構當中的默認值問題
默認值為undefined時需要注意的一些問題:
數組的解構賦值是允許指定默認值的,你甚至可以指定默認值是undefined,但要注意的是ES6中因為內部約定"==="是一個嚴格相等的運算符,用於判斷一個位置是否有值,在解構賦值過程中,右側的數組成員只有在嚴格等於undefined的情況下,指定的默認值才會生效,比如:
let[x, y = "b"] = ["a"]; // x="a", y="b"
let [x, y = "b"] =["a", undefined]; // x="a", y="b"
let[x, y = "b"] = ["a", null]; // x="a",y=null
第三個例子中的null不嚴格等於undefined。
默認值為函數時需要注意的一些問題:
以上是一個默認值為undefined的問題,再來看一個默認值為函數的問題。如:
例9:
function f() {
console.log("a");
}
let [x = f()] = [1];
例10:
let x;
if ([1][0] ===undefined) {
x = f();
} else {
x = [1][0];
}
因為當默認值是函數時,函數是惰性的,也就是在需要用時才會執行,所以例9、10這兩種寫法表達的是一個意思,是不是感覺代碼能簡介很多。當然還是要提醒一下,注意"函數是惰性的!"
對象
不難看出,數組結構的解構是與元素順序有一定的關係,而對象則不然,簡而言之,對象解構的特點就是「鍵值匹配」的模式;
例1:
let exp:{ a:string }= { a: "aaa" };
console.log(exp) //"aaa"
瀏覽完例1,我們在來對「鍵值匹配」進行解讀,其本質是先找到同名屬性,其次,再賦值給屬性對應的變數,針對於上例來解釋,被改變的是對象exp.a所對應的變數,而a僅僅只是一個索引賦值的匹配模式,即所謂的鍵。
再來看一個阮老師簡潔又嚴謹的例子:
例2:
const node = {
loc: {
start: {
line: 1,
column: 5
}
}
};
let { loc, loc: , loc: { start: { line }} } = node;
line // 1
loc // Object
start // Object
阮老師的註解:「上面的代碼有三次解構,分別是對loc, start, line三個屬性的解構賦值,注意,最後一次對line屬性的解構賦值中,只有line是變數,其他loc, start都是模式,不是變數。」
例3:
let { log, sin, cos} = Math; // 將Math對象的對數、正弦、餘弦三個方法,複製到對應的變數上,使用起來方便了很多。
例4:
let arr = [1, 2, 3];
let = arr;
first // 1
last // 3
由於數組本質是特殊的對象,因此可以對數組進行對象屬性的解構。上例就是對數組進行了對象的解構,數組arr的鍵對應的值是1,[arr.length-1]就是2鍵,對應的值是3。
在這裡需要插一句,對象的解構賦值是一個淺拷貝,也就是我在本文最開始所介紹的內存中的拷貝關係,什麼?不信?好吧,就知道你有點蒙,請看下圖中我敲的一個例子截圖,先定義了一個變數a, 這是一個具有一定結構的對象,緊接著按照上述的對象解構賦值方式,我進行了一波賦值,然後新創建的未命名變數按照「鍵值匹配」的規則被賦值了,隨後我改變了被賦值的未命名變數(先後改變並輸出了其中的基礎類型變數和引用類型變數),結果很顯然,這是一個淺拷貝,a的基礎類型變數ab沒有發生變化,但引用類型變數ac對應的acb被改變了,這意味著a.ac的堆內存發生了變化,好了,解釋到這裡,我覺得我都已經有點啰嗦了。
按照分析數組解構的思路,接下來該說一下對象解構過程中的默認值問題,不過,也確實沒什麼好說的,和數組解構要求一致,對象屬性值嚴格等於undefined 時,默認值方可生效,如:
例5:
let = ;
// x = 3
let = ;
// y = null
字元串
字元串的解構賦值是將字元串先轉換為一個數組的對象,那麼進而就可以進行數組的解構賦值操作,如
例1:
const [a, b, c] ="abc";
a //"a"
b //"b"
c //"c"
例2:
let ="hello";
len // 5
這樣的解構賦值也是成立的,因為數組也是特殊對象,其中也是有length這個屬性的。
看到這裡,基本上可以做一個本篇的結束語了,這麼多範例和用法,其實仔細想想,我們的生產環境中不乏有他們的身影,當函數需要返回多個值,需要構建臨時對象、當解析伺服器返回的json時需要進行拷貝構造、當函數傳參需要進行指定形參默認值、當遍歷需要只取鍵名或只取鍵值……等等,是不是覺得很神奇?其實他們基礎就是如上,希望對你能有幫助。
有些看過或部分沒耐心看完的朋友多半會發此一問(事實上確實很多朋友問我),這麼簡單的解構賦值會用就行了,有必要扣得這麼仔細或是累么?對此疑問我個人是這麼認為的,首先大多數的圖書,或是現有市面上的專業介紹的都是比較複雜,章節很多,但實際上我們使用的並不是那麼全面,但做技術和做學問是不能分開的,我們可以誤解學者們的考究,但不能誤解科學學習的態度,所以提煉、剝離與總結是我們剛需開發者應該且必做的過程。其次針對於分享筆記有沒有必要這麼寫的這麼費勁,我是覺得,首先既然是分享,那麼受眾群體必然是專業技能的持有者或是學習者,那麼我必須嚴格要求自己會仔細學習一遍兩遍…甚至多遍,這種反覆學習探究與核實的過程其實是對自己基礎學科知識的夯實與打磨,與其說是分享給別人,更多的是提高了自己,何樂而不為呢?當然如果你不認同,那也可以出門左轉哈,謝謝,歡迎大家參與分享討論,多多給予批評和支持。再次謝謝!
下期預告:
ES6 學習筆記 - Js的非同步處理與應用
TAG:LuckyJasmine |