南平網(wǎng)站開(kāi)發(fā)客戶引流推廣方案
前言
?📫 大家好,我是南木元元,熱愛(ài)技術(shù)和分享,歡迎大家交流,一起學(xué)習(xí)進(jìn)步!
?🍅?個(gè)人主頁(yè):南木元元
今天來(lái)分享一下10個(gè)常見(jiàn)的JavaScript手寫功能。
目錄
1.實(shí)現(xiàn)new
2.call、apply、bind
實(shí)現(xiàn)call
實(shí)現(xiàn)apply
實(shí)現(xiàn)bind
3.防抖和節(jié)流
防抖
節(jié)流
4.實(shí)現(xiàn)instanceof
5.實(shí)現(xiàn)Ajax
6.深拷貝和淺拷貝
淺拷貝
深拷貝
7.函數(shù)柯里化
參數(shù)定長(zhǎng)的柯里化
參數(shù)不定長(zhǎng)的柯里化
8.數(shù)組扁平化
9.數(shù)組去重
10.手寫類型判斷函數(shù)
?結(jié)語(yǔ)
1.實(shí)現(xiàn)new
(1)首先創(chuàng)建一個(gè)新的空對(duì)象。
(2)設(shè)置原型,將對(duì)象的原型設(shè)置為函數(shù)的prototype對(duì)象。
(3)讓函數(shù)的this指向這個(gè)對(duì)象,執(zhí)行構(gòu)造函數(shù)的代碼。
(4)判斷函數(shù)的返回值類型,如果是值類型,返回創(chuàng)建的對(duì)象。如果是引用類型,就返回這個(gè)引用類型的對(duì)象。
function myNew(constructor, ...args) {// 如果不是一個(gè)函數(shù),就報(bào)錯(cuò)if (typeof constructor !== "function") {throw "myNew function the first param must be a function";}// 基于原型鏈 創(chuàng)建一個(gè)新對(duì)象,繼承構(gòu)造函數(shù)constructor的原型對(duì)象上的屬性let newObj = Object.create(constructor.prototype);// 將newObj作為this,執(zhí)行 constructor ,傳入?yún)?shù)let res = constructor.apply(newObj, args);// 判斷函數(shù)的執(zhí)行結(jié)果是否是對(duì)象,typeof null 也是'object'所以要排除nulllet isObject = typeof res === "newObject" && res !== null;// 判斷函數(shù)的執(zhí)行結(jié)果是否是函數(shù)let isFunction = typeof res === "function";// 如果函數(shù)的執(zhí)行結(jié)果是一個(gè)對(duì)象或函數(shù), 則返回執(zhí)行的結(jié)果, 否則, 返回新創(chuàng)建的對(duì)象return isObject || isFunction ? res : newObj;
}// 用法
function Person(name, age) {this.name = name;this.age = age;// 如果構(gòu)造函數(shù)內(nèi)部,return 一個(gè)引用類型的對(duì)象,則整個(gè)構(gòu)造函數(shù)失效,而是返回這個(gè)引用類型的對(duì)象
}
Person.prototype.say = function() {console.log(this.age);
};
let p1 = myNew(Person, "poety", 18);
console.log(p1.name); //poety
console.log(p1); //Person {name: 'poety', age: 18}
p1.say(); //18
測(cè)試結(jié)果:?
2.call、apply、bind
實(shí)現(xiàn)call
思路:接受傳入的context上下文,如果不傳默認(rèn)為window,將被調(diào)用的方法設(shè)置為上下文的屬性,使用上下文對(duì)象來(lái)調(diào)用這個(gè)方法,刪除新增屬性,返回結(jié)果。
//寫在函數(shù)的原型上
Function.prototype.myCall = function (context) {// 如果要調(diào)用的方法不是一個(gè)函數(shù),則報(bào)錯(cuò)if (typeof this !== "function") {throw new Error("Type error");}// 判斷 context 是否傳入,如果沒(méi)有傳就設(shè)置為 windowcontext = context || window;// 獲取參數(shù),[...arguments]把類數(shù)組轉(zhuǎn)為數(shù)組let args = [...arguments].slice(1);let result = null;// 將被調(diào)用的方法設(shè)置為context的屬性,this即為要調(diào)用的方法context.fn = this;// 執(zhí)行要被調(diào)用的方法result = context.fn(...args);// 刪除手動(dòng)增加的屬性方法delete context.fn;// 將執(zhí)行結(jié)果返回return result;
};//測(cè)試
function f(a,b){console.log(a+b)console.log(this.name)
}
let obj={name:1
}
f.myCall(obj,1,2) // 3,1
測(cè)試結(jié)果:
實(shí)現(xiàn)apply
除了傳參方式是數(shù)組,其它與call沒(méi)區(qū)別。
Function.prototype.myApply = function (context) {if (typeof this !== "function") {throw new Error("Type error");}let result = null;context = context || window;// 與上面代碼相比,我們使用 Symbol 來(lái)保證屬性唯一,也就是保證不會(huì)重寫用戶自己原來(lái)定義在 context 中的同名屬性const fnSymbol = Symbol();context[fnSymbol] = this;// 執(zhí)行要被調(diào)用的方法,處理參數(shù)和 call 有區(qū)別,判斷是否傳了參數(shù)數(shù)組if (arguments[1]) {//傳了參數(shù)數(shù)組result = context[fnSymbol](...arguments[1]);} else {//沒(méi)傳參數(shù)數(shù)組result = context[fnSymbol]();}delete context[fnSymbol];return result;
};//測(cè)試
function f(a,b){console.log(a,b)console.log(this.name)
}
let obj={name:'張三'
}
f.myApply(obj,[1,2]) //1 2,張三
測(cè)試結(jié)果:
實(shí)現(xiàn)bind
bind返回的是一個(gè)函數(shù),需要判斷函數(shù)作為構(gòu)造函數(shù)的情況,當(dāng)作為構(gòu)造函數(shù)時(shí),this 指向?qū)嵗?#xff0c;不會(huì)被任何方式改變 this,所以要忽略傳入的context上下文。
bind可以分開(kāi)傳遞參數(shù),所以需要將參數(shù)拼接。
如果綁定的是構(gòu)造函數(shù),還需要繼承構(gòu)造函數(shù)原型上的屬性和方法,保證不丟失。
Function.prototype.myBind = function (context) {// 判斷調(diào)用對(duì)象是否為函數(shù)if (typeof this !== "function") {throw new Error("Type error");}// 獲取參數(shù)const args = [...arguments].slice(1);const fn = this; // 保存this的值,代表調(diào)用bind的函數(shù)//返回一個(gè)函數(shù),此函數(shù)可以被作為構(gòu)造函數(shù)調(diào)用,也可以作為普通函數(shù)調(diào)用const Fn = function () {// 根據(jù)調(diào)用方式,傳入不同綁定值// 當(dāng)作為構(gòu)造函數(shù)時(shí),this 指向?qū)嵗?#xff0c;不會(huì)被任何方式改變 this,要忽略傳入的context上下文return fn.apply(this instanceof Fn ? this : context,// bind可以分開(kāi)傳遞參數(shù)(如f.bind(obj, 1)(2)),所以需要將參數(shù)拼接,這里使用apply,參數(shù)拼接成一個(gè)數(shù)組args.concat(...arguments)//當(dāng)前的這個(gè) arguments 是指 Fn 的參數(shù),也可以用剩余參數(shù)的方式);};//對(duì)于構(gòu)造函數(shù),要保證原函數(shù)的原型對(duì)象上的屬性不能丟失Fn.prototype = Object.create(fn.prototype);return Fn;
};// 1.先測(cè)試作為構(gòu)造函數(shù)調(diào)用
function Person(name, age) {console.log(name);console.log(age);console.log(this); //構(gòu)造函數(shù)this指向?qū)嵗龑?duì)象
}
// 構(gòu)造函數(shù)原型的方法
Person.prototype.say = function () {console.log("say");
};
var obj = {name: "cc",age: 18,
};
var bindFun = Person.myBind(obj, "cxx");
var a = new bindFun(10);
// cxx
// 10
// Person {}
a.say(); // say// 2.再測(cè)試作為普通函數(shù)調(diào)用
function normalFun(name, age) {console.log(name);console.log(age);console.log(this); // 普通函數(shù)this指向綁定bind的第一個(gè)參數(shù) 也就是例子中的obj
}
var obj = {name: "aa",age: 18,
};
var bindNormalFun = normalFun.myBind(obj, "cc");
bindNormalFun(12);
// cc
// 12
// { name: 'aa', age: 18 }
測(cè)試結(jié)果:?
3.防抖和節(jié)流
防抖
防抖是指在事件被觸發(fā) n 秒后再執(zhí)行回調(diào),如果在這 n 秒內(nèi)事件又被觸發(fā),則重新計(jì)時(shí)。可以使用在一些點(diǎn)擊請(qǐng)求的事件上,避免因?yàn)橛脩舻亩啻吸c(diǎn)擊向后端發(fā)送多次請(qǐng)求。
//fn是需要防抖的函數(shù),delay是等待時(shí)間
function debounce(fn, delay = 500) {let timer = null;// 這里返回的函數(shù)是每次用戶實(shí)際調(diào)用的防抖函數(shù)return function(...args) { //...args是es6的剩余參數(shù)語(yǔ)法,將多余的參數(shù)放入數(shù)組,用來(lái)代替arguments對(duì)象// 如果已經(jīng)設(shè)定過(guò)定時(shí)器了就清空上一次的定時(shí)器if(timer) {clearTimeout(timer); }// 開(kāi)始一個(gè)新的定時(shí)器,延遲執(zhí)行用戶傳入的方法;注:定時(shí)器的返回值是一個(gè)數(shù)值,作為定時(shí)器的編號(hào),可以傳入clearTimeout來(lái)取消定時(shí)器timer = setTimeout(() => { //這里必須是箭頭函數(shù),不然this指向window,要讓this就指向fn的調(diào)用者fn.apply(this, args); }, delay) }
}
節(jié)流
節(jié)流就是一定時(shí)間內(nèi)只執(zhí)行一次事件,即使重復(fù)觸發(fā),也只有一次生效??梢允褂迷诒O(jiān)聽(tīng)滾動(dòng)scroll事件上,通過(guò)事件節(jié)流來(lái)降低事件調(diào)用的頻率。
- 定時(shí)器版本
function throttle(fn, delay = 500) {let timer = null;return function(...args) {// 當(dāng)前有任務(wù)了,直接返回if(timer) {return;}timer = setTimeout(() => {fn.apply(this, args);//執(zhí)行完后,需重置定時(shí)器,不然timer一直有值,無(wú)法開(kāi)啟下一個(gè)定時(shí)器timer = null; }, delay)}
}
- 時(shí)間戳版本
// 節(jié)流
function throttle(fn, delay = 500) {let prev = Date.now();// 上一次執(zhí)行該函數(shù)的時(shí)間return function(...args) {let now = Date.now();//返回從UTC到當(dāng)前時(shí)間的毫秒數(shù)// 如果差值大于等于設(shè)置的等待時(shí)間就執(zhí)行函數(shù)if (now - prev >= delay) {fn.apply(this, args);prev = Date.now();}};
}
詳細(xì)可以去看我的這篇文章——前端性能優(yōu)化之防抖&節(jié)流
4.實(shí)現(xiàn)instanceof
instanceof用于檢測(cè)構(gòu)造函數(shù)的prototype
屬性是否出現(xiàn)在某個(gè)實(shí)例對(duì)象的原型鏈上。
function myInstanceof(instance, constructor) {//如果不是對(duì)象,或者是null,直接返回falseif (typeof instance !== "object" || instance === null) {return false;}// 獲取對(duì)象的原型let proto = Object.getPrototypeOf(instance);// 獲取構(gòu)造函數(shù)的 prototype 對(duì)象let prototype = constructor.prototype;// 判斷構(gòu)造函數(shù)的 prototype對(duì)象是否在對(duì)象的原型鏈上while (true) {// 到達(dá)原型鏈終點(diǎn)null,說(shuō)明沒(méi)找到if (!proto) {return false;}if (proto === prototype) {return true;}// 如果沒(méi)有找到,就繼續(xù)從其原型上找proto = Object.getPrototypeOf(proto);}
}//測(cè)試
let Fn = function () { }
let p1 = new Fn()
console.log(myInstanceof(p1, Fn));//true
console.log(myInstanceof([], Fn));//false
console.log(myInstanceof([], Array)) // true
console.log(myInstanceof(function(){}, Function)) // true
測(cè)試結(jié)果:?
5.實(shí)現(xiàn)Ajax
(1)創(chuàng)建一個(gè) XMLHttpRequest 對(duì)象。
(2)在這個(gè)對(duì)象上使用 open 方法創(chuàng)建一個(gè) HTTP 請(qǐng)求(參數(shù)為請(qǐng)求方法、請(qǐng)求地址、是否異步和用戶的認(rèn)證信息)。
(3)通過(guò)send方法來(lái)向服務(wù)器發(fā)起請(qǐng)求(post請(qǐng)求可以入?yún)?shù)作為發(fā)送的數(shù)據(jù)體)。
(4)監(jiān)聽(tīng)請(qǐng)求成功后的狀態(tài)變化:根據(jù)狀態(tài)碼進(jìn)行相應(yīng)的處理。onreadystatechange設(shè)置監(jiān)聽(tīng)函數(shù),當(dāng)對(duì)象的readyState變?yōu)?的時(shí)候,代表服務(wù)器返回的數(shù)據(jù)接收完成,這個(gè)時(shí)候可以通過(guò)判斷請(qǐng)求的狀態(tài),如果狀態(tài)是200則代表成功,404或500代表失敗。
function ajax(url) {//1.創(chuàng)建XMLHttpRequest對(duì)象const xhr = new XMLHttpRequest();//2.使用open方法創(chuàng)建一個(gè)GET請(qǐng)求xhr.open('GET',url);//xhr.open('GET',url,true);//true代表異步,已完成事務(wù)的通知可供事件監(jiān)聽(tīng)器使用;如果為false,send() 方法直到收到答復(fù)前不會(huì)返回//3.發(fā)送請(qǐng)求xhr.send(); //4.監(jiān)聽(tīng)請(qǐng)求成功后的狀態(tài)變化(readyState改變時(shí)觸發(fā)):根據(jù)狀態(tài)碼(0~5)進(jìn)行相應(yīng)的處理xhr.onreadystatechange = function () {//readyState為4代表服務(wù)器返回的數(shù)據(jù)接收完成if (xhr.readyState == 4) { //請(qǐng)求的狀態(tài)為200或304代表成功if(xhr.status == 200 || xhr.status == 304) {//this.response代表返回的數(shù)據(jù)handle(this.response);} else {//this.statusText代表返回的文本信息console.error(this.statusText);}}};
}
使用Promise封裝AJAX:
function ajax(url) {return new Promise((resolve, reject) => {let xhr = new XMLHttpRequest()xhr.open('get', url)xhr.send() xhr.onreadystatechange = () => {if (xhr.readyState == 4) {if (xhr.status == 200 || xhr.status == 304) {resolve(this.response)} else {reject(new Error(this.statusText));}}}})
}
//使用
let url = '/data.json'
ajax(url).then(res => console.log(res)).catch(reason => console.log(reason))
6.深拷貝和淺拷貝
淺拷貝
淺拷貝是創(chuàng)建一個(gè)新對(duì)象,這個(gè)對(duì)象有著原始對(duì)象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的就是內(nèi)存地址 ,所以如果其中一個(gè)對(duì)象改變了這個(gè)地址,就會(huì)影響到另一個(gè)對(duì)象。
//實(shí)現(xiàn)淺拷貝
function shallowCopy (obj){// 只拷貝對(duì)象,基本類型或null直接返回if(typeof obj !== 'object' || obj === null) {return obj;}// 判斷是新建一個(gè)數(shù)組還是對(duì)象let newObj = Array.isArray(obj) ? []: {};//for…in會(huì)遍歷對(duì)象的整個(gè)原型鏈,如果只考慮對(duì)象本身的屬性,需要搭配hasOwnPropertyfor(let key in obj ){//hasOwnProperty判斷是否是對(duì)象自身屬性,會(huì)忽略從原型鏈上繼承的屬性if(obj.hasOwnProperty(key)){newObj[key] = obj[key];//只拷貝對(duì)象本身的屬性}}return newObj;
}//測(cè)試
var obj ={name:'張三',age:8,pal:['王五','王六','王七']
}
let obj2 = shallowCopy(obj);
obj2.name = '李四'
obj2.pal[0] = '王麻子'
console.log(obj); //{age: 8, name: "張三", pal: ['王麻子', '王六', '王七']}
console.log(obj2); //{age: 8, name: "李四", pal: ['王麻子', '王六', '王七']}
測(cè)試結(jié)果:?
深拷貝
深拷貝是將一個(gè)對(duì)象從內(nèi)存中完整的拷貝一份出來(lái),從堆內(nèi)存中開(kāi)辟一個(gè)新的區(qū)域存放新對(duì)象,且修改新對(duì)象不會(huì)影響原對(duì)象。
function deepCopy (obj, map = new WeakMap()){// 基本類型或null直接返回if(typeof obj !== 'object' || obj === null) {return obj;}// 判斷是新建一個(gè)數(shù)組還是對(duì)象let newObj = Array.isArray(obj) ? []: {};//利用map解決循環(huán)引用if (map.has(obj)) {return map.get(obj);}map.set(obj, newObj);//將當(dāng)前對(duì)象作為key,克隆對(duì)象作為valuefor(let key in obj ){ if(obj.hasOwnProperty(key)){newObj[key] = deepCopy(obj[key], map); //遞歸}}return newObj
}// 測(cè)試
let obj1 = {name : '南木元元',arr : [1,[2,3],4],
};
let obj2=deepCopy(obj1)
obj2.name = "阿元";
obj2.arr[1] = [5,6,7] ; // 新對(duì)象跟原對(duì)象不共享內(nèi)存console.log('obj1',obj1) // obj1 { name: '南木元元', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj2',obj2) // obj2 { name: '阿元', arr: [ 1, [ 5, 6, 7 ], 4 ] }
測(cè)試結(jié)果:
7.函數(shù)柯里化
函數(shù)柯里化指的是一種將使用多個(gè)參數(shù)的一個(gè)函數(shù)轉(zhuǎn)換成一系列使用一個(gè)參數(shù)的函數(shù)的技術(shù)。
作用:可以參數(shù)復(fù)用(公共的參數(shù)已經(jīng)通過(guò)柯里化預(yù)置了)和延遲執(zhí)行(柯里化時(shí)只是返回一個(gè)預(yù)置參數(shù)的新函數(shù),并沒(méi)有立刻執(zhí)行,在滿足條件后才會(huì)執(zhí)行)。
參數(shù)定長(zhǎng)的柯里化
思路:通過(guò)函數(shù)的length屬性獲取函數(shù)的形參個(gè)數(shù)?????,形參的個(gè)數(shù)就是所需參數(shù)的個(gè)數(shù)。維護(hù)一個(gè)數(shù)組,當(dāng)數(shù)組的長(zhǎng)度與函數(shù)接收參數(shù)的個(gè)數(shù)一致,再執(zhí)行該函數(shù)。
// 實(shí)現(xiàn)函數(shù)柯里化
function curry(fn) {// 返回一個(gè)新函數(shù)return function curried(...args) {if (args.length >= fn.length) {return fn.apply(this, args); // 如果參數(shù)夠了,就執(zhí)行原函數(shù),返回結(jié)果} else {//返回一個(gè)新函數(shù),繼續(xù)遞歸去進(jìn)行柯里化,利用閉包,將當(dāng)前已經(jīng)傳入的參數(shù)保存下來(lái)return function (...args2) {//遞歸調(diào)用 curried 函數(shù)return curried.apply(this, [...args, ...args2]); //新函數(shù)調(diào)用時(shí)會(huì)繼續(xù)傳參,拼接參數(shù)};}};
}// 測(cè)試
function sum(a, b, c) {return a + b + c;
}
var curried = curry(sum);
console.log(curried(1, 2, 3)); //6
console.log(curried(1, 2)(3)); //6
console.log(curried(1)(2, 3)); //6
console.log(curried(1)(2)(3)); //6
測(cè)試結(jié)果:
參數(shù)不定長(zhǎng)的柯里化
題目:如何實(shí)現(xiàn)一個(gè)方法,使計(jì)算結(jié)果能夠滿足如下預(yù)期。
add(1, 2, 3) // 6
add(1) // 1
add(1)(2) // 3
add(1, 2)(3) // 6
add(1)(2)(3) // 6
add(1)(2)(3)(4) // 10
思路:利用閉包和遞歸,如果參數(shù)為空,則判斷遞歸結(jié)束,求和,返回結(jié)果。
function addCurry() {// 利用閉包的特性收集所有參數(shù)值let arr = [...arguments] //返回函數(shù)return function fn() {// 如果參數(shù)為空,則判斷遞歸結(jié)束,即傳入一個(gè)()執(zhí)行函數(shù)if(arguments.length === 0) { return arr.reduce((a, b) => a + b) // 求和} else {arr.push(...arguments)return fn //遞歸}}
}// 測(cè)試
console.log(addCurry(1)()); //1
console.log(addCurry(1)(2)()); //3
console.log(addCurry(1)(2)(3)()); //6
console.log(addCurry(1, 2)(3)()); //6
console.log(addCurry(1, 2, 3)()); //6
上述寫法,總是要以空括號(hào)()結(jié)尾,于是再改進(jìn)為隱式轉(zhuǎn)換.toString
寫法,原理:當(dāng)用 Function的值做計(jì)算的時(shí)候,會(huì)調(diào)用toString做隱式轉(zhuǎn)換。注意一些舊版本的瀏覽器隱式轉(zhuǎn)換會(huì)默認(rèn)執(zhí)行,新版本不行了??梢岳秒[式轉(zhuǎn)換或者alert。
function addCurry() {let arr = [...arguments]// 利用閉包的特性收集所有參數(shù)值var fn = function() {arr.push(...arguments);return fn;//遞歸};// 利用 toString 隱式轉(zhuǎn)換,轉(zhuǎn)換的時(shí)候再返回結(jié)果fn.toString = function () {return arr.reduce(function (a, b) {return a + b;});}return fn;
}//測(cè)試
console.log(addCurry(1)(2) == 3) //true 利用隱式轉(zhuǎn)換,自動(dòng)調(diào)用toString方法得到柯里化的結(jié)果
//alert(addCurry(1)(2)(3))//6 alert參數(shù)只能是字符串,如果其他類型的值,會(huì)轉(zhuǎn)換成字符串,會(huì)調(diào)用toString方法
console.log(addCurry(1).toString());//1 手動(dòng)調(diào)用toString
console.log(addCurry(1, 2)(3).toString());//6
console.log(addCurry(1, 2)(3)(4)(5).toString());//15
測(cè)試結(jié)果:
8.數(shù)組扁平化
數(shù)組扁平化其實(shí)就是將多維數(shù)組轉(zhuǎn)為一維數(shù)組。
(1)ES6中的flat
const arr = [1,[2,[3,[4,5]]],6]
// arr.flat([depth]) flat的參數(shù)代表的是需要展開(kāi)幾層,如果是Infinity的話,就是不管嵌套幾層,全部都展開(kāi)
console.log(arr.flat(Infinity)) //[1,2,3,4,5,6]
(2)遞歸
let arr = [1, [2, [3, 4]]];
function flatten(arr) {let result = [];for (let i = 0; i < arr.length; i++) {//如果當(dāng)前元素還是一個(gè)數(shù)組if (Array.isArray(arr[i])) {result = result.concat(flatten(arr[i]));//遞歸拼接} else {result.push(arr[i]);}}return result;
}
console.log(flatten(arr)); // [1, 2, 3, 4]
(3)reduce函數(shù)迭代
從上面普通的遞歸函數(shù)中可以看出,其實(shí)就是對(duì)數(shù)組的每一項(xiàng)進(jìn)行處理,那么其實(shí)也可以用reduce來(lái)實(shí)現(xiàn)數(shù)組的拼接,從而簡(jiǎn)化第一種方法的代碼。
let arr = [1, [2, [3, 4]]];
function flatten(arr) {return arr.reduce((total, cur) => {return total.concat(Array.isArray(cur) ? flatten(cur) : cur);}, []); //傳遞初始值空數(shù)組[],就會(huì)從數(shù)組索引為 0 的元素開(kāi)始執(zhí)行
}
console.log(flatten(arr)); // [1, 2, 3, 4]
(4)split和toString
數(shù)組的toString
方法可以把數(shù)組直接轉(zhuǎn)換成逗號(hào)分隔的字符串。如[1, [2, [3, 4]]] => "1,2,3,4"
let arr = [1, [2, [3, 4]]];
function flatten(arr) {//先把數(shù)組直接轉(zhuǎn)換成逗號(hào)分隔的字符串,然后再用 split 方法把字符串重新轉(zhuǎn)換為數(shù)組return arr.toString().split(",").map(Number);
}
console.log(flatten(arr)); // [ 1, 2, 3, 4 ]
9.數(shù)組去重
(1)利用Set。new一個(gè)Set,參數(shù)為需要去重的數(shù)組,Set 會(huì)自動(dòng)刪除重復(fù)的元素,再Array.from將 Set 轉(zhuǎn)為數(shù)組返回。
const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log([...new Set(arr)]); //[ 1, 2, 3, 5, 9, 8 ]
console.log(Array.from(new Set(arr))); //[ 1, 2, 3, 5, 9, 8 ]
(2)利用數(shù)組的filter() + indexOf去重。利用filter方法,返回arr.indexOf(num)等于index的值。原理就是indexOf會(huì)返回最先找到的數(shù)字的索引。
function unique(arr) {return arr.filter((item, index, array) => {return array.indexOf(item) === index;});
}
const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log(unique(arr)); // [1, 2, 3, 5, 9, 8]
(3)利用map。新建一個(gè)數(shù)組和map,如果當(dāng)前值在map中沒(méi)有出現(xiàn)過(guò),就加入數(shù)組,最后返回?cái)?shù)組。
const unique = (arr) => {const map = new Map();const res = [];for (let item of arr) {if (!map.has(item)) {map.set(item, true);res.push(item);}}return res;
}
const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log(unique(arr)); // [1, 2, 3, 5, 9, 8]
10.手寫類型判斷函數(shù)
思路:如果是null,直接返回String(null);基本類型和函數(shù),直接使用typeof;
其它引用類型,使用Object.prototype.toString.call。
function getType(value) {// 判斷數(shù)據(jù)是 null 的情況if (value === null) {return String(value);}// 判斷數(shù)據(jù)是基本數(shù)據(jù)類型的情況和函數(shù)的情況,使用typeofif (typeof value !== "object") {return typeof value;} else {// 判斷數(shù)據(jù)是引用類型的情況,設(shè)當(dāng)前類型為datelet valueClass = Object.prototype.toString.call(value); //"[object Date]"type = valueClass.split(" ")[1].split(""); //[ 'D', 'a', 't', 'e', ']' ] 截取類型并轉(zhuǎn)換為數(shù)組type.pop(); //[ 'D', 'a', 't', 'e' ],去掉數(shù)組最后的右括號(hào)"]"return type.join("").toLowerCase(); //[ 'D', 'a', 't', 'e' ] => "Date" => "date" 數(shù)組轉(zhuǎn)小寫字符串}
}// 測(cè)試
console.info(getType(null)); // null
console.info(getType(undefined)); // undefined
console.info(getType(100)); // number
console.info(getType("abc")); // string
console.info(getType(true)); // boolean
console.info(getType(Symbol())); // symbol
console.info(getType({})); // object
console.info(getType([])); // array
console.info(getType(() => {})); // function
console.info(getType(new Date())); // date
console.info(getType(new RegExp(""))); // regexp
console.info(getType(new Map())); // map
console.info(getType(new Set())); // set
console.info(getType(new WeakMap())); // weakmap
console.info(getType(new WeakSet())); // weakset
console.info(getType(new Error())); // error
console.info(getType(new Promise(() => {}))); // promise
測(cè)試結(jié)果:
結(jié)語(yǔ)
本文總結(jié)了前端常見(jiàn)的一些手寫功能,你是不是全都掌握了呢,歡迎在評(píng)論區(qū)交流。
🔥如果此文對(duì)你有幫助的話,歡迎💗關(guān)注、👍點(diǎn)贊、?收藏、??評(píng)論,支持一下博主~?????