實(shí)現(xiàn)forEach方法
Array.prototype.myForEach = function(callback, context=window) {
// this=>arr
let self = this,
i = 0,
len = self.length;
for(;i<len;i++) {
typeof callback == 'function' && callback.call(context,self[i], i)
}
}
修改嵌套層級(jí)很深對(duì)象的 key
// 有一個(gè)嵌套層次很深的對(duì)象奴紧,key 都是 a_b 形式 ,需要改成 ab 的形式,注意不能用遞歸。
const a = {
a_y: {
a_z: {
y_x: 6
},
b_c: 1
}
}
// {
// ay: {
// az: {
// yx: 6
// },
// bc: 1
// }
// }
方法1:序列化 JSON.stringify + 正則匹配
const regularExpress = (obj) => {
try {
const str = JSON.stringify(obj).replace(/_/g, "");
return JSON.parse(str);
} catch (error) {
return obj;
}
};;
方法2:遞歸
const recursion = (obj) => {
const keys = Object.keys(obj);
keys.forEach((key) => {
const newKey = key.replace(/_/g, "");
obj[newKey] = recursion(obj[key]);
delete obj[key];
});
return obj;
};
數(shù)組中的數(shù)據(jù)根據(jù)key去重
給定一個(gè)任意數(shù)組款违,實(shí)現(xiàn)一個(gè)通用函數(shù),讓數(shù)組中的數(shù)據(jù)根據(jù) key 排重:
const dedup = (data, getKey = () => {} ) => {
// todo
}
let data = [
{ id: 1, v: 1 },
{ id: 2, v: 2 },
{ id: 1, v: 1 },
];
// 以 id 作為排重 key群凶,執(zhí)行函數(shù)得到結(jié)果
// data = [
// { id: 1, v: 1 },
// { id: 2, v: 2 },
// ];
實(shí)現(xiàn)
const dedup = (data, getKey = () => { }) => {
const dateMap = data.reduce((pre, cur) => {
const key = getKey(cur)
if (!pre[key]) {
pre[key] = cur
}
return pre
}, {})
return Object.values(dateMap)
}
使用
let data = [
{ id: 1, v: 1 },
{ id: 2, v: 2 },
{ id: 1, v: 1 },
];
console.log(dedup(data, (item) => item.id))
// 以 id 作為排重 key插爹,執(zhí)行函數(shù)得到結(jié)果
// data = [
// { id: 1, v: 1 },
// { id: 2, v: 2 },
// ];
實(shí)現(xiàn)節(jié)流函數(shù)(throttle)
節(jié)流函數(shù)原理:指頻繁觸發(fā)事件時(shí),只會(huì)在指定的時(shí)間段內(nèi)執(zhí)行事件回調(diào)请梢,即觸發(fā)事件間隔大于等于指定的時(shí)間才會(huì)執(zhí)行回調(diào)函數(shù)赠尾。總結(jié)起來就是: 事件溢陪,按照一段時(shí)間的間隔來進(jìn)行觸發(fā) 萍虽。
[圖片上傳失敗...(image-fc5918-1663910059294)]
像dom的拖拽睛廊,如果用消抖的話形真,就會(huì)出現(xiàn)卡頓的感覺,因?yàn)橹辉谕V沟臅r(shí)候執(zhí)行了一次超全,這個(gè)時(shí)候就應(yīng)該用節(jié)流咆霜,在一定時(shí)間內(nèi)多次執(zhí)行,會(huì)流暢很多
手寫簡(jiǎn)版
使用時(shí)間戳的節(jié)流函數(shù)會(huì)在第一次觸發(fā)事件時(shí)立即執(zhí)行嘶朱,以后每過 wait 秒之后才執(zhí)行一次蛾坯,并且最后一次觸發(fā)事件不會(huì)被執(zhí)行
時(shí)間戳方式:
// func是用戶傳入需要防抖的函數(shù)
// wait是等待時(shí)間
const throttle = (func, wait = 50) => {
// 上一次執(zhí)行該函數(shù)的時(shí)間
let lastTime = 0
return function(...args) {
// 當(dāng)前時(shí)間
let now = +new Date()
// 將當(dāng)前時(shí)間和上一次執(zhí)行函數(shù)時(shí)間對(duì)比
// 如果差值大于設(shè)置的等待時(shí)間就執(zhí)行函數(shù)
if (now - lastTime > wait) {
lastTime = now
func.apply(this, args)
}
}
}
setInterval(
throttle(() => {
console.log(1)
}, 500),
1
)
定時(shí)器方式:
使用定時(shí)器的節(jié)流函數(shù)在第一次觸發(fā)時(shí)不會(huì)執(zhí)行,而是在 delay 秒之后才執(zhí)行疏遏,當(dāng)最后一次停止觸發(fā)后脉课,還會(huì)再執(zhí)行一次函數(shù)
function throttle(func, delay){
var timer = null;
returnfunction(){
var context = this;
var args = arguments;
if(!timer){
timer = setTimeout(function(){
func.apply(context, args);
timer = null;
},delay);
}
}
}
適用場(chǎng)景:
-
DOM
元素的拖拽功能實(shí)現(xiàn)(mousemove
) - 搜索聯(lián)想(
keyup
) - 計(jì)算鼠標(biāo)移動(dòng)的距離(
mousemove
) -
Canvas
模擬畫板功能(mousemove
) - 監(jiān)聽滾動(dòng)事件判斷是否到頁面底部自動(dòng)加載更多
- 拖拽場(chǎng)景:固定時(shí)間內(nèi)只執(zhí)行一次救军,防止超高頻次觸發(fā)位置變動(dòng)
- 縮放場(chǎng)景:監(jiān)控瀏覽器
resize
- 動(dòng)畫場(chǎng)景:避免短時(shí)間內(nèi)多次觸發(fā)動(dòng)畫引起性能問題
總結(jié)
- 函數(shù)防抖 :將幾次操作合并為一次操作進(jìn)行。原理是維護(hù)一個(gè)計(jì)時(shí)器倘零,規(guī)定在delay時(shí)間后觸發(fā)函數(shù)唱遭,但是在delay時(shí)間內(nèi)再次觸發(fā)的話,就會(huì)取消之前的計(jì)時(shí)器而重新設(shè)置呈驶。這樣一來拷泽,只有最后一次操作能被觸發(fā)。
- 函數(shù)節(jié)流 :使得一定時(shí)間內(nèi)只觸發(fā)一次函數(shù)袖瞻。原理是通過判斷是否到達(dá)一定時(shí)間來觸發(fā)函數(shù)司致。
基于Generator函數(shù)實(shí)現(xiàn)async/await原理
核心:傳遞給我一個(gè)
Generator
函數(shù),把函數(shù)中的內(nèi)容基于Iterator
迭代器的特點(diǎn)一步步的執(zhí)行
function readFile(file) {
return new Promise(resolve => {
setTimeout(() => {
resolve(file);
}, 1000);
})
};
function asyncFunc(generator) {
const iterator = generator(); // 接下來要執(zhí)行next
// data為第一次執(zhí)行之后的返回結(jié)果聋迎,用于傳給第二次執(zhí)行
const next = (data) => {
let { value, done } = iterator.next(data); // 第二次執(zhí)行脂矫,并接收第一次的請(qǐng)求結(jié)果 data
if (done) return; // 執(zhí)行完畢(到第三次)直接返回
// 第一次執(zhí)行next時(shí),yield返回的 promise實(shí)例 賦值給了 value
value.then(data => {
next(data); // 當(dāng)?shù)谝淮蝪alue 執(zhí)行完畢且成功時(shí)霉晕,執(zhí)行下一步(并把第一次的結(jié)果傳遞下一步)
});
}
next();
};
asyncFunc(function* () {
// 生成器函數(shù):控制代碼一步步執(zhí)行
let data = yield readFile('a.js'); // 等這一步驟執(zhí)行執(zhí)行成功之后羹唠,再往下走,沒執(zhí)行完的時(shí)候娄昆,直接返回
data = yield readFile(data + 'b.js');
return data;
})
實(shí)現(xiàn)模板字符串解析功能
let template = '我是{{name}}佩微,年齡{{age}},性別{{sex}}';
let data = {
name: '姓名',
age: 18
}
render(template, data); // 我是姓名萌焰,年齡18哺眯,性別undefined
function render(template, data) {
const reg = /\{\{(\w+)\}\}/; // 模板字符串正則
if (reg.test(template)) { // 判斷模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找當(dāng)前模板里第一個(gè)模板字符串的字段
template = template.replace(reg, data[name]); // 將第一個(gè)模板字符串渲染
return render(template, data); // 遞歸的渲染并返回渲染后的結(jié)構(gòu)
}
return template; // 如果模板沒有模板字符串直接返回
}
AJAX
const getJSON = function(url) {
return new Promise((resolve, reject) => {
const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
xhr.open('GET', url, false);
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) return;
if (xhr.status === 200 || xhr.status === 304) {
resolve(xhr.responseText);
} else {
reject(new Error(xhr.responseText));
}
}
xhr.send();
})
}
實(shí)現(xiàn)雙向數(shù)據(jù)綁定
let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 數(shù)據(jù)劫持
Object.defineProperty(obj, 'text', {
configurable: true,
enumerable: true,
get() {
console.log('獲取數(shù)據(jù)了')
},
set(newVal) {
console.log('數(shù)據(jù)更新了')
input.value = newVal
span.innerHTML = newVal
}
})
// 輸入監(jiān)聽
input.addEventListener('keyup', function(e) {
obj.text = e.target.value
})
實(shí)現(xiàn)數(shù)組的扁平化
(1)遞歸實(shí)現(xiàn)
普通的遞歸思路很容易理解,就是通過循環(huán)遞歸的方式扒俯,一項(xiàng)一項(xiàng)地去遍歷奶卓,如果每一項(xiàng)還是一個(gè)數(shù)組,那么就繼續(xù)往下遍歷撼玄,利用遞歸程序的方法夺姑,來實(shí)現(xiàn)數(shù)組的每一項(xiàng)的連接:
let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
flatten(arr); // [1, 2, 3, 4,5]
(2)reduce 函數(shù)迭代
從上面普通的遞歸函數(shù)中可以看出掌猛,其實(shí)就是對(duì)數(shù)組的每一項(xiàng)進(jìn)行處理盏浙,那么其實(shí)也可以用reduce 來實(shí)現(xiàn)數(shù)組的拼接,從而簡(jiǎn)化第一種方法的代碼荔茬,改造后的代碼如下所示:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.reduce(function(prev, next){
return prev.concat(Array.isArray(next) ? flatten(next) : next)
}, [])
}
console.log(flatten(arr));// [1, 2, 3, 4废膘,5]
(3)擴(kuò)展運(yùn)算符實(shí)現(xiàn)
這個(gè)方法的實(shí)現(xiàn),采用了擴(kuò)展運(yùn)算符和 some 的方法慕蔚,兩者共同使用丐黄,達(dá)到數(shù)組扁平化的目的:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
(4)split 和 toString
可以通過 split 和 toString 兩個(gè)方法來共同實(shí)現(xiàn)數(shù)組扁平化孔飒,由于數(shù)組會(huì)默認(rèn)帶一個(gè) toString 的方法灌闺,所以可以把數(shù)組直接轉(zhuǎn)換成逗號(hào)分隔的字符串艰争,然后再用 split 方法把字符串重新轉(zhuǎn)換為數(shù)組,如下面的代碼所示:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.toString().split(',');
}
console.log(flatten(arr)); // [1, 2, 3, 4桂对,5]
通過這兩個(gè)方法可以將多維數(shù)組直接轉(zhuǎn)換成逗號(hào)連接的字符串园细,然后再重新分隔成數(shù)組。
(5)ES6 中的 flat
我們還可以直接調(diào)用 ES6 中的 flat 方法來實(shí)現(xiàn)數(shù)組扁平化接校。flat 方法的語法:arr.flat([depth])
其中 depth 是 flat 的參數(shù)猛频,depth 是可以傳遞數(shù)組的展開深度(默認(rèn)不填、數(shù)值是 1)蛛勉,即展開一層數(shù)組鹿寻。如果層數(shù)不確定,參數(shù)可以傳進(jìn) Infinity诽凌,代表不論多少層都要展開:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.flat(Infinity);
}
console.log(flatten(arr)); // [1, 2, 3, 4毡熏,5]
可以看出,一個(gè)嵌套了兩層的數(shù)組侣诵,通過將 flat 方法的參數(shù)設(shè)置為 Infinity痢法,達(dá)到了我們預(yù)期的效果。其實(shí)同樣也可以設(shè)置成 2杜顺,也能實(shí)現(xiàn)這樣的效果财搁。在編程過程中,如果數(shù)組的嵌套層數(shù)不確定躬络,最好直接使用 Infinity尖奔,可以達(dá)到扁平化。 (6)正則和 JSON 方法 在第4種方法中已經(jīng)使用 toString 方法穷当,其中仍然采用了將 JSON.stringify 的方法先轉(zhuǎn)換為字符串提茁,然后通過正則表達(dá)式過濾掉字符串中的數(shù)組的方括號(hào),最后再利用 JSON.parse 把它轉(zhuǎn)換成數(shù)組:
let arr = [1, [2, [3, [4, 5]]], 6];
function flatten(arr) {
let str = JSON.stringify(arr);
str = str.replace(/(\[|\])/g, '');
str = '[' + str + ']';
return JSON.parse(str);
}
console.log(flatten(arr)); // [1, 2, 3, 4馁菜,5]
實(shí)現(xiàn)apply方法
思路: 利用
this
的上下文特性茴扁。apply
其實(shí)就是改一下參數(shù)的問題
Function.prototype.myApply = function(context = window, args) {
// this-->func context--> obj args--> 傳遞過來的參數(shù)
// 在context上加一個(gè)唯一值不影響context上的屬性
let key = Symbol('key')
context[key] = this; // context為調(diào)用的上下文,this此處為函數(shù),將這個(gè)函數(shù)作為context的方法
// let args = [...arguments].slice(1) //第一個(gè)參數(shù)為obj所以刪除,偽數(shù)組轉(zhuǎn)為數(shù)組
let result = context[key](...args); // 這里和call傳參不一樣
// 清除定義的this 不刪除會(huì)導(dǎo)致context屬性越來越多
delete context[key];
// 返回結(jié)果
return result;
}
// 使用
function f(a,b){
console.log(a,b)
console.log(this.name)
}
let obj={
name:'張三'
}
f.myApply(obj,[1,2]) //arguments[1]
數(shù)組去重方法匯總
首先:我知道多少種去重方式
1. 雙層 for 循環(huán)
function distinct(arr) {
for (let i=0, len=arr.length; i<len; i++) {
for (let j=i+1; j<len; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1);
// splice 會(huì)改變數(shù)組長(zhǎng)度汪疮,所以要將數(shù)組長(zhǎng)度 len 和下標(biāo) j 減一
len--;
j--;
}
}
}
return arr;
}
思想: 雙重
for
循環(huán)是比較笨拙的方法峭火,它實(shí)現(xiàn)的原理很簡(jiǎn)單:先定義一個(gè)包含原始數(shù)組第一個(gè)元素的數(shù)組,然后遍歷原始數(shù)組铲咨,將原始數(shù)組中的每個(gè)元素與新數(shù)組中的每個(gè)元素進(jìn)行比對(duì)躲胳,如果不重復(fù)則添加到新數(shù)組中,最后返回新數(shù)組纤勒;因?yàn)樗臅r(shí)間復(fù)雜度是O(n^2)
,如果數(shù)組長(zhǎng)度很大隆檀,效率會(huì)很低
2. Array.filter() 加 indexOf/includes
function distinct(a, b) {
let arr = a.concat(b);
return arr.filter((item, index)=> {
//return arr.indexOf(item) === index
return arr.includes(item)
})
}
思想: 利用
indexOf
檢測(cè)元素在數(shù)組中第一次出現(xiàn)的位置是否和元素現(xiàn)在的位置相等摇天,如果不等則說明該元素是重復(fù)元素
3. ES6 中的 Set 去重
function distinct(array) {
return Array.from(new Set(array));
}
思想: ES6 提供了新的數(shù)據(jù)結(jié)構(gòu) Set粹湃,Set 結(jié)構(gòu)的一個(gè)特性就是成員值都是唯一的,沒有重復(fù)的值泉坐。
4. reduce 實(shí)現(xiàn)對(duì)象數(shù)組去重復(fù)
var resources = [
{ name: "張三", age: "18" },
{ name: "張三", age: "19" },
{ name: "張三", age: "20" },
{ name: "李四", age: "19" },
{ name: "王五", age: "20" },
{ name: "趙六", age: "21" }
]
var temp = {};
resources = resources.reduce((prev, curv) => {
// 如果臨時(shí)對(duì)象中有這個(gè)名字为鳄,什么都不做
if (temp[curv.name]) {
}else {
// 如果臨時(shí)對(duì)象沒有就把這個(gè)名字加進(jìn)去,同時(shí)把當(dāng)前的這個(gè)對(duì)象加入到prev中
temp[curv.name] = true;
prev.push(curv);
}
return prev
}, []);
console.log("結(jié)果", resources);
這種方法是利用高階函數(shù)
reduce
進(jìn)行去重腕让, 這里只需要注意initialValue
得放一個(gè)空數(shù)組[]孤钦,不然沒法push
實(shí)現(xiàn) add(1)(2)(3)
函數(shù)柯里化概念: 柯里化(Currying)是把接受多個(gè)參數(shù)的函數(shù)轉(zhuǎn)變?yōu)榻邮芤粋€(gè)單一參數(shù)的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)纯丸。
1)粗暴版
function add (a) {
return function (b) {
return function (c) {
return a + b + c;
}
}
}
console.log(add(1)(2)(3)); // 6
2)柯里化解決方案
- 參數(shù)長(zhǎng)度固定
var add = function (m) {
var temp = function (n) {
return add(m + n);
}
temp.toString = function () {
return m;
}
return temp;
};
console.log(add(3)(4)(5)); // 12
console.log(add(3)(6)(9)(25)); // 43
對(duì)于add(3)(4)(5)偏形,其執(zhí)行過程如下:
先執(zhí)行add(3),此時(shí)m=3觉鼻,并且返回temp函數(shù)俊扭;
執(zhí)行temp(4),這個(gè)函數(shù)內(nèi)執(zhí)行add(m+n)坠陈,n是此次傳進(jìn)來的數(shù)值4萨惑,m值還是上一步中的3,所以add(m+n)=add(3+4)=add(7)仇矾,此時(shí)m=7庸蔼,并且返回temp函數(shù)
執(zhí)行temp(5),這個(gè)函數(shù)內(nèi)執(zhí)行add(m+n)贮匕,n是此次傳進(jìn)來的數(shù)值5朱嘴,m值還是上一步中的7,所以add(m+n)=add(7+5)=add(12)粗合,此時(shí)m=12萍嬉,并且返回temp函數(shù)
由于后面沒有傳入?yún)?shù),等于返回的temp函數(shù)不被執(zhí)行而是打印隙疚,了解JS的朋友都知道對(duì)象的toString是修改對(duì)象轉(zhuǎn)換字符串的方法壤追,因此代碼中temp函數(shù)的toString函數(shù)return m值,而m值是最后一步執(zhí)行函數(shù)時(shí)的值m=12供屉,所以返回值是12行冰。
- 參數(shù)長(zhǎng)度不固定
function add (...args) {
//求和
return args.reduce((a, b) => a + b)
}
function currying (fn) {
let args = []
return function temp (...newArgs) {
if (newArgs.length) {
args = [
...args,
...newArgs
]
return temp
} else {
let val = fn.apply(this, args)
args = [] //保證再次調(diào)用時(shí)清空
return val
}
}
}
let addCurry = currying(add)
console.log(addCurry(1)(2)(3)(4, 5)()) //15
console.log(addCurry(1)(2)(3, 4, 5)()) //15
console.log(addCurry(1)(2, 3, 4, 5)()) //15
實(shí)現(xiàn)some方法
Array.prototype.mySome=function(callback, context = window){
var len = this.length,
flag=false,
i = 0;
for(;i < len; i++){
if(callback.apply(context, [this[i], i , this])){
flag=true;
break;
}
}
return flag;
}
// var flag=arr.mySome((v,index,arr)=>v.num>=10,obj)
// console.log(flag);
使用 setTimeout 實(shí)現(xiàn) setInterval
setInterval 的作用是每隔一段指定時(shí)間執(zhí)行一個(gè)函數(shù),但是這個(gè)執(zhí)行不是真的到了時(shí)間立即執(zhí)行伶丐,它真正的作用是每隔一段時(shí)間將事件加入事件隊(duì)列中去悼做,只有當(dāng)當(dāng)前的執(zhí)行棧為空的時(shí)候,才能去從事件隊(duì)列中取出事件執(zhí)行哗魂。所以可能會(huì)出現(xiàn)這樣的情況肛走,就是當(dāng)前執(zhí)行棧執(zhí)行的時(shí)間很長(zhǎng),導(dǎo)致事件隊(duì)列里邊積累多個(gè)定時(shí)器加入的事件录别,當(dāng)執(zhí)行棧結(jié)束的時(shí)候朽色,這些事件會(huì)依次執(zhí)行邻吞,因此就不能到間隔一段時(shí)間執(zhí)行的效果。
針對(duì) setInterval 的這個(gè)缺點(diǎn)葫男,我們可以使用 setTimeout 遞歸調(diào)用來模擬 setInterval抱冷,這樣我們就確保了只有一個(gè)事件結(jié)束了,我們才會(huì)觸發(fā)下一個(gè)定時(shí)器事件梢褐,這樣解決了 setInterval 的問題旺遮。
實(shí)現(xiàn)思路是使用遞歸函數(shù),不斷地去執(zhí)行 setTimeout 從而達(dá)到 setInterval 的效果
function mySetInterval(fn, timeout) {
// 控制器盈咳,控制定時(shí)器是否繼續(xù)執(zhí)行
var timer = {
flag: true
};
// 設(shè)置遞歸函數(shù)耿眉,模擬定時(shí)器執(zhí)行。
function interval() {
if (timer.flag) {
fn();
setTimeout(interval, timeout);
}
}
// 啟動(dòng)定時(shí)器
setTimeout(interval, timeout);
// 返回控制器
return timer;
}
實(shí)現(xiàn)new的過程
new操作符做了這些事:
- 創(chuàng)建一個(gè)全新的對(duì)象
- 這個(gè)對(duì)象的
__proto__
要指向構(gòu)造函數(shù)的原型prototype - 執(zhí)行構(gòu)造函數(shù)猪贪,使用
call/apply
改變 this 的指向 - 返回值為
object
類型則作為new
方法的返回值返回跷敬,否則返回上述全新對(duì)象
function myNew(fn, ...args) {
// 基于原型鏈 創(chuàng)建一個(gè)新對(duì)象
let newObj = Object.create(fn.prototype);
// 添加屬性到新對(duì)象上 并獲取obj函數(shù)的結(jié)果
let res = fn.apply(newObj, args); // 改變this指向
// 如果執(zhí)行結(jié)果有返回值并且是一個(gè)對(duì)象, 返回執(zhí)行的結(jié)果, 否則, 返回新創(chuàng)建的對(duì)象
return typeof res === 'object' ? res: newObj;
}
// 用法
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log(this.age);
};
let p1 = myNew(Person, "poety", 18);
console.log(p1.name);
console.log(p1);
p1.say();
實(shí)現(xiàn)apply方法
apply原理與call很相似,不多贅述
// 模擬 apply
Function.prototype.myapply = function(context, arr) {
var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push("arr[" + i + "]");
}
result = eval("context.fn(" + args + ")");
}
delete context.fn;
return result;
};
實(shí)現(xiàn)防抖函數(shù)(debounce)
防抖函數(shù)原理:在事件被觸發(fā)n秒后再執(zhí)行回調(diào)热押,如果在這n秒內(nèi)又被觸發(fā)西傀,則重新計(jì)時(shí)。
那么與節(jié)流函數(shù)的區(qū)別直接看這個(gè)動(dòng)畫實(shí)現(xiàn)即可桶癣。
手寫簡(jiǎn)化版:
// 防抖函數(shù)
const debounce = (fn, delay) => {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
};
適用場(chǎng)景:
- 按鈕提交場(chǎng)景:防止多次提交按鈕拥褂,只執(zhí)行最后提交的一次
- 服務(wù)端驗(yàn)證場(chǎng)景:表單驗(yàn)證需要服務(wù)端配合,只執(zhí)行一段連續(xù)的輸入事件的最后一次牙寞,還有搜索聯(lián)想詞功能類似
生存環(huán)境請(qǐng)用lodash.debounce
實(shí)現(xiàn)call方法
call做了什么:
- 將函數(shù)設(shè)為對(duì)象的屬性
- 執(zhí)行和刪除這個(gè)函數(shù)
- 指定
this
到函數(shù)并傳入給定參數(shù)執(zhí)行函數(shù) - 如果不傳入?yún)?shù)饺鹃,默認(rèn)指向?yàn)?
window
// 模擬 call bar.mycall(null);
//實(shí)現(xiàn)一個(gè)call方法:
// 原理:利用 context.xxx = self obj.xx = func-->obj.xx()
Function.prototype.myCall = function(context = window, ...args) {
if (typeof this !== "function") {
throw new Error('type error')
}
// this-->func context--> obj args--> 傳遞過來的參數(shù)
// 在context上加一個(gè)唯一值不影響context上的屬性
let key = Symbol('key')
context[key] = this; // context為調(diào)用的上下文,this此處為函數(shù),將這個(gè)函數(shù)作為context的方法
// let args = [...arguments].slice(1) //第一個(gè)參數(shù)為obj所以刪除,偽數(shù)組轉(zhuǎn)為數(shù)組
// 綁定參數(shù) 并執(zhí)行函數(shù)
let result = context[key](...args);
// 清除定義的this 不刪除會(huì)導(dǎo)致context屬性越來越多
delete context[key];
// 返回結(jié)果
return result;
};
//用法:f.call(obj,arg1)
function f(a,b){
console.log(a+b)
console.log(this.name)
}
let obj={
name:1
}
f.myCall(obj,1,2) //否則this指向window
setTimeout與setInterval實(shí)現(xiàn)
setTimeout 模擬實(shí)現(xiàn) setInterval
題目描述: setInterval
用來實(shí)現(xiàn)循環(huán)定時(shí)調(diào)用 可能會(huì)存在一定的問題 能用 setTimeout
解決嗎
實(shí)現(xiàn)代碼如下:
function mySetInterval(fn, t) {
let timerId = null;
function interval() {
fn();
timerId = setTimeout(interval, t); // 遞歸調(diào)用
}
timerId = setTimeout(interval, t); // 首次調(diào)用
return {
// 利用閉包的特性 保存timerId
cancel:() => {
clearTimeout(timerId)
}
}
}
// 測(cè)試
var a = mySetInterval(()=>{
console.log(111);
},1000)
var b = mySetInterval(() => {
console.log(222)
}, 1000)
// 終止定時(shí)器
a.cancel()
b.cancel()
為什么要用
setTimeout
模擬實(shí)現(xiàn)setInterval
间雀?setInterval
的缺陷是什么悔详?
setInterval(fn(), N);
上面這句代碼的意思其實(shí)是
fn()
將會(huì)在N
秒之后被推入任務(wù)隊(duì)列。在setInterval
被推入任務(wù)隊(duì)列時(shí)惹挟,如果在它前面有很多任務(wù)或者某個(gè)任務(wù)等待時(shí)間較長(zhǎng)比如網(wǎng)絡(luò)請(qǐng)求等茄螃,那么這個(gè)定時(shí)器的執(zhí)行時(shí)間和我們預(yù)定它執(zhí)行的時(shí)間可能并不一致
// 最常見的出現(xiàn)的就是,當(dāng)我們需要使用 ajax 輪詢服務(wù)器是否有新數(shù)據(jù)時(shí)连锯,必定會(huì)有一些人會(huì)使用 setInterval归苍,然而無論網(wǎng)絡(luò)狀況如何,它都會(huì)去一遍又一遍的發(fā)送請(qǐng)求运怖,最后的間隔時(shí)間可能和原定的時(shí)間有很大的出入
// 做一個(gè)網(wǎng)絡(luò)輪詢拼弃,每一秒查詢一次數(shù)據(jù)。
let startTime = new Date().getTime();
let count = 0;
setInterval(() => {
let i = 0;
while (i++ < 10000000); // 假設(shè)的網(wǎng)絡(luò)延遲
count++;
console.log(
"與原設(shè)定的間隔時(shí)差了:",
new Date().getTime() - (startTime + count * 1000),
"毫秒"
);
}, 1000)
// 輸出:
// 與原設(shè)定的間隔時(shí)差了: 567 毫秒
// 與原設(shè)定的間隔時(shí)差了: 552 毫秒
// 與原設(shè)定的間隔時(shí)差了: 563 毫秒
// 與原設(shè)定的間隔時(shí)差了: 554 毫秒(2次)
// 與原設(shè)定的間隔時(shí)差了: 564 毫秒
// 與原設(shè)定的間隔時(shí)差了: 602 毫秒
// 與原設(shè)定的間隔時(shí)差了: 573 毫秒
// 與原設(shè)定的間隔時(shí)差了: 633 毫秒
再次強(qiáng)調(diào) 摇展,定時(shí)器指定的時(shí)間間隔吻氧,表示的是何時(shí)將定時(shí)器的代碼添加到消息隊(duì)列,而不是何時(shí)執(zhí)行代碼。所以真正何時(shí)執(zhí)行代碼的時(shí)間是不能保證的医男,取決于何時(shí)被主線程的事件循環(huán)取到砸狞,并執(zhí)行捻勉。
setInterval(function, N)
//即:每隔N秒把function事件推到消息隊(duì)列中
[圖片上傳失敗...(image-9ef7ae-1663910059294)]
上圖可見镀梭,
setInterval
每隔100ms
往隊(duì)列中添加一個(gè)事件;100ms
后踱启,添加T1
定時(shí)器代碼至隊(duì)列中报账,主線程中還有任務(wù)在執(zhí)行,所以等待埠偿,some event
執(zhí)行結(jié)束后執(zhí)行T1
定時(shí)器代碼透罢;又過了100ms
,T2
定時(shí)器被添加到隊(duì)列中冠蒋,主線程還在執(zhí)行T1
代碼羽圃,所以等待;又過了100ms
抖剿,理論上又要往隊(duì)列里推一個(gè)定時(shí)器代碼朽寞,但由于此時(shí)T2
還在隊(duì)列中,所以T3
不會(huì)被添加(T3
被跳過)斩郎,結(jié)果就是此時(shí)被跳過脑融;這里我們可以看到,T1
定時(shí)器執(zhí)行結(jié)束后馬上執(zhí)行了 T2 代碼缩宜,所以并沒有達(dá)到定時(shí)器的效果
setInterval有兩個(gè)缺點(diǎn)
- 使用
setInterval
時(shí)肘迎,某些間隔會(huì)被跳過 - 可能多個(gè)定時(shí)器會(huì)連續(xù)執(zhí)行
可以這么理解 :每個(gè)
setTimeout
產(chǎn)生的任務(wù)會(huì)直接push
到任務(wù)隊(duì)列中;而setInterval
在每次把任務(wù)push
到任務(wù)隊(duì)列前锻煌,都要進(jìn)行一下判斷(看上次的任務(wù)是否仍在隊(duì)列中)妓布。因而我們一般用setTimeout
模擬setInterval
,來規(guī)避掉上面的缺點(diǎn)
setInterval 模擬實(shí)現(xiàn) setTimeout
const mySetTimeout = (fn, t) => {
const timer = setInterval(() => {
clearInterval(timer);
fn();
}, t);
};
// 測(cè)試
// mySetTimeout(()=>{
// console.log(1);
// },1000)
驗(yàn)證是否是身份證
function isCardNo(number) {
var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
return regx.test(number);
}