一、instanceof
在 JavaScript 里,instanceof
是一個相當實用的運算符,它的主要功能是檢查某個對象是否屬于特定構造函數的實例。這里需要明確的是,判斷的依據并非對象的類型,而是其原型鏈。下面為你詳細介紹它的用法和特點:
基礎語法
object instanceof constructor
若 object
是 constructor
的實例,或者說在其原型鏈上能找到 constructor.prototype
,該表達式就會返回 true
,反之則返回 false
。
主要作用
1. 判定對象類型
你可以借助 instanceof
來判斷一個對象是否為特定類的實例。
class Person {}
const person = new Person();console.log(person instanceof Person); // true
2. 驗證內置對象類型
對于 JavaScript 的內置對象,同樣可以使用 instanceof
來驗證其類型。
const arr = [];
const num = 5;console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true(因為數組本質上也是對象)
console.log(num instanceof Number); // false(基本類型通過裝箱轉換才會成為對象)
console.log(new Number(5) instanceof Number); // true
3. 檢查原型鏈關系
instanceof
還能用于檢查對象的原型鏈上是否存在某個構造函數的原型。
function Animal() {}
function Dog() {}Dog.prototype = Object.create(Animal.prototype);const dog = new Dog();console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true
需留意的特殊情形
1. 基本類型與 instanceof
基本類型(像 number
、string
、boolean
等)直接使用 instanceof
會返回 false
,除非它們通過構造函數(如 new Number()
)被轉換為對象。
const str = "hello";
const strObj = new String("hello");console.log(str instanceof String); // false
console.log(strObj instanceof String); // true
2. 跨窗口(Cross-Window)問題
在瀏覽器環境中,不同窗口(比如 iframe)的全局對象是相互獨立的。這就導致,從一個窗口創建的對象和另一個窗口的構造函數使用 instanceof
比較時,結果會是 false
。
// 在 iframe 中執行
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);const arr = iframe.contentWindow.Array();
console.log(arr instanceof Array); // false(當前窗口的 Array 與 iframe 的 Array 不同)
3. 手動修改原型
要是手動對對象的原型(__proto__
或 Object.setPrototypeOf
)進行修改,instanceof
的結果可能會與預期不符。
const obj = {};
Object.setPrototypeOf(obj, Array.prototype);console.log(obj instanceof Array); // true(盡管 obj 并非通過 new Array() 創建)
替代方案
1. Object.prototype.toString.call()
這種方法可以更精準地判斷對象類型,而且能處理基本類型的情況。
const arr = [];
console.log(Object.prototype.toString.call(arr)); // [object Array]
2. Array.isArray()
專門用于判斷一個值是否為數組,并且能解決跨窗口的問題。
console.log(Array.isArray([])); // true
總結
instanceof
運算符在檢查對象與構造函數的繼承關系時非常有用,但在使用過程中要特別注意基本類型、跨窗口對象以及原型修改等特殊情況可能帶來的影響。在實際的編程工作中,你可以根據具體的使用場景,將 instanceof
與其他類型檢查方法結合起來使用。
二、深拷貝和淺拷貝
在 JavaScript 中,深拷貝和淺拷貝是處理對象和數組時的重要概念,也是面試中的高頻考點。以下是對這兩個概念的詳細解釋及相關面試題分析:
一)、基本概念
1. 淺拷貝(Shallow Copy)
- 定義:創建一個新對象,復制原始對象的一層屬性。如果屬性是基本類型(如 number、string),則復制值;如果屬性是引用類型(如對象、數組),則復制引用(內存地址)。
- 特點:新對象和原始對象共享引用類型的屬性,修改其中一個會影響另一個。
2. 深拷貝(Deep Copy)
- 定義:創建一個新對象,遞歸復制原始對象的所有屬性(包括嵌套的引用類型)。
- 特點:新對象和原始對象完全獨立,修改其中一個不會影響另一個。
二)、實現方式
淺拷貝方法
淺拷貝直接賦值的方式這里就不做講解了,這里列舉一些容易忽略的淺拷貝方式,在項目中可能會因此產生一些bug問題。
1. 手動遍歷對象:
function shallowCopy(obj) {const newObj = {};for (let key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = obj[key];}}return newObj;
}
2. 展開語法(Spread):
const newObj = { ...oldObj };
const newArr = [...oldArr];
3. Object.assign():
const newObj = Object.assign({}, oldObj);
4. Array.prototype.slice() / Array.from():
const newArr = oldArr.slice();
const newArr = Array.from(oldArr);
深拷貝方法
1. JSON.parse(JSON.stringify()):
const newObj = JSON.parse(JSON.stringify(oldObj));
限制
- 無法處理函數、正則、Date 等特殊對象。
- 會忽略
undefined
和symbol
類型的屬性。 - 無法處理循環引用(對象引用自身)。
2. 遞歸實現(手動-推薦方式):
function deepCopy(obj) {if (obj === null || typeof obj !== 'object') return obj;const newObj = Array.isArray(obj) ? [] : {};for (let key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = deepCopy(obj[key]);}}return newObj;
}
3. 第三方庫:
- Lodash 的
_.cloneDeep()
:const newObj = _.cloneDeep(oldObj);
- 結構化克隆(Structured Clone):
瀏覽器原生 API,支持循環引用,但有兼容性限制const newObj = structuredClone(oldObj); // 瀏覽器原生 API,支持循環引用,但有兼容性限制
三)、面試題
1. 手寫深拷貝函數
要求:實現一個能處理嵌套對象、數組、循環引用的深拷貝函數。
答案示例:
function deepCopy(obj, map = new WeakMap()) {// 處理基本類型和 nullif (obj === null || typeof obj !== 'object') return obj;// 處理循環引用if (map.has(obj)) return map.get(obj);// 處理特殊對象(Date、RegExp 等)if (obj instanceof Date) return new Date(obj.getTime());if (obj instanceof RegExp) return new RegExp(obj);// 創建新對象/數組const newObj = Array.isArray(obj) ? [] : {};map.set(obj, newObj); // 記錄已處理的對象// 遞歸復制所有屬性for (let key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = deepCopy(obj[key], map);}}return newObj;
}
2. 淺拷貝和深拷貝的區別
回答要點:
- 淺拷貝:只復制一層屬性,引用類型共享內存地址。
- 深拷貝:完全獨立的新對象,遞歸復制所有層級。
- 使用場景:淺拷貝適用于簡單對象,深拷貝適用于需要完全隔離的復雜對象。
3. JSON.stringify () 的局限性
回答要點:
- 無法處理函數、正則、Symbol、Date 等特殊對象。
- 忽略
undefined
和循環引用。 - 示例:
const obj = {func: () => {},date: new Date(),nested: { prop: undefined } }; const copy = JSON.parse(JSON.stringify(obj)); console.log(copy); // { date: "2023-01-01T00:00:00.000Z", nested: {} }
4. 如何處理循環引用?
回答要點:
- 使用
WeakMap
記錄已處理的對象,避免遞歸時無限循環。 - 示例代碼(見手寫深拷貝函數中的
map
參數)。
5. 實際應用場景
- 淺拷貝:狀態管理庫(如 Redux)中的不可變數據更新、對象合并。
- 深拷貝:游戲狀態復制、復雜表單數據備份、避免副作用。
四)、總結
- 淺拷貝:適用于單層對象,使用 Object.assign()、展開語法等。
- 深拷貝:適用于復雜嵌套對象,推薦使用成熟庫(如 Lodash)或手動遞歸實現。
- 面試注意點:處理循環引用、特殊對象(如 Date、RegExp)、性能優化(避免過度遞歸)。