JS类型判断、对象克隆、数组克隆

本文深入探讨JavaScript中数据类型的判断方法,包括使用typeof和Object.prototype.toString,并提供了一种改进的类型判断函数。此外,文章还详细讲解了对象克隆的多种方式,包括浅克隆和深克隆,以及如何避免循环引用问题。
  1. 类型判断

我们先说一下JS的数据类型,我们一般说JS有六大数据类型(ES6以前)分别是:

  • 基本数据类型
    • Number
    • String
    • Boolean
    • null
    • undefined
  • 引用数据类型
    • object

在ES6中新增了Symbol数据类型。

有时我们需要知道数据的类型其判断一些事情,我们经常会用typeof去判断数据类型。

  • 那么typeof能判断什么类型呢?
    • Number
    • String
    • Boolean
    • object
    • undefined
    • function

就这几个数据类型,但这些我们够用吗?或者说准确吗?

我们没有看到null那么他是什么类型呢?我们用typeof null发现它是object,是不是很奇怪,其实这是一个bug,但是这个bug是能修复的但是不能修复,因为null一般是用来表示一个对象是空的,有时我们用null来取消事件,我理解的它好像是一个占位符,表示这个对象是空的。那为什么不能修复呢?因为修复它好说,但是修复了它会带来许多麻烦。

  • 专业点说就是:不同的对象在底层都是用二进制来表示,在JS中二进制前三位都是0的就会判断为object类型,因为null全是0所以会判断null也是object类型

我们来看一下typeof判断的情况。

console.log(typeof(1)); //number
console.log(typeof("1")); // string
console.log(typeof(true)); // boolean
console.log(typeof({})); // object
console.log(typeof(function (){})); // function
console.log(typeof(null)); // object
console.log(typeof(undefined)); // undefined
复制代码

当我们想判断一个对象那个是不是null或者是不是Date、RegExp等类型时会怎么样呢?我们发现都是object,那我们有没有办法区分他们呢? 在这之前我先介绍一下Object.prototype.toString这个方法,我相信大家不陌生吧。它也能判断数据类型,但是是这样的。

console.log(Object.prototype.toString.call(1)); 
console.log(Object.prototype.toString.call("1"));
console.log(Object.prototype.toString.call(true));
console.log(Object.prototype.toString.call({})); 
console.log(Object.prototype.toString.call(function (){}));
console.log(Object.prototype.toString.call(null));
console.log(Object.prototype.toString.call(undefined)); 
console.log(Object.prototype.toString.call(new RegExp())); 
console.log(Object.prototype.toString.call(new Date()));
[object Number]
[object String]
[object Boolean]
[object Object]
[object Function]
[object Null]
[object Undefined]
[object RegExp]
[object Date]
复制代码

我们发现它比typeof高级点,能分辨的更准确,但是格式好像不是我们要的。

接下来进入主题,直接上代码。

//首先定义好数据类型。
let types = {
	"[object Object]": "Object",
	"[object RegExp]": "RegExp",
	"[object Date]": "Date"
};
function type(regs) {
    let result = typeof(regs); // 先获取传过来的参数
    // 如果是对象在进行判断,不是则返回。
    if(result === 'object'){
    	if(regs === null){
    		return 'null';
    	}else{
    		return types[Object.prototype.toString.call(regs)];
    	}
    }else{
    	return result;
    }
}
console.log(type(1)); //number
console.log(type("1")); // string
console.log(type(true)); // boolean
console.log(type({})); // object
console.log(type(function (){})); // function
console.log(type(null)); // null
console.log(type(undefined)); // undefined
console.log(type(new RegExp())); //RegExp
console.log(type(new Date())); //Date
复制代码
  1. 对象克隆

我们经常会用到一个对象去做一些事情,可能有时候我们不想改变原有的数据。,时候我们就需要对象克隆了,你可能简单的以为就是 = 就行了,那我们来看一看。

let obj = {
	a: 1
}
let obj2 = obj;
console.log(obj); //{a: 1}
console.log(obj2); //{a: 1}
复制代码

我们看到复制过来了,这样我们就可以随便使用了。那我们来修改一下obj看看。

obj.a = 2;
console.log(obj); //{a: 2}
console.log(obj2); //{a: 2}
复制代码

发现都变成了{a: 2},是不是很奇怪。因为对象是引用类型的,他们赋值其实是赋的地址值,就是他们指向同一个地方,那么我们应该怎么做呢?你应该知道怎么遍历对象,我们就把它遍历一遍再复制。看代码

let obj = {
	a: 1
}
function clone(obj) {
		let a = {};
		for(let o in obj){
			a[o] = obj[o]
		}
		return a;
}
let obj2 = clone(obj);
console.log(obj); //{a: 1}
console.log(obj2); //{a: 1}
obj.a = 2;
console.log(obj); //{a: 2}
console.log(obj2); //{a: 1}
复制代码

没有改变,看来我们成功了,那这篇文章就到这了。呵呵,其实远没有,我们来看一下有没有什么问题。

  • 当里面的数据为引用类型时:
let obj = {
	a: {
		b: 1
	},
	c: 3
}
function clone(obj) {
		let a = {};
		for(let o in obj){
			a[o] = obj[o]
		}
		return a;
}
let obj2 = clone(obj);
console.log(obj);
console.log(obj2);
obj.a .b = 2;
console.log(obj);
console.log(obj2);
复制代码

我们发现

又出问题了。

  • 如果你知道for...in你就会知道它的另一个错误。就是它会遍历它原型上的可枚举属性和非Symbol的属性。那么我们怎么改善一下呢?现在介绍一下hasOwnProperty这个属性,它就是判断自身有没有这个属性,而不会去原型上找。
function clone(obj) {
	let a = {};
	for(let o in obj){
		if(obj.hasOwnProperty(o)){
			a[o] = obj[o];
		}
	}
	return a;
}
复制代码

这个问题解决了,就差上一个了,我们接着用判断数据的类型来判断是否还需要复制的方法解决上一个问题。

let obj = {
	a: {
		b: 1,
		d: {
			e:[{f: 2}],
			g: {
				h:{
					l: 5
				}
			}
		}
	},
	c: 3
}

function deepClone(origin, target) {
		let tar = target || {},
			arr = "[object Array]",
			str =  Object.prototype.toString;
		for(let o in origin){
			if(origin.hasOwnProperty(o)){
			    // 如果是对象接着递归复制
				if(typeof origin[o] === 'object'){ 
				// 判断是对象还是数组
						tar[o] = str.call(origin[o]) === arr ?  [] : {};
						deepClone(origin[o], tar[o]);
				}else{
					tar[o] = origin[o]; 
				}
			}
		}
		return tar;
}
let obj2 = deepClone(obj, {});
console.log(obj);
console.log(obj2);
obj.a.d.g.h.l = 6;
console.log(obj.a.d.g.h.l); //6
console.log(obj2.a.d.g.h.l); //5
复制代码
  • 其实这还不是最终的深克隆,因为这一个也有它自己的问题,但是面对一般的情况应该没问题,跟高级的用法请自行学习。
  • 模拟实现JQ的$.extend()方法(只是粗略的写了一下,如有错误欢迎指出):
function extend() {
	let origin, // 要拷贝的源
		 target = arguments[0], // 获取第一个参数
		 isDeepClone = false; // 是否深拷贝
		 length = arguments.length, //拷贝的个数
		 arr = "[object Array]",
		str =  Object.prototype.toString,
		 i = 0;
	if(typeof target === 'boolean'){
		isDeepClone = target;
		i ++;
		target = arguments[i]; //获取目标元素
	}
	//防止循环引用
	if(origin === target){
	    return;
	}
	// 兼容function
	if(typeof target !== 'object' && typeof target !== 'function' ){
		target = {};
	}
	for ( ; i < length; i++) {
		origin = arguments[i];
		for(let o in origin){
			if(origin.hasOwnProperty(o)){
				if(origin[o] === 'object'){
						if(isDeepClone){
							target[o] = str.call(origin[o]) === arr ? [] : {};
							extend(true, target[o], origin[o]);
						}
				}else{
					target[o] = origin[o];
				}
			}
		}
	}
	return target;
}
复制代码
  • 补充:其实不止这一种深克隆的方法,不如我们处理数据最常使用的JSON
let obj = {
	a: {
		b: function (argument) {

		},
		d: {
			e:[{f: 2}],
			g: {
				h:{
					l: 5
				}
			}
		}
	},
	c: 3
}
let r = JSON.stringify(obj);
r = JSON.parse(r);
obj.a.d.g.h.l = 6;
console.log(r.a.d.g.h.l); // 5
复制代码

也是可以的,我们输出一下r看看。

有没有发现少了什么?对,就是function,它不仅不能复制function还有undefined也不行,还有别的自己查一下吧。

3.数组克隆

有了上面的铺垫,我们知道数组也是引用类型,就不能简单的等于来复制。

  • concat
let arr = [8,5,6,6,8];
let arr2 = arr.concat();
arr2[3] = 1;
console.log(arr); //[8, 5, 6, 6, 8]
console.log(arr2); //[8, 5, 6, 1, 8]
复制代码

可以复制成功,那么引用类型呢?

let arr = [8,{a: 1},6,6,8];
let arr2 = arr.concat();
arr2[1].a = 2;
console.log(arr); 
console.log(arr2);
复制代码

  • 还有我们常用的slice也是一样
let arr = [8,{a: 1},6,6,8];
let arr2 = arr.slice();
arr2[1].a = 2;
arr2[2] = 2;
console.log(arr); 
console.log(arr2);
复制代码

还有一些别的方法,我就不一一列举了,这些都是浅复制。

如果想深度克隆数组,也可以使用上面介绍的使用JSON也是可以的。

let arr = [8,{a: 1},6,6,8];
let arr2 = JSON.parse( JSON.stringify(arr) );
arr2[1].a = 2;
arr2[2] = 2;
console.log(arr); 
console.log(arr2);
复制代码

目前想到的就这些,总感觉拉下了什么,如果我想起来了我会继续补充的。

4.闲聊

上面写的我意犹未尽,可能是自己知识的局限性暂时只能想到那些,上面说到了for...in,那么我们来简单的说一下for...of和它的区别。

  • 他们都是遍历用的,每次遍历数组和对象都会想起它们,那么你会不会弄混呢。 那我们直接遍历一次,看看有什么区别。
let arr = [8,{a: 1},6,6,8];
let a = {
	b:1,
	r: 8,
	h:{
		e:6
	}
}
console.log('for...of');
for(let i of arr){
	console.log(i); 
}
console.log('for...in');	
for(let i in a){
	console.log('key:' + i);  
}
复制代码

是不是感觉挺好的,我们再来看看。

let arr = [8,{a: 1},6,6,8];
let a = {
	b:1,
	r: 8,
	h:{
		e:6
	}
}
console.log('for...in遍历数组');
for(let i in arr){
	console.log(i); 
}
console.log('for...of遍历对象');	
for(let i of a){
	console.log('key:' + i);  
}
复制代码

  • for...of遍历对象直接报错了,你有没有注意到报错的信息。就是不可遍历,因为不是iterable。数组、字符串、Set、Map,内置好了Iterator(迭代器),它们的原型中都有一个Symbol.iterator方法,而Object对象并没有实现这个接口,使得它无法被for...of遍历。
  • 至于for...of遍历对象就需要实现Iterator,这里我就不写了,百度好多。
  • for...in遍历数组遍历出来的是索引。不过我们也可以得到数组的值。
for(let i in arr){
	console.log(arr[i]); //[8,{a: 1},6,6,8]
}
复制代码

我觉得你看到这里应该知道遍历什么用哪个更合适了。

  • 补充
    • __proto__的实现
    Object.defineProperty(Object.prototype, __proto__, {
       get: function(){
           return Object.getPrototypeOf(this);
       },
       set: function(ob){
           Object.setPrototypeOf(this, ob);
           return ob;
       }
    })
    复制代码

下一篇文章我想说一下数组去重好像不是最全的数组去重方法,因为内容挺多,我就不一起写了,喜欢的可以点一个赞,或者关注一下。鼓励一下一名自学前端的大学生。

转载于:https://juejin.im/post/5c20dd80518825644e6239e0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值