debounce
function debounce(func, wait) {
let timer;
return function () {
if (timer) {
window.clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(this, arguments);
timer = null;
}, wait);
};
}
throttle
function throttle(func, wait) {
let time = 0;
return function () {
const current = Date.now();
if (current - time < wait) {
return;
}
// func()
func.apply(this, arguments);
time = current;
};
}
memoize
function shallowEqual(arg1, arg2) {
if (arg1.length !== arg2.length) {
return false;
}
for (const n in arg1) {
if (arg1[n] !== arg2[n]) {
return false;
}
}
return true;
}
function memoize(fn) {
let lastThis,
lastArgs = [],
lastResult,
triggerBefore = false;
return function (...args) {
if (triggerBefore && lastThis === this && shallowEqual(lastArgs, args)) {
return lastResult;
}
lastThis = this;
lastArgs = args;
triggerBefore = true;
return (lastResult = fn(...args));
};
}
bind
Function.prototype.myBind = function (context, ...bindArgs) {
const ctx = context || window;
const self = this;
return function (...args) {
return self.apply(_this, [...args, ...bindArgs]);
};
};
call
Function.prototype.myCall = function (context, ...args) {
const ctx = context || window;
const unique = new Symbol();
ctx[unique] = this;
ctx[unique](...args);
delete ctx[unique];
};
apply
Function.prototype.myApply = function (context, args) {
const ctx = context || window;
const unique = new Symbol();
ctx[unique] = this;
ctx[unique](...args);
delete ctx[unique];
};
定时器 hook
function useCountDown(init) {
const [count, setCount] = useState(init);
const [key, forceUpdate] = useReducer((x) => x + 1, 0);
const reset = useCallback(() => {
setCount(init);
forceUpdate();
}, [init]);
useEffect(() => {
const timer = window.setInterval(() => {
setCount((prevCount) => {
if (prevCount <= 0) {
window.clearInterval(timer);
return 0;
}
return prevCount - 1;
});
}, 1000);
return function () {
window.clearInterval(timer);
};
}, [key]);
return [count, reset];
}
单例模式
const MyClass = (function () {
let instance;
return function (name) {
if (instance) {
return instance;
}
this.name = name;
return (instance = this);
};
})();
MyClass.prototype.showName = function () {
console.log(this.name);
};
const a = new MyClass("aa");
const b = new MyClass("bb");
a.showName();
b.showName();
数组拍平
function flat(arr) {
return arr.reduce(
(total, curr) => total.concat(Array.isArray(curr) ? flat(curr) : curr),
[]
);
}
深拷贝
需要考虑的点:
- 准确的判断数据类型
- 引用类型的复制
- 方法的复制
- 原型链的指向
要实现深拷贝,有且只有一种思路,就是判断不同的数据类型,如果遇到可遍历数据就进行遍历复制
function deepCopy(orgin) {
switch (Object.prototype.toString.call(origin)) {
case "[object Object]":
const newObj = {};
Object.entries(origin).forEach(
([key, value]) => (newObj[key] = deepCopy(value))
);
if (origin.__proto__ !== Object.prototype) {
newObj.__proto__ = origin.prototype;
}
return newObj;
case "[object Array]":
return origin.map((item) => deepCopy(item));
case "[object Number]":
case "[object String]":
case "[object Boolean]":
case "[object Symbol]":
case "[object Null]":
case "[object Undefined]":
return origin;
default:
return Object.assign(origin);
}
}
p.s.
更严谨的方式是对一些不可遍历的数据也重新开辟新的存储空间,如 Number、String、Boolean 使用构造函数重新实例化(譬如new Number(origin)),对 Symbol 使用 Object(Symbol.prototype.valueOf.call(origin))。
但是其实不这么严谨也行,因为不影响实际使用
以下方法所实现的拷贝均不是深拷贝
- JSON.parse(JSON.stringify(obj))
- arr.slice(0)
科里化,compose 方法
function compose(...fns) {
return function (num) {
return fns.reduce((total, fn) => fn(total), num);
};
}
function fn1(x) {
return x + 1;
}
function fn2(x) {
return x + 2;
}
function fn3(x) {
return x + 3;
}
function fn4(x) {
return x + 4;
}
function fn5(x) {
return x * 5;
}
const a = compose(fn1, fn2, fn3, fn4, fn5);
console.log(a(1)); // 1+1+2+3+4*5=55
科里化,add 方法
题目描述:实现一个 add 方法 使计算结果能够满足如下预期:
- add(1)(2)(3)()=6
- add(1,2,3)(4)()=10
function add(...numsOutter) {
let result = numsOutter.reduce((total, curr) => total + curr, 0);
const fn = function (...numsInner) {
if (numsInner.length <= 0) {
return result;
}
result = numsInner.reduce((total, curr) => total + curr, result);
return fn;
};
return fn;
}
es5 实现继承
// 父类
function Human(gender) {
this.gender = gender;
}
Human.prototype.showGender = function () {
console.log(this.gender);
};
// 子类
function Teacher(name, level) {
this.level = level;
Human.call(this, name);
}
Teacher.prototype = Object.create(Human.prototype, {
constructor: {
value: Teacher,
},
});
Teacher.prototype.showLevel = function () {
console.log(this.level);
};
instanceof
function myInstanceof(a, b) {
const target = b.prototype;
let match = a.__proto__;
while (match) {
if (match === target) {
return true;
}
match = match.__proto__;
}
return false;
}
new
function myNew(C, ...args) {
/**
* 创建一个空对象
*
* 下面写法有相同效果:
* - const obj = Object.create({});
* - const obj = {};
*/
const obj = new Object();
/**
* 修改原型链
*
* 下面写法有相同效果:
* - obj.__proto__ = C.prototype;
*/
Object.setPrototypeOf(obj, C.prototype);
/**
* 执行构造函数
*
* 需要接收结果用以判断
*/
const result = C.apply(obj, args);
/**
* 需要判断调用构造函数后返回值 result 是否引用类型
*
* 如果是 result 是引用类型,意味着 new 操作符无效,返回 result
* 如果不是,则忽略 result,返回前面一直要处理的对象 obj
*/
return result instanceof Object ? result : obj;
}
上面 return 之所以要判断,是因为有些构造函数不正常,而你写的这个 myNew 要处理这些情况
你可以看下对下面三种构造函数使用 new 之后的产物就会明白了
// 正常写法
function Normal(name) {
this.name = name;
}
// 不按套路来出牌,但也能正常实例化
function SpecialButOk(name) {
this.name = name;
return "abcdef";
}
// new 操作符无效
function NotOk(name) {
this.name = name;
return {
age: 18,
};
}
setTimeout 模拟实现 setInterval
带清除定时器的版本
思考下为什么要用 setTimeout 替代 setInterval
function mySetTimeout(fn, timeout) {
let timer;
const set = function () {
timer = window.setTimeout(function () {
fn();
set();
}, timeout);
};
set();
return {
cancel: function () {
window.clearTimeout(timer);
timer = null;
},
};
}