防抖
/**
* 防抖:
* 一段时间内若无事件触发,则执行函数,否则延后执行
* 定时器版本:
* 实现原理就是利用定时器,函数第一次执行时设定一个定时器,之后调用时发现已经设定过定时器就清空之前的定时器,并重新设定一个新的定时器;
* 如果存在没有被清空的定时器,当定时器计时结束后触发函数执行。
* 参考https://github.com/mqyqingfeng/Blog/issues/22
*/
function debounce1(fn, wait) {
let timer;
return function (...args) {
console.log('trigger event');
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.call(this, ...args);
}, wait);
};
}
/**
* 进阶版:
* 1. immediate 立即执行参数
* immediate 表示第一次是否立即执行,如果是 immediate 的情况下,我们立即执行 fn ,并在 wait 时间内锁住 fn 的执行, wait 时间之后再触发,才会重新执行 fn
* 2. immediate 为ture时的返回值
* 3. 取消
*/
function debounce2(fn, wait, immediate = true) {
let timer;
let result;
let debounced = function (...args) {
console.log('trigger event');
if (timer) {
clearTimeout(timer);
}
if (immediate && !timer) {
// 返回值
result = fn.call(this, ...args);
}
timer = setTimeout(() => {
fn.call(this, ...args);
}, wait);
return result;
};
// 取消
debounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
// 第一次触发执行一次 fn,后续只有在停止触发 1 秒后才执行函数 fn
let debounced = debounce2(() => {
console.log('fn call');
}, 1000);
let i = 0;
let timer = setInterval(() => {
debounced();
if (++i > 5) {
clearInterval(timer);
}
}, 600);
节流
高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率 github.com/mqyqingfeng…
要点:
- this、入参
- 返回值
- 事件触发后是否会立刻执行,事件停止触发后是否再执行
/*
setTimeout版:不立即执行,事件触发后会再执行。
思路:
返回的函数内,若无定时器,设置定时器timer;
定时器回调后,清空timer,执行函数。
*/
function throttle1(fn, timeout) {
let timer;
let previous = 0;
return function () {
let context = this;
let args = arguments;
if (!timer) {
timer = setTimeout(function () {
timer = null;
fn.apply(context, args);
}, timeout);
}
};
}
function throttle(fn, timeout) {
let timer = null;
let last;
let throttledFn = function (...args) {
let context = this;
let now = Date.now();
let duration = last ? now - last : timeout;
if (timer) {
clearTimeout(timer);
timer = null;
}
if (duration >= timeout) {
last = now;
fn.call(context, ...args);
} else {
timer = setTimeout(() => {
last = now;
fn.apply(context, args);
}, timeout - duration);
}
};
return throttledFn;
}
let i = 0;
let throttled = throttle((...args) => {
console.log('throttled call', ...args);
}, 1000);
let timer = setInterval(() => {
if (i >= 20) clearInterval(timer);
throttled(i++);
}, 100);
手写bind
bind() 函数会创建一个新的绑定函数(bound function,BF)。
- 第一个参数绑定为函数this值
- 其余参数是预置的初始参数,可以应用在偏函数中
- 返回值是绑定this后的函数
- 可以通过new调用,但提供的 this 值会被忽略。new 调用时返回值由new的规则决定,需要注意修改原型指向
Function.prototype._bind=function(self,...args1){
let fn=this
let result=function(...args2){
const isNew=this instanceof result
const context=isNew?this:self
return fn.call(context,...args1,...args2)
}
result.prototype=Object.create(fn.prototype)
return result
}
// 测试
let obj={name:'cn'}
function getName(...args){
this.args=args
console.log('fn call',this,...args)
return '1'
}
getName.prototype={
group:1
}
let bindFn=getName._bind(obj,1,2,3)
let res=new bindFn(4,5)
console.log('res',res)
Call
Apply
Promise 简单实现
Promise A+ 规范: promisesaplus.cn/
let PADDING = 'PADDING',
FULFILLED = 'FULFILLED',
REJECTED = 'REJECTED';
class Promise {
static status;
result;
resolvedCallbacks = [];
rejectedCallbacks = [];
constructor(fn) {
this.status = PADDING;
try {
fn(this.resolve.bind(this), this.reject.bind(this));
} catch (e) {
this.reject(e);
}
}
// 当promise处于pending时 promise可以转为fulfilled或rejected状态
resolve(res) {
setTimeout(() => {
if (this.status === PADDING) {
this.status = FULFILLED;
this.result = res;
this.resolvedCallbacks.forEach((fn) => {
fn(res);
});
}
});
}
reject(err) {
setTimeout(() => {
if (this.status === PADDING) {
this.status = REJECTED;
this.result = err;
this.rejectedCallbacks.forEach((fn) => {
fn(err);
});
}
});
}
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
if (this.status === PADDING) {
this.resolvedCallbacks.push(onFulfilled);
this.rejectedCallbacks.push(onRejected);
} else if (this.status === FULFILLED) {
setTimeout(() => {
try {
this.result = onFulfilled();
} catch (e) {
onRejected(e);
}
});
} else if (this.status === REJECTED) {
setTimeout(() => {
onRejected(this.result);
});
}
});
}
}
let p = new Promise((resolve, reject) => {
console.log('created promise');
setTimeout(() => {
resolve('1');
console.log('setTimeout called');
});
});
p.then(
(res) => {
console.log('then resolved', res);
},
(err) => {
console.log('then rejected', err);
}
);
console.log('executed');
Promise.finally
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value, () => value),
reason => P.resolve(callback()).then(() => { throw reason }, () => {throw reason})
);
};
手写 Promise.all
方法返回一个Promise实例,此实例在 promise 都完成(resolved)时回调完成(resolve);如果参数中 promise有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败promise的结果。
Promise.all = function(arr) {
let list = []
let len = 0
let hasErr = false
return new Promise((resolve, reject) => {
for(let i = 0; i < arr.length; i++) {
arr[i].then( data=> {
list[i] = data
len++
len === arr.length && resolve(list)
}, error => {
!hasErr && reject(error)
hasErr = true
})
}
})
}
Promise.race
方法返回一个Promise实例,一旦迭代器中的某个 promise 完成(resolved)或失败(rejected),返回的 promise 就会 resolve 或 reject
Promise.race = function(arr) {
let hasValue = false
let hasError = false
return new Promise1((resolve, reject) => {
for(let i = 0; i < arr.length; i++) {
arr[i].then(data => {
!hasValue && !hasError && resolve(data)
hasValue = true
}, error => {
!hasValue && !hasError &&reject(error)
hasError = true
})
}
})
}
Promise 任务调度
class Scheduler {
constructor() {
this.tasks = [], // 待运行的任务
this.runing = [] // 正在运行的任务
this.limit=2
}
// promiseCreator 是一个异步函数,return Promise
add(promiseCreator) {
// 返回promsie用于任务等待
return new Promise((resolve,reject)=>{
// 绑定fn和对应的resolve
let task={fn:promiseCreator,resolve}
//console.log('runing:',this.runing)
if (this.runing.length <this.limit) {
this.run(task)
} else {
this.tasks.push(task)
}
})
}
run(task) {
this.runing.push(task)
task.fn().then((res)=>{
task.resolve(res)
this.runing=this.runing.filter(v=>v!=task)
if(this.limit<=2 && this.tasks.length){
this.run(this.tasks.shift())
}
})
}
}
const timeout = (time) => new Promise(resolve => {
setTimeout(resolve, time)
})
const scheduler = new Scheduler()
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then(() => console.log(order,new Date().getTime()))
}
addTask(400, 4)
addTask(200, 2)
addTask(300, 3)
addTask(100, 1)
Redux
function createStore(reducer) {
// 创建一个 store 对象
let state; // 状态对象
let listeners = []; // 监听器
// 订阅器
function subscribe(callback) {
listeners.push(callback); // 每订阅一个,就为监听器添加一个回调函数
}
// 更新 state
function dispatch(action) {
// 用户编写的reducer,形式为 (state, action) => state 的纯函数。
state = reducer(state, action); // 更新 state
listeners.forEach((i) => {
// 通知所有订阅者
i();
});
}
// 获取 state
function getState() {
return state;
}
// 返回 store 对象
return {
subscribe,
dispatch,
getState
};
}
参考:
排序
快速排序
算法思想:
-
在数据集之中,选择一个元素作为"基准"(pivot)。
-
所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。
-
对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
递归实现:
let quickSort = (arr)=> {
let mid= arr.splice(Math.floor(arr.length/2),1)[0];
let left =[],right=[];
arr.forEach((v,i)=>{
if(v>mid){
right.push(v)
}else{
left.push(v)
}
})
if(left.length>1) left = quickSort(left)
if(right.length>1) right = quickSort(right)
return [...left,mid,...right]
};
quickSort([3,5,0,2,4,8,1,9,7,6,2])
时间复杂度: O(nlogn)
插入排序
算法步骤
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
function insertionSort(arr) {
var len = arr.length;
var preIndex, current;
for (var i = 1; i < len; i++) {
preIndex = i - 1;
current = arr[i];
while(preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex+1] = arr[preIndex];
preIndex--;
}
arr[preIndex+1] = current;
}
return arr;
}
Array.sort
sort方法基本使用:arr.sort([compareFunction])
如果不传入 compareFunction,则元素按照转换为字符串的各个字符的 Unicode 位点进行排序
如果指明了 compareFunction 参数 ,那么数组会按照调用该函数的返回值排序,即 a 和 b 是两个将要被比较的元素:
- compareFunction(a, b)< 0,a 会被排列到 b 之前
- compareFunction(a, b)=== 0,a 和 b 的相对位置不变
- compareFunction(a, b)> 0,b 会被排列到 a 之前
参考: blog.csdn.net/xuyangxinle… juejin.cn/post/697798…
ParseInt
function _parseInt(str, radix) {
let str_type = typeof str;
let res = 0;
if (str_type !== 'string' && str_type !== 'number') {
// 如果类型不是 string 或 number 类型返回NaN
return NaN
}
// 字符串处理
str = String(str).trim().split('.')[0]
let length = str.length;
if (!length) {
// 如果为空则返回 NaN
return NaN
}
if (!radix) {
// 如果 radix 为0 null undefined
// 则转化为 10
radix = 10;
}
if (typeof radix !== 'number' || radix < 2 || radix > 36) {
return NaN
}
for (let i = 0; i < length; i++) {
let arr = str.split('').reverse().join('');
res += Math.floor(arr[i]) * Math.pow(radix, i)
}
return res;
}
大整数相加
function addBig(a, b) {
// 补齐位数
if(a.length>b.length) [a,b]=[b,a]
a=Array(b.length-a.length).fill(0).join('')+a
let sign = 0;//标记 是否进位
let newVal = [];//用于存储最后的结果
for(let i = a.length-1;i>=0;i--){
let val = a[i]*1+b[i]*1+sign;
if(val>=10){
sign = 1;
newVal.unshift(val%10)//这里用unshift而不是push是因为可以省了使用reverse
}else{
sign = 0;
newVal.unshift(val)
}
}
return newVal.join('')
}
字符串相乘
/**
* @param {string} num1
* @param {string} num2
* @return {string}
*/
const multiply = function(num1, num2) {
if(num1==0 || num2==0) return "0"
const res=[];// 结果集
for(let i=0;i<num1.length;i++){
let tmp1=num1[num1.length-1-i]; // num1尾元素
for(let j=0;j<num2.length;j++){
let tmp2 = num2[num2.length-1-j]; // num2尾元素
let pos = res[i+j] ? res[i+j]+tmp1*tmp2 : tmp1*tmp2;// 目标值 ==》三元表达式,判断结果集索引位置是否有值
res[i+j]=pos%10; // 赋值给当前索引位置
// 目标值是否大于10 ==》是否进位 这样简化res去除不必要的"0"
if(pos >=10){
res[i+j+1]=res[i+j+1] ? res[i+j+1]+Math.floor(pos/10) : Math.floor(pos/10)
}
}
}
return res.reverse().join("");
};
LRC
// https://github.com/sisterAn/JavaScript-Algorithms/issues/7
var LRUCache = function(capacity) {
this.keys = []
this.cache = Object.create(null)
this.capacity = capacity
};
LRUCache.prototype.get = function(key) {
if(this.cache[key]) {
// 调整位置
remove(this.keys, key)
this.keys.push(key)
return this.cache[key]
}
return -1
};
LRUCache.prototype.put = function(key, value) {
if(this.cache[key]) {
// 存在即更新
this.cache[key] = value
remove(this.keys, key)
this.keys.push(key)
} else {
// 不存在即加入
this.keys.push(key)
this.cache[key] = value
// 判断缓存是否已超过最大值
if(this.keys.length > this.capacity) {
removeCache(this.cache, this.keys, this.keys[0])
}
}
};
// 移除 key
function remove(arr, key) {
if (arr.length) {
const index = arr.indexOf(key)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
// 移除缓存中 key
function removeCache(cache, keys, key) {
cache[key] = null
remove(keys, key)
}
cloneDeep
// 1.数组处理
// 2.循环引用
// 3.Symbol key
// 4. 破解递归爆栈-BFS
function cloneDeep3(source, hash = new WeakMap()) {
if (!isObject(source)) return source;
if (hash.has(source)) return hash.get(source); // 新增代码,查哈希表
var target = Array.isArray(source) ? [] : {};
hash.set(source, target); // 新增代码,哈希表设值
// Symbol key
let symKeys = Object.getOwnPropertySymbols(source); // 查找
if (symKeys.length) { // 查找成功
symKeys.forEach(symKey => {
if (isObject(source[symKey])) {
target[symKey] = cloneDeep4(source[symKey], hash);
} else {
target[symKey] = source[symKey];
}
});
}
for(var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep3(source[key], hash); // 新增代码,传入哈希表
} else {
target[key] = source[key];
}
}
}
return target;
}
获取URL参数
function getQueryString(url,name) {
let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
let r = url.split('?')[1].split('#')[0].match(reg);
if (r != null) {
return decodeURIComponent(r[2]);
};
return ‘’;
}
var url = `http://m.meijiabang.cn/index.html?key0=0&key1=1&key2=2#/index`
getQueryString(url,'key2')
柯里化
只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数,如将fn(x,y)转化为fn(x)fn(y)
const curry = (fn, ...args) =>
// 函数的参数个数可以直接通过函数数的.length属性来访问
args.length >= fn.length // 这个判断很关键!!!
// 传入的参数大于等于原始函数fn的参数个数,则直接执行该函数
? fn(...args)
/**
* 传入的参数小于原始函数fn的参数个数时
* 则继续对当前函数进行柯里化,返回一个接受所有参数(当前参数和剩余参数) 的函数
*/
: (..._args) => curry(fn, ...args, ..._args);
function add1(x, y, z) {
return x + y + z;
}
const add = curry(add1);
console.log(add(1, 2, 3));
console.log(add(1)(2)(3));
console.log(add(1, 2)(3));
console.log(add(1)(2, 3));