深克隆 VS 淺克隆|深比較 VS 淺比較|回調函數

上一篇文章 案例|原生手寫一個輪播圖——漸隱漸顯版 還有不少不足,這裏要很是感謝大佬 csdoker給出的寶貴意見和指導🙏,因此筆者決定從新完善一下輪播圖的案例,打算作一個簡易版的左右輪播圖插件的封裝;javascript

想到筆者寫文章的初衷是總結知識,爭取作到通俗易懂,因此今天筆者打算,先鋪墊幾個須要用到的很重要的知識點:深克隆 VS 淺克隆深比較 VS 淺比較回調函數基礎知識java

1、深克隆 VS 淺克隆

思惟導圖

一、淺克隆

-1)定義:

只把第一級的拷貝一份,賦值給新的數組(通常咱們實現數組克隆的辦法都是淺克隆)ajax

-2)方法:

  • slice:
    • 實現克隆原理:建立一個新的數組,循環原始數組中的每一項,把每一項賦值給新數組
    • let arr2 = arr1.slice(0);
  • concat:
    • let arr2 = arr1.concat();
  • 擴展運算符[...ary]:
    • let arr2 = [...arr1];
  • ......等

二、深克隆

-1)定義:

不只把第一級克隆一份給新的數組,若是原始數組中存在多級,那麼是把每一級都克隆一份賦值給新數組的每個級別數組

-2)方法一:利用 JSON 數據格式

  • 語法:
    • let arr2 = JSON.parse(JSON.stringify(arr1));
  • 實現原理:
    • JSON.stringify(arr1):先把原始對象變爲一個字符串(去除堆和堆嵌套的關係)
    • JSON.parse(...):在把字符串轉換爲新的對象,這樣瀏覽器會從新開闢內存來存儲信息
  • 應用:
    • 數字/字符串/布爾/null/普通對象/數組對象 等都沒有影響,可使用
  • 缺點:
    • JSON.stringify(arr1):並非對全部的值都能有效處理瀏覽器

      • 正則會變成空對象
      • 函數/undefined/Symbol 都會變成null
      • 這樣克隆後的信息和原始數據產生差別化
    • 日期格式數據變爲字符串後,基於parse 也回不到對象格式了bash

  • 舉個🌰:一個變態的數組
let arr1 = [10, '20', [30, 40], /\d+/, function () {}, null, undefined, {
    xxx: 'xxx'
}, Symbol('xxx'), new Date()];
複製代碼

-3)方法二:本身封裝

  • 語法:
    • let arr2 = cloneDeep(arr1);

思路:函數

  • 一、傳遞進來的是函數時,不須要操做,直接返回便可post

    • 由於在一個執行環境棧中一個名字的函數只能又一個,若是咱們本身又克隆了一個,會把原來的替換掉,這樣作沒有任何意義
  • 二、傳遞進來的是基本數據類型時,不須要操做,直接返回便可ui

  • 三、傳遞的是對象類型時this

    • (1). 正則對象:建立一個新的實例儲存當前正則便可(由於咱們的目的讓空間地址不同便可)
    • (2). 日期對象:建立一個日期實例儲存當前日期
    • (3). 普通對象&&數組對象:建立一個新的實例,循環存儲當前信息;
      • 普通對象&&數組對象 中有可能還會存在多層嵌套的關係,因此這裏咱們能夠用下遞歸
  • 代碼實現:
function _cloneDeep(obj) {
	// 傳遞進來的若是不是對象,則無需處理,直接返回原始的值便可(通常Symbol和Function也不會進行處理的)
	if (obj === null) return null;
	if (typeof obj !== "object") return obj;

	// 過濾掉特殊的對象(正則對象或者日期對象):直接使用原始值建立當前類的一個新的實例便可,這樣克隆後的是新的實例,可是值和以前同樣
	if (obj instanceof RegExp) return new RegExp(obj);
	if (obj instanceof Date) return new Date(obj);

	// 若是傳遞的是數組或者對象,咱們須要建立一個新的數組或者對象,用來存儲原始的數據
	// obj.constructor 獲取當前值的構造器(Array/Object)
	let cloneObj = new obj.constructor;
	for (let key in obj) {
		// 循環原始數據中的每一項,把每一項賦值給新的對象
		if (!obj.hasOwnProperty(key)) break;
		cloneObj[key] = _cloneDeep(obj[key]);
	}
	return cloneObj;
}
複製代碼

2、深比較 VS 淺比較

首先咱們先來看下這是什麼意思呢?

以題爲例:

let obj1 = {
    name: '小芝麻',
    age: 10,
    teacher: {
        0: '張三',
        1: '李四'
    }
};

let obj2 = {
    age: 20,
    school: "北京",
    teacher: {2: "王五"}
};
複製代碼

當咱們想要把上面兩個對象合併的時候,就涉及到了「比較」的問題(筆者也不是很清楚爲何叫作「比較」);

  • 兩個對象中都有age、school、teacher屬性;其中咱們看見teacher的值是一個對象,並且內容還不同,那當合並的時候,會是怎樣的結果呢?

這就是咱們接下來要說的深淺比較問題;

一、淺比較

-1)定義:

把兩個對象合併爲一個對象

-2)方法:Object.assign(obj1,obj2)

  • 合併兩個對象(用後一個替換前一個),返回合併後的新對象
  • 這個方法中的合併就是淺比較:只比較第一級

仍是這題

let obj1 = {
    name: '小芝麻',
    age: 10,
    teacher: {
        0: '張三',
        1: '李四'
    }
};

let obj2 = {
    age: 20,
    school: "北京",
    teacher: {2: "王五"}
};

let obj = Object.assign(obj1,obj2);
console.log(obj);
複製代碼

輸出的結果如👇

能夠看到合併兩個對象(用後一個替換前一個)後,返回合併後的新對象;

其中同時共有的屬性teacher是一個對象數據類型,只比較了一級,就用後一項(obj2)對應的空間地址替換了前一項(obj1)的teacher 值的空間地址;

不少時候能們想要的效果並非這樣,咱們想要的是把相同屬性名對應的屬性值也合併,就像上題中teacher屬性合併後應該是{0: '張三', 1: '李四', 2: "王五"},這個時候咱們就須要進行深比較了

二、深比較

  • 語法:
    • let res = _assignDeep(obj1,obj2)

思路:

  • 一、首先深克隆一份obj1

  • 二、循環拿出obj2中的每一項與克隆的obj1比較,

    • 若是當前拿出這一項是對象數據類型 而且 克隆的obj1 中相同屬性名對應的也是對象數據類型的值,
      • 再次進行深比較,用遞歸處理一下便可;
    • 其他狀況都直接用obj2的值替換obj1的值便可;
  • 代碼實現:
function _assignDeep(obj1, obj2) {
    // 先把OBJ1中的每一項深度克隆一份賦值給新的對象
    let obj = _cloneDeep(obj1);

    // 再拿OBJ2替換OBJ中的每一項
    for (let key in obj2) {
        if (!obj2.hasOwnProperty(key)) break;
        let v2 = obj2[key],
            v1 = obj[key];
        // 若是OBJ2遍歷的當前項是個對象,而且對應的OBJ這項也是一個對象,此時不能直接替換,須要把兩個對象從新合併一下,合併後的最新結果賦值給新對象中的這一項
        if (typeof v1 === "object" && typeof v2 === "object") {
            obj[key] = _assignDeep(v1, v2);
            continue;
        }
        obj[key] = v2;
    }
    return obj;
}
複製代碼

3、回調函數

約定俗成的回調函數形參名字:callback

思惟導圖

一、定義:

把一個函數看成值傳遞給另外開一個函數,在另一個函數中把這個函數執行

二、特色

在大函數執行的過程當中,咱們能夠「盡情」的操做傳給他的回調函數

  • 一、能夠把它執行(執行零到屢次)
  • 二、還能夠給回調函數傳遞實參
  • 三、還能夠改變裏面的this
    • 若是回調函數是一個箭頭函數須要注意
    • 箭頭函數中沒有THIS,用的THIS都是上下文中的
  • 四、還能夠接受函數執行的返回結果
function func(callback) {
	// callback => anonymous
	// 在FUNC函數執行的過程當中,咱們能夠「盡情」的操做這個回調函數
	// 1.能夠把它執行(執行零到屢次)
	// 2.還能夠給回調函數傳遞實參
	// 3.還能夠改變裏面的THIS
	// 4.還能夠接受函數執行的返回結果
	for (let i = 0; i < 5; i++) {
		// callback(i); //=>分別把每一次循環的I的值當作實參傳遞給anonymous,因此anonymous總計被執行了5次,每一次執行均可以基於形參index獲取到傳遞的i的值
		let res = callback.call(document, i);
		// res是每一次anonymous執行返回的結果
		if (res === false) {
			// 接受回調函數返回的結果,控制循環結束
			break;
		}
	}
}

func(function anonymous(index) {
	// console.log(index, this);
	if (index >= 3) {
		return false;
	}
	return '@' + index;
});

func((index) => {
 	// 箭頭函數中沒有THIS,用的THIS都是上下文中的
 	console.log(index, this);
}); 
複製代碼

三、幾個回調函數的經典用法

參數是回調函數的有不少

  • 一、數組迭代的方法 forEach
    • arr.forEach(item=>{})
    • forEach在執行的時候,會遍歷數組中的每一項,每遍歷一項 會把咱們傳遞進來的箭頭函數執行一次
  • 二、JQ中的ajax
    • $.ajax({ url:'', success:function(){ // 請求成功會把傳遞的函數執行 }});
  • 三、事件綁定
    • window.addEventListener('scroll',function(){});
  • ......等

四、封裝一個迭代的方法(適用於:數組/類數組/對象)

  • 定義:一個強大的迭代器

  • 語法:_each([ARRAY/OBJECT/類數組],[CALLBACK])

  • @params:

    • obj:要迭代的數組、類數組、對象
    • callback:每一次迭代觸發執行的回調函數
      • 參數:item:當前項
      • 參數:index:當前項索引
    • context:要改變的回調函數的THIS
  • @return:返回處理後的新數組/對象

  • 功能:

    • 一、能夠遍歷數組、類數組、對象,每一次遍歷均可以把【CALLBACK】執行
    • 二、每一次執行回調函數,都會把當前遍歷的結果(當前項/索引)傳遞給回調函數
    • 三、支持第三個參數,用來改變回調函數中的THIS執行(不傳遞,默認是WINDOW)
    • 四、支持回調函數返回值,每一次返回的值會把當前集合中的這一項的值替換掉;若是回調函數返回的是FALSE(必定是FALSE),則結束遍歷
  • 代碼實現

// 檢測是否爲數組或者類數組
function isArrayLike(obj) {
    let length = !!obj && ("length" in obj) && obj.length;
    return Array.isArray(obj) || length === 0 || (typeof length === "number" && length > 0 && (length - 1) in obj);
}

function _each(obj, callback, context = window) {
    //=>把原始傳遞的進來的數據深度克隆一份,後期操做的都是克隆後的結果,對原始的數據不會產生改變
    obj = _cloneDeep(obj); 

    // 參數合法性校驗
    if (obj == null) {
        //=>null undefined 
        // 手動拋出異常信息,一但拋出,控制檯會報錯,下面代碼不在執行 Error/TypeError/ReferenceError/SyntaxError...
        throw new TypeError('OBJ必須是一個對象/數組/類數組!');
    }
    if (typeof obj !== "object") {
        throw new TypeError('OBJ必須是一個對象/數組/類數組!');
    }
    if (typeof callback !== "function") {
        throw new TypeError('CALLBACK必須是一個函數!');
    }

    // 開始循環(數組和類數組基於FOR循環,對象循環是基於FOR IN)
    if (isArrayLike(obj)) {
        // 數組或者類數組
        for (let i = 0; i < obj.length; i++) {
            // 每一次遍歷都執行回調函數,傳遞實參:當前遍歷這一項和對應索引
            // 並且改變其THIS
            // RES就是回調函數的返回值
            let res = callback.call(context, obj[i], i);
            if (res === false) {
                // 返回FALSE結束循環
                break;
            }
            if (res !== undefined) {
                // 有返回值,則把當前數組中的這一項替換掉
                obj[i] = res;
            }
        }
    } else {
        // 對象
        for (let key in obj) {
            if (!obj.hasOwnProperty(key)) break;
            let res = callback.call(context, obj[key], key);
            if (res === false) break;
            if (res !== undefined) obj[key] = res;
        }
    }
    return obj;
}
複製代碼

好了,基礎知識部分咱們先鋪墊這些;

下一篇主要作:左右輪播圖的插件封裝(只是簡單的思考與模仿)

筆者深知將來的路還很長,插件封裝是一項重要課題,雖然能力有限,但要勇於嘗試,但願能在各位大佬的監督下與你們一塊兒成長😄

相關文章
相關標籤/搜索