深浅拷贝,以及JS如何判断数据类型
1. 最基础的方法:typeof
特点:简单,适合判断基础数据类型。
- 适用:Number, String, Boolean, Symbol, Undefined, Function。
- 局限性(面试必考点):
null的陷阱:typeof null会返回'object'(这是 JS 的历史遗留 Bug)。- 引用类型无法区分:除了 Function 外,其他的引用类型(Array, Date, RegExp, Error)都会统统返回
'object',无法具体区分。
js
12345678
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null // 'object' (注意!)
typeof [] // 'object'
typeof {} // 'object'
2. 基于原型链的方法:instanceof
特点:适合判断引用数据类型(Object),检测构造函数的
prototype属性是否出现在某个实例对象的原型链上。
- 适用:Array, Date, RegExp(正则) 等对象类型。
- 局限性:
- 不能判断基础类型:比如
1 instanceof Number为false。 - 原型链可被修改:如果手动修改了原型指向,结果可能不准确。
- 多窗口/iframe 问题:在不同 iframe 中,全局对象不同,
[] instanceof Array可能会失效。
- 不能判断基础类型:比如
js
123
[] instanceof Array // true
{} instanceof Object // true
new Date() instanceof Date // true
3. 构造函数属性:constructor
特点:查看对象的构造函数。
- 适用:大部分类型。
- 局限性:非常不安全。因为
constructor属性是可以被随意修改的,而且null和undefined没有constructor。一般不推荐作为首选方案。
JavaScript
1
[].constructor === Array // true
4. 终极全能方案:Object.prototype.toString.call()
特点:这是最准确、最通用的方法,也是面试中的“标准答案”。
- 原理:所有对象都继承自 Object,调用 Object 原型上的
toString方法,会返回格式为[object type]的字符串。 - 适用:所有类型(包括
null和undefined)。
js
12345678
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call(function(){}) // "[object Function]"
Object.prototype.toString.call({}) // "[object Object]"
手写通用类型判断函数
js
123456789101112131415
function getType(value) {
// 1. 处理 null 和 undefined (虽然 toString 也能处理,但这样更直观)
if (value === null) return 'null';
if (typeof value === 'undefined') return 'undefined';
// 2. 使用 typeof 处理基础类型 (性能稍好)
// 注意:Function 也是 'function',这里也可以直接返回
if (typeof value !== 'object') {
return typeof value;
}
// 3. 使用 Object.prototype.toString.call 处理复杂类型
// slice(8, -1) 是为了去掉 "[object " 和 "]",只保留类型名称
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}
深浅拷贝
第一,从原理上讲: 浅拷贝 (Shallow Copy) 就像是‘复制了钥匙’。对于引用类型,它只复制了指向堆内存中对象的地址(指针)。所以如果修改了新对象里的属性,原对象也会受到影响,因为它们指向的是同一个房间。 深拷贝 (Deep Copy) 则是‘建造了一个一模一样的新房子’。它会在堆内存中开辟新的空间,完整复制原对象的所有属性。新旧对象互不干扰。
第二,在日常开发中: 如果只是简单的对象,我会用 Object.assign() 或者 ES6 的展开运算符 ... 来做浅拷贝。 如果要深拷贝,最简单粗暴的方法是 JSON.parse(JSON.stringify()),但这只适用于纯数据对象。
第三,关于特殊情况: JSON 方法有局限性,比如它无法处理函数、正则、Date 对象,遇到循环引用还会报错。 所以在复杂的场景或者面试手写题中,我会采用递归的方式来实现深拷贝,并且需要通过 WeakMap 来解决循环引用的问题。”
手写实现深拷贝
js
1234567891011121314151617181920212223242526272829303132
function deepClone(obj, hash = new WeakMap()) {
// 如果是null或者不是对象,则直接返回
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 如果是正则表达式,则直接返回
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 如果是日期对象,则直接返回
if (obj instanceof Date) {
return new Date(obj);
}
// 如果是函数,则直接返回
if (typeof obj === 'function') {
return obj;
}
//如果已经存在于hash中,直接返回克隆后的对象
if (map.hash(obj)) {
return hash.get(obj);
}
// 创建一个新的对象或数组
const cloneObj = Array.isArray(obj) ? [] : {};
// 将新对象存入hash中
hash.set(obj, cloneObj);
// 递归克隆
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
}