連等賦值a.x = a = {n:2} 和a = a.x = {n:2}一樣嗎?
前言
最近有空看了一點(diǎn)《js 高級(jí)程序設(shè)計(jì)》中變量和內(nèi)存的章節(jié),對(duì)變量和內(nèi)存問題做了總結(jié)整理,其中包含了面試中一些老生常談的問題,并且穿插了一些看似簡單卻一不小心就掉坑里的小代碼題,假如你在面試中被問到,不知道能不能給面試官留下機(jī)敏過人的好印象呢?如果你也感興趣,那就繼續(xù)往下看吧。
本文涉及以下知識(shí)點(diǎn)
第一部分 變量
一 基礎(chǔ)數(shù)據(jù)類型
8 種。undefined、Null、Boolean、Number、String、symbol、bigInt、object。
1.1 基本類型
7 種。undefined、Null、Boolean、Number、String、symbol、bigInt。其中兩種較為特殊的稍作解釋。
symbol
每個(gè)從Symbol()返回的 symbol 值都是唯一的。一個(gè) symbol 值能作為對(duì)象屬性的標(biāo)識(shí)符;這是該數(shù)據(jù)類型僅有的目的。
- Symbol('1') == Symbol('1') // false
作為構(gòu)造函數(shù)來說它并不完整,因?yàn)樗恢С终Z法:new Symbol()。
- Symbol(1) //Symbol(1)
- new Symbol(1) // Uncaught TypeError: Symbol is not a constructor
Q1. 如果一個(gè)對(duì)象的 key 是用 Symbol 創(chuàng)建的,如何獲取該對(duì)象下所有的 key ,至少說出兩種?
A1. Object.getOwnPropertyNames Object.getOwnPropertySymbols 或者 Reflect.ownKeys
Q2. typeof Symbol 和 typeof Symbol()分別輸出什么?
A2.
- typeof Symbol //"function"
- typeof Symbol() //"symbol"
bigInt
BigInt 是一種數(shù)字類型的數(shù)據(jù),它可以表示任意精度格式的整數(shù)。目的是安全地使用更加準(zhǔn)確的時(shí)間戳,大整數(shù) ID 等,是 chrome 67 中的新功能。Number類型只能安全地表示-9007199254740991 (-(2^53-1)) 和9007199254740991(2^53-1)之間的整數(shù),超出此范圍的整數(shù)值可能失去精度。
- 9007199254740991 //9007199254740991
- 9007199254740992 //9007199254740992
- 9007199254740993 //9007199254740992 !!
創(chuàng)建BigInt在整數(shù)的末尾追加 n,或者調(diào)用BigInt()構(gòu)造函數(shù)。
- 9007199254740993n //9007199254740993n
- BigInt('9007199254740993') //9007199254740993n
- BigInt(9007199254740993) //9007199254740992n !!
BigInt 和 Number不是嚴(yán)格相等的,但是寬松相等的。
- 9n == 9
- //true
- 9n === 9
- //false
1.2 引用類型
object 里面包含的 function、Array、Date、RegExp。
1.3 null 和 undefined 的區(qū)別
undefined讀取一個(gè)沒有被賦值的變量,null定義一個(gè)空對(duì)象,是一個(gè)不存在的對(duì)象的占位符
null和undefined轉(zhuǎn)換成 number 數(shù)據(jù)類型結(jié)果不同。null轉(zhuǎn)成0,undefined轉(zhuǎn)成NaN
- new Number(null) //Number {0}
- new Number(undefined) //Number {NaN}
二 判斷數(shù)據(jù)類型
2.1 typeof
只適用于判斷基本數(shù)據(jù)類型(除null外)。null,表示一個(gè)空對(duì)象指針,返回object。對(duì)于對(duì)象來說,除了 function 能正確返回 function,其余也都返回object。
- var a;
- console.log(a);//undefined
- console.log(typeof a); //undefined
- typeof typeof 1 //"string"
2.2 instanceof
判斷 A 是否是 B 的實(shí)例,A instanceof B,用于判斷已知對(duì)象類型或自定義類型。
- function Beauty(name, age) {
- this.name = name;
- this.age = age;
- }
- var beauty = new Beauty("lnp", 18);
- beauty instanceof Beauty // true
Q1.
- Object instanceof Function // true
- Function instanceof Object // true
- Object instanceof Object // true
- Function instanceof Function // true
- instanceof 能夠判斷出 [] 是Array的實(shí)例,但它認(rèn)為[ ] 也是Object的實(shí)例,因?yàn)樗鼉?nèi)部機(jī)制是通過對(duì)象的原型鏈判斷的,在查找的過程中會(huì)遍歷左邊變量的原型鏈,直到找到右邊變量的prototype,找得到就返回true。
- instanceof無法判斷通過字面量創(chuàng)建的Boolean,Number,String類型,但是可以判斷經(jīng)new操作符創(chuàng)建的實(shí)例。
- instanceof可以判斷通過字面量創(chuàng)建的引用類型Array,Object。
2.3 constructor
- constructor 屬性返回對(duì)創(chuàng)建此對(duì)象的數(shù)組函數(shù)的引用。
constructor 不能用于判斷undefined 與 null,因?yàn)樗鼈儧]有構(gòu)造函數(shù)。
實(shí)用場景小程序中,WXS 不支持使用 Array 對(duì)象,因此我們平常用于判斷數(shù)組類型[] instanceof Array就不能使用了,而typeof []輸出結(jié)果是object,并不能滿足要求。這個(gè)時(shí)候就可以用constructor 屬性進(jìn)行類型判斷:[].constructor === Array //true。
2.4 toString
Object.prototype.toString.call()判斷某個(gè)對(duì)象屬于哪種內(nèi)置類型。是判斷數(shù)據(jù)類型最嚴(yán)謹(jǐn)通用的方法。
- 判斷基本類型
- Object.prototype.toString.call(null); // "[object Null]"
- Object.prototype.toString.call(undefined); // "[object Undefined]"
- Object.prototype.toString.call(true);// "[object Boolean]"
- Object.prototype.toString.call(123);// "[object Number]"
- Object.prototype.toString.call(“lnp”);// "[object String]"
- 判斷原生引用類型
- // 函數(shù)類型
- Function Beauty(){console.log(“beautiful lnp”);}
- Object.prototype.toString.call(Beauty);//”[object Function]”
- // 日期類型
- var date = new Date();
- Object.prototype.toString.call(date);//”[object Date]”
- // 數(shù)組類型
- var arr = [1,2,3];
- Object.prototype.toString.call(arr);//”[object Array]”
- // 正則表達(dá)式
- var reg = /^[\d]{5,20}$/;
- Object.prototype.toString.call(arr);//”[object RegExp]”
- 判斷原生 JSON 對(duì)象
- var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON);
- console.log(isNativeJSON);//輸出結(jié)果為”[object JSON]”說明 JSON 是原生的,否則不是
Q1.
- ({a:1}).toString() //返回什么
- ({a:1}).__proto__ === Object.prototype
- ({a:1}).toString() // [object Object]
- 對(duì)于 Object 對(duì)象,直接調(diào)用 toString() 就能返回 [object Object] 。而對(duì)于其他對(duì)象,則需要通過call來調(diào)用才能返回正確的類型信息。
小結(jié)
第二部分 內(nèi)存
一 堆/棧
基本類型保存在棧內(nèi)存,引用類型的值保存在堆內(nèi)存,存儲(chǔ)該值的地址(指針)保存在棧內(nèi)存中,這是因?yàn)楸4嬖跅?nèi)存的必須是大小固定的數(shù)據(jù),而引用類型的大小不固定。
堆和棧的區(qū)別
二 深淺拷貝
2.1 數(shù)據(jù)類型的拷貝
Q1. 以下代碼輸出什么?
- 1 == 1;
- [] == []; //?
- {} == {}; //?
A1. 答對(duì)了嗎?
- 1 == 1; //true
- [] == []; //false
- {} == {}; //false
原因:原始值的比較是值的比較,值相等時(shí)它們就相等(),值和類型都相等時(shí)它們就恒等(=) 對(duì)象(數(shù)組)的比較是引用的比較,即使兩個(gè)對(duì)象包含同樣的屬性及相同的值,它們也是不相等的。
基本類型
圖片復(fù)制的時(shí)候,在棧內(nèi)存中重新開辟內(nèi)存,存放變量 age2,所以在棧內(nèi)存中分別存放著變量 age、age2 各自的值,修改時(shí)互不影響。
引用類型
復(fù)制的時(shí)候,只復(fù)制了棧內(nèi)存中存儲(chǔ)的指針,Beauty 和 Beauty2 的指針指向的是堆內(nèi)存中的同一個(gè)值。
連等賦值 a.x = a = {n:2}
理解了嗎?那么題目中的問題就要來嘍!準(zhǔn)備好了嗎?請(qǐng)看下面一小段代碼... ...
- var a = {n:1};
- var b = a;
- a.x = a = {n:2}
- console.log(a.x);
- console.log(b.x);
- console.log(a);
- console.log(b);
- // 以上代碼輸出什么?
答(猜)對(duì)了嗎?
- undefined
- {n: 2}
- {n: 2}
- {n: 1, x: {n: 2}}
解析我們先來還原一下這道題的執(zhí)行過程。首先var a = {n:1}; 時(shí),為變量 a 在堆內(nèi)存中開辟了一塊內(nèi)存空間。圖片
當(dāng)var b = a; 時(shí),將變量 b 的指針指向了堆內(nèi)存中與 a 相同的內(nèi)存空間。
a.x = a = {n:2}中由于“.”是優(yōu)先級(jí)最高的運(yùn)算符,先計(jì)算a.x,a.x尚未定義,所以是undefined。
然后賦值運(yùn)算按照從右往左的順序解析,執(zhí)行a = {n:2},a(一層變量)被重新賦值,指向一塊新的內(nèi)存空間。
此時(shí)a.x,是指堆中已存在的內(nèi)存 {n:1 x:undefined }的x屬性,可以把a(bǔ).x當(dāng)作一個(gè)整體A,A的指向此時(shí)已經(jīng)確定了,所以a的指向并不會(huì)影響a.x,執(zhí)行a.x = a,則是把{n:2}賦值給{n:1 x:undefined }的x屬性。
到這里函數(shù)執(zhí)行完畢,那我們來看(a.x); (b.x); (a); (b); 分別指向什么就 一目了然了。
這個(gè)題考察以下 2 個(gè)知識(shí)點(diǎn):
- object 類型數(shù)據(jù)在堆棧中的存儲(chǔ)和賦值
- Javascript 中運(yùn)算符的優(yōu)先級(jí)
同理,來看一下把a(bǔ).x = a = {n:2} 換成a = a.x = {n:2}呢?
- var a = {n:1};
- var b = a;
- a = a.x = {n:2}
- console.log(a.x);
- console.log(b.x);
- console.log(a);
- console.log(b);
答案是一樣的,原理同上。
2.2 深拷貝和淺拷貝區(qū)別
- 淺拷貝 只復(fù)制指向某個(gè)對(duì)象的指針,而不復(fù)制對(duì)象本身,拷貝后新舊對(duì)象指向堆內(nèi)存的同一個(gè)值,修改新對(duì)象對(duì)改變舊對(duì)象。
- 深拷貝 不但對(duì)指針進(jìn)行拷貝,也對(duì)指針指向的值進(jìn)行拷貝,也就是會(huì)另外創(chuàng)造一個(gè)一模一樣的對(duì)象副本,新對(duì)象跟舊對(duì)象是各自獨(dú)立的內(nèi)存空間,修改新對(duì)象不會(huì)影響舊對(duì)象。
2.3 深拷貝和淺拷貝優(yōu)劣,常用場景
- 深拷貝好處 避免內(nèi)存泄漏:在對(duì)含有指針成員的對(duì)象進(jìn)行拷貝時(shí),使拷貝后的對(duì)象指針成員有自己的內(nèi)存空間。
- 淺拷貝好處 如果對(duì)象比較大,層級(jí)也比較多,深拷貝會(huì)帶來性能上的問題。在遇到需要采用深拷貝的場景時(shí),可以考慮有沒有其他替代的方案。在實(shí)際的應(yīng)用場景中,也是淺拷貝更為常用。
2.4 實(shí)現(xiàn)深淺拷貝的方法和其弊端
深拷貝方法:
JSON.parse()與 JSON.stringify() 針對(duì)純 JSON 數(shù)據(jù)對(duì)象,但是它只能正確處理的對(duì)象只有 Number, String, Boolean, Array, 扁平對(duì)象。
- let obj = {a:{b:22}};
- let copy = JSON.parse(JSON.stringify(obj));
- postMessage
- 遞歸
- lodash
淺拷貝方法:
- object.assign
- 擴(kuò)展運(yùn)算符...
- slice
- Array.prototype.concat()
2.5 總結(jié)
深淺拷貝只針對(duì) Object, Array 這樣的復(fù)雜對(duì)象,淺拷貝只復(fù)制一層對(duì)象的屬性,而深拷貝則遞歸復(fù)制了所有層級(jí)。
第三部分 垃圾收集
javaScript 具有自動(dòng)垃圾收集機(jī)制,也就是說,執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過程中使用的內(nèi)存。
那么,是不是我們就可以不管了呢?當(dāng)然不是啊。開發(fā)人員只是無需關(guān)注內(nèi)存是如何分配和回收的,但是仍然要避免自己的操作導(dǎo)致內(nèi)存無法被跟蹤到和回收。
垃圾收集
首先,我們來了解一下 js 垃圾回收的原理。在局部作用域中,函數(shù)執(zhí)行完畢,局部變量就沒有存在的必要了,因此可以釋放他們的內(nèi)存以供將來使用。這種情況,js 垃圾回收機(jī)制很容易做出判斷并且回收。垃圾收集器必須跟蹤哪個(gè)變量有用哪個(gè)沒用,把不用的打上標(biāo)記,等待將來合適時(shí)機(jī)回收。
js 的垃圾收集機(jī)制有兩種,一種為標(biāo)記清除,一種為引用計(jì)數(shù)。
所以對(duì)于全局變量,很難確認(rèn)它什么時(shí)候不再需要。
內(nèi)存泄漏 vs 堆棧溢出
什么是內(nèi)存泄漏和堆棧溢出,二者關(guān)系是什么?
內(nèi)存泄露是指你不再訪問的變量,依然占據(jù)著內(nèi)存空間。堆棧溢出是指調(diào)用即進(jìn)棧操作過多,返回即出棧不夠。關(guān)系:內(nèi)存泄漏的堆積最終會(huì)導(dǎo)致堆棧溢出。
內(nèi)存泄漏會(huì)導(dǎo)致什么問題?
運(yùn)行緩慢,崩潰,高延遲
4 種常見的內(nèi)存泄露陷阱
- 意外的全局變量,這些都是不會(huì)被回收的變量,特別是那些用來臨時(shí)存儲(chǔ)大量信息的變量(除非設(shè)置 null 或者被重新賦值)
- 定時(shí)器未銷毀
- DOM 以外的節(jié)點(diǎn)引用
- 閉包(閉包的局部變量會(huì)一直保存在內(nèi)存中)
2 種常見的堆棧溢出
- 遞歸
- 無限循環(huán)
內(nèi)存泄漏避免策略
- 減少不必要的全局變量,或者生命周期較長的對(duì)象,及時(shí)解除引用(將不再使用的全局對(duì)象、全局對(duì)象屬性設(shè)置為 null)
- 注意程序邏輯,避免“死循環(huán)”之類的
- 避免創(chuàng)建過多的對(duì)象
- 減少層級(jí)過多的引用
劉妮萍: 微醫(yī)前端工程師。養(yǎng)魚養(yǎng)花養(yǎng)狗,熬夜蹦迪喝酒。出走半生,歸來仍是三年前端工作經(jīng)驗(yàn)。