前端的面试笔记——JavaScript篇(二)
一、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)、性能优化(避免过度递归)。