JS類型判斷、對象克隆、數組克隆

  1. 類型判斷

咱們先說一下JS的數據類型,咱們通常說JS有六大數據類型(ES6之前)分別是:前端

  • 基本數據類型
    • Number
    • String
    • Boolean
    • null
    • undefined
  • 引用數據類型
    • object

在ES6中新增了Symbol數據類型。數組

有時咱們須要知道數據的類型其判斷一些事情,咱們常常會用typeof去判斷數據類型。bash

  • 那麼typeof能判斷什麼類型呢?
    • Number
    • String
    • Boolean
    • object
    • undefined
    • function

就這幾個數據類型,但這些咱們夠用嗎?或者說準確嗎?post

咱們沒有看到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這個方法,我相信你們不陌生吧。它也能判斷數據類型,可是是這樣的。ui

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高級點,能分辨的更準確,可是格式好像不是咱們要的。this

接下來進入主題,直接上代碼。spa

//首先定義好數據類型。
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. 對象克隆

咱們常常會用到一個對象去作一些事情,可能有時候咱們不想改變原有的數據。,時候咱們就須要對象克隆了,你可能簡單的覺得就是 = 就好了,那咱們來看一看。prototype

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;
       }
    })
    複製代碼

下一篇文章我想說一下數組去重好像不是最全的數組去重方法,由於內容挺多,我就不一塊兒寫了,喜歡的能夠點一個贊,或者關注一下。鼓勵一下一名自學前端的大學生。

相關文章
相關標籤/搜索