防抖函数(debounce)
防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
// 非立即执行版
const debounce = (func, wait = 50) => {
// 缓存一个定时器id
let timer = 0
// 这里返回的函数是每次用户实际调用的防抖函数
// 如果已经设定过定时器了就清空上一次的定时器
// 开始一个新的定时器,延迟执行用户传入的方法
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
// 立即执行版function debounce(func,wait) {
let timeout;
return function () {
const context = this;
const args = [...arguments];
if (timeout) clearTimeout(timeout);
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
}
function useDebounce(fn, delay, dep = []) {
const { current } = useRef({ fn, timer: null });
useEffect(function () {
current.fn = fn;
}, [fn]);
return useCallback(function f(...args) {
if (current.timer) {
clearTimeout(current.timer);
}
current.timer = setTimeout(() => {
current.fn.call(this, ...args);
}, delay);
}, dep)
}
适用场景:
- 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
- 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似
生存环境请用lodash.debounce
节流函数(throttle)
防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
// 非立即执行版
const throttle = (fn, delay = 500) => {
let flag = true;
return (...args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args);
flag = true;
}, delay);
};
};
// 立即执行版
function throttle(func, wait) {
var previous = 0;
return function() {
let now = Date.now();
let context = this;
let args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}
适用场景:
- 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
- 缩放场景:监控浏览器resize
- 动画场景:避免短时间内多次触发动画引起性能问题
实现Event(event bus)
event bus既是node中各个模块的基石,又是前端组件通信的依赖手段之一,同时涉及了订阅-发布设计模式,是非常重要的基础。
function EventBus() {}
EventBus.prototype.on = function (name, callback) {
//如果没有事件对象,新增一个
if (!this._events) {
//创建一个干净的没有原型链的对象
this._events = Object.create(null);
}
//如果没有这个事件的订阅,新增一个,如果有,push进去
if (!this._events[name]) {
this._events[name] = [callback];
} else {
this._events[name].push(callback);
}
}
EventBus.prototype.emit = function (name, ...args) {
//发布的时候,如果有这个事件,循环执行所有这个订阅的方法
if (this._events[name]) {
this._events[name].forEach(callback => {
callback(...args);
})
}
}
EventBus.prototype.off = function (name) {
//如果有这个事件的订阅,清除所有订阅
if (this._events[name]) {
delete this._events[name];
}
}
EventBus.prototype.once = function (name, callback) {
let once = (...args) => {
callback(...args);
this.off(name);
};
this.on(name, once);
}
const eventBus = new EventBus();
eventBus.on('fn1', function (msg) {
console.log(`收到了信息:${msg}`)
})
实现instanceOf
// 模拟 instanceof
function instance_of(L, R) {
//L 表示左表达式,R 表示右表达式
var O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {
if (L === null) return false;
if (O === L)
// 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
模拟new
new操作符做了这些事:
用new Object() 的方式新建了一个对象 obj
取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 arguments 会被去除第一个参数
将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性
使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性
如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用
// objectFactory(name, 'cxk', '18')
function objectFactory() {
const obj = new Object();
const Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
const ret = Constructor.apply(obj, arguments);
return typeof ret === "object" ? ret : obj;
}
柯里化curry
原理:比较多次接受的参数总数与函数定义时的入参数量,当接受参数的数量大于或等于被 Currying函数的传入参数数量时,就返回计算结果,否则返回一个继续接受参数的函数。
// 关键步骤:使用一个变量(args)来记录传入的所有参数;递归调用函数
function curry(fn, ...args) {
return (...arr) => {
let result = [...args, ...arr]
if (result.length === fn.length) {
return fn(...result)
} else {
return curry(fn, ...result)
}
}
}
// es6 实现
function curry(fn, ...args) {
return args.length >= fn.length ? fn(...args) : curry.bind(null, fn, ...args);
}
实现flatten(数组扁平化)
//迭代实现
function flatten(arr) {
let arrs = [...arr]
let newArr = [];
while (arrs.length) {
let item = arrs.shift()
if (Array.isArray(item)) {
arrs.unshift(...item)
} else {
newArr.push(item)
}
}
return newArr
}
//递归实现
function flatten(arr) {
let arrs = [];
arr.map(item => {
if (Array.isArray(item)) {
arrs.push(...flatten(item))
} else {
arrs.push(item)
}
})
return arrs
}
//字符串转换
arr.join(',').split(',').map(item => Number(item))
// 用 reduce 展开一层 + 递归
const flat = arr => {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flat(cur) : cur);
}, []);
};
const flat = (arr, num = 1) => {
return num > 0 ?
arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flat(cur, num - 1) : cur);
}, []) :
arr.slice()
};
实现数组转树
let list =[
{id:1,name:'部门A',parentId:0},
{id:2,name:'部门B',parentId:0},
{id:3,name:'部门C',parentId:1},
{id:4,name:'部门D',parentId:1},
{id:5,name:'部门E',parentId:2},
{id:6,name:'部门F',parentId:3},
{id:7,name:'部门G',parentId:2},
{id:8,name:'部门H',parentId:4}
];
function arrayToTree(input, parentId) {
const array = [];
const buildTree = (arr, parentId, childrenArray) => {
arr.forEach((item) => {
if (item.parentId === parentId) {
item.children = [];
buildTree(arr, item.id, item.children);
childrenArray.push(item);
}
});
}
buildTree(input, parentId, array);
return array;
}
arrayToTree(list, 0);
function convert(list) {
const res = []
const map = list.reduce((res, v) => (res[v.id] = v, res), {})
for (const item of list) {
if (item.parentId === null) {
res.push(item)
continue
}
if (item.parentId in map) {
const parent = map[item.parentId]
parent.children = parent.children || []
parent.children.push(item)
}
}
return res
}
convert(list)
实现树转数组(扁平化)
function treeToArray(obj, res = []) { // 默认初始结果数组为[]
res.push(obj); // 当前元素入栈
// 若元素包含children,则遍历children并递归调用使每一个子元素入栈
if (obj.children && obj.children.length) {
for (const item of obj.children) {
treeToArray(item, res);
}
}
return res;
}
function treeToArray(obj) {
const stack = []; // 声明栈,用来存储待处理元素
const res = []; // 接收结果
stack.push(obj); // 将初始元素压入栈
while (stack.length) { // 栈不为空则循环执行
const item = stack[0]; // 取出栈顶元素
res.push(item); // 元素本身压入结果数组
stack.shift(); // 将当前元素弹出栈
// 逻辑处理,如果当前元素包含子元素,则将子元素压入栈
if (item.children && item.children.length) {
stack.push(...item.children);
}
}
return res;
}
给一个 id 找出链条中其对应的所有的父级 id
function fn(data, value) {
let res = []
const dfs = (arr, temp = []) => {
for (const node of arr) {
if (node.id === value) {
res = temp
return
} else {
node.children && dfs(node.children, temp.concat(node.id))
}
}
}
dfs(data)
return res
}
function search(object, value) {
for (var key in object) {
if (object[key] == value) return [key];
if (typeof (object[key]) == "object") {
var temp = search(object[key], value);
if (temp) return [key, temp].flat();
}
}
}
实现call
call做了什么:
将函数设为对象的属性
执行&删除这个函数
指定this到函数并传入给定参数执行函数
如果不传入参数,默认指向为 window
Function.prototype.myCall = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
context = context || window
context.fn = this
const args = [...arguments].slice(1)
const result = context.fn(...args)
delete context.fn
return result
}
实现apply
第二项为数组
Function.prototype.myApply = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
context = context || window
context.fn = this
let result
// 处理参数和 call 有区别
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
实现bind
返回一个函数,绑定this,传递预置参数
bind返回的函数可以作为构造函数使用。故作为构造函数时应使得this失效,但是传入的参数依然有效
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
const _this = this
const args = [...arguments].slice(1)
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
实现map
Array.prototype.copyMap = function (fn, toThis) {
let arr = this;
const result = [];
const redirectThis = toThis || Object.create(null);
for (let i = 0; i < arr.length; i++) {
const item = fn.call(redirectThis, arr[i], i, arr);
result.push(item);
}
return result;
};
实现reduce
Array.prototype.reduce = function(fn, init) {
var arr = this // this就是调用reduce方法的数组
var total = init || arr[0] // 有初始值使用初始值
// 有初始值的话从0遍历, 否则从1遍历
for (var i = init ? 0 : 1; i < arr.length; i++) {
// (四个参数,前两个是重点,1:当前累加总和,2:当前要累加的值,3:当前要累加值的索引,4:当前的数组)
total = fn(total, arr[i], i , arr)
}
return total
}
var arr = [1,2,3]
console.log(arr.reduce((prev, item) => prev + item, 10))
模拟Object.create
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
// 模拟 Object.create
function create(proto) {
function F() {}
F.prototype = proto;
return new F();
}
实现Promise.all Promise.race Promise.retry
Promise.prototype.promiseAll = (promises) => {
if (!Array.isArray(promises)) {
throw new Error("promises must be an array")
}
return new Promise((resolve, reject) => {
// 用来存储每个promise的返回值
let values = new Array(promises.length);
// 当前已经完成了几个promise
let finishCount = 0;
for (let i = 0; i < promises.length; ++i) {
Promise.resolve(promises[i]).then(val => {
values[i] = val;
++finishCount;
if (finishCount === promises.length) {
resolve(values);
}
}).catch(err => {
reject(err)
})
}
});
};
Promise.prototype.promiseRace = (promises) => {
if (!Array.isArray(promises)) {
throw new Error("promises must be an array")
}
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; ++i) {
Promise.resolve(promises[i]).then(val => {
resolve(val);
}).catch(err => {
reject(err)
})
}
});
};
Promise.prototype.retry = (fn,times,delay) => {
let time = 0;
return new Promise((resolve,reject)=>{
const attemp = ()=>{
Promise.resolve(fn)
.then(resolve)
.catch(err=>{
time++;
console.log("尝试失败");
if(time==times){
reject(err);
}else{
setTimeOut(()=>{
attemp();
},delay)
}
})
}
attemp();
})
}
实现Promise
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
function MyPromise(fn) {
const that = this
that.state = PENDING
that.value = null
that.resolvedCallbacks = []
that.rejectedCallbacks = []
// 待完善 resolve 和 reject 函数
// 待完善执行 fn 函数
function resolve(value) {
if (that.state === PENDING) {
that.state = RESOLVED
that.value = value
that.resolvedCallbacks.map(cb => cb(that.value))
}
}
function reject(value) {
if (that.state === PENDING) {
that.state = REJECTED
that.value = value
that.rejectedCallbacks.map(cb => cb(that.value))
}
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
const that = this
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected =
typeof onRejected === 'function' ?
onRejected :
r => {
throw r
}
if (that.state === PENDING) {
that.resolvedCallbacks.push(onFulfilled)
that.rejectedCallbacks.push(onRejected)
}
if (that.state === RESOLVED) {
onFulfilled(that.value)
}
if (that.state === REJECTED) {
onRejected(that.value)
}
}
MyPromise.prototype.then = function(onFulfilled, onRejected) { let promise2 = new Promise((resolve, reject) => {
if (this.status === RESOLVED) {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
}
if (this.status === REJECTED) {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
}
if (this.status === PENDING) {
this.resolveCbs.push(() => {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
});
this.rejectCbs.push(() => {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
});
}
});
return promise2;
function resolvePromise (promise2, x, resolve, reject) {
if (promise2 === x) {
// 不允许 promise2 === x; 避免自己等待自己
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 防止重复调用
let called = false;
try {
if (x instanceof Promise) {
let then = x.then;
// 第一个参数指定调用对象
// 第二个参数为成功的回调,将结果作为 resolvePromise 的参数进行递归
// 第三个参数为失败的回调
then.call(x, y => {
if (called) return;
called = true;
// resolve 的结果依旧是 Promise 那就继续解析
resolvePromise(promise2, y, resolve, reject);
}, err => {
if (called) return;
called = true;
reject(err);
});
} else {
resolve(x);
}
} catch (e) {
reject(e);
}
} }
解析 URL Params 为对象
const getUrlParams = (url) => {
const arrSearch = url.split('?').pop().split('#').shift().split('&');
let obj = {};
arrSearch.forEach((item) => {
const [k, v] = item.split('=');
obj[k] = v;
return obj;
});
return obj;
};const getUrlParams2 = (url) => {
const u = new URL(url);
const s = new URLSearchParams(u.search);
const obj = {};
s.forEach((v, k) => (obj[k] = v));
return obj;
};
模板引擎实现
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]; // 查找当前模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
return render(template, data); // 递归的渲染并返回渲染后的结构
}
return template; // 如果模板没有模板字符串直接返回
}
转化为驼峰命名
var s1 = "get-element-by-id"
// 转化为 getElementById
var f = function(s) {
return s.replace(/-\w/g, function(x) {
return x.slice(1).toUpperCase();
})
}
查找字符串中出现最多的字符和个数
例: abbcccddddd -> 字符最多的是d,出现了5次
var str="sssfgtdfssddfsssfssss";
function max(){
var json={};
var num=0;
var value=null;
for(var i=0;i<str.length;i++){
var k=str[i];
if(!json[k]){
json[k]=[];
}
json[k].push(k); //这里不需要else,否则只有存在这个字符时才添加。次数会少一次
}
for(var attr in json){
if(num<json[attr].length){
num=json[attr].length;
value=json[attr][0];
}
}
alert("出现最多的字符是:"+value+',出现次数是:'+num);
};
max(str);
字符串查找
请使用最基本的遍历来实现判断字符串 a 是否被包含在字符串 b 中,并返回第一次出现的位置(找不到返回 -1)。
a='34';b='1234567'; // 返回 2
a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
isContain(a,b);
function isContain(a, b) {
for (let i in b) {
if (a[0] === b[i]) {
let tmp = true;
for (let j in a) {
if (a[j] !== b[Number(i) + Number(j)]) { tmp = false;
}
}
if (tmp) {
return i;
}
}
}
return -1;
}
实现千位分隔符
// 保留三位小数
parseToMoney(1234.56); // return '1,234.56'
parseToMoney(123456789); // return '123,456,789'
parseToMoney(1087654.321); // return '1,087,654.321'
function parseToMoney(num) {
num = parseFloat(num.toFixed(3));
let [integer, decimal] = String.prototype.split.call(num, '.');
integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');
return integer + '.' + (decimal ? decimal : '');
}
正则表达式(运用了正则的前向声明和反前向声明):
function parseToMoney(str){
// 仅仅对位置进行匹配
let re = /(?=(?!\b)(\d{3})+$)/g;
return str.replace(re,',');
}