上一篇文章 案例|原生手寫一個輪播圖——漸隱漸顯版 還有不少不足,這裏要很是感謝大佬 csdoker給出的寶貴意見和指導🙏,因此筆者決定從新完善一下輪播圖的案例,打算作一個簡易版的左右輪播圖插件的封裝;javascript
想到筆者寫文章的初衷是總結知識,爭取作到通俗易懂,因此今天筆者打算,先鋪墊幾個須要用到的很重要的知識點:深克隆 VS 淺克隆
、深比較 VS 淺比較
、回調函數基礎知識
;java
只把第一級的拷貝一份,賦值給新的數組(通常咱們實現數組克隆的辦法都是淺克隆)ajax
不只把第一級克隆一份給新的數組,若是原始數組中存在多級,那麼是把每一級都克隆一份賦值給新數組的每個級別數組
let arr2 = JSON.parse(JSON.stringify(arr1))
;數字
/字符串
/布爾
/null
/普通對象
/數組對象
等都沒有影響,可使用JSON.stringify(arr1):並非對全部的值都能有效處理瀏覽器
日期格式數據變爲字符串後,基於parse 也回不到對象格式了bash
let arr1 = [10, '20', [30, 40], /\d+/, function () {}, null, undefined, {
xxx: 'xxx'
}, Symbol('xxx'), new Date()];
複製代碼
思路:函數
一、傳遞進來的是函數時,不須要操做,直接返回便可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;
}
複製代碼
首先咱們先來看下這是什麼意思呢?
以題爲例:
let obj1 = {
name: '小芝麻',
age: 10,
teacher: {
0: '張三',
1: '李四'
}
};
let obj2 = {
age: 20,
school: "北京",
teacher: {2: "王五"}
};
複製代碼
當咱們想要把上面兩個對象合併的時候,就涉及到了「比較」的問題(筆者也不是很清楚爲何叫作「比較」);
teacher
的值是一個對象,並且內容還不同,那當合並的時候,會是怎樣的結果呢?這就是咱們接下來要說的深淺比較問題;
把兩個對象合併爲一個對象
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: "王五"}
,這個時候咱們就須要進行深比較了
思路:
一、首先深克隆一份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;
}
複製代碼
約定俗成的回調函數形參名字:callback
把一個函數看成值傳遞給另外開一個函數,在另一個函數中把這個函數執行
在大函數執行的過程當中,咱們能夠「盡情」的操做傳給他的回調函數
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);
});
複製代碼
參數是回調函數的有不少
定義:一個強大的迭代器
語法:_each([ARRAY/OBJECT/類數組],[CALLBACK])
@params:
@return:返回處理後的新數組/對象
功能:
代碼實現
// 檢測是否爲數組或者類數組
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;
}
複製代碼
好了,基礎知識部分咱們先鋪墊這些;
下一篇主要作:左右輪播圖的插件封裝(只是簡單的思考與模仿)
筆者深知將來的路還很長,插件封裝是一項重要課題,雖然能力有限,但要勇於嘗試,但願能在各位大佬的監督下與你們一塊兒成長😄