重要api实现
用ES5实现数组的map方法
Array.prototype.MyMap = function(fn, context){
//不修改原来的数组。由于是ES5所以就不用...展开符了。
var arr = Array.prototype.slice.call(this);
var mappedArr = [];
for (var i = 0; i < arr.length; i++ ){
mappedArr.push(fn.call(context, arr[i], i, this));
}
return mappedArr;
}
用ES5实现数组的reduce方法
Array.prototype.myReduce = function(fn, initialValue) {
var arr = Array.prototype.slice.call(this);
var res, startIndex;
// 初始值不传怎么处理
res = initialValue ? initialValue : arr[0];
startIndex = initialValue ? 0 : 1;
for(var i = startIndex; i < arr.length; i++) {
res = fn.call(null, res, arr[i], i, this);
}
return res;
}
用ES5实现数组的filter方法
Array.prototype.filter = function (callback) {
const res = []
for (let i = 0; i < this.length; i++) {
callback(this[i], i, this) && res.push(this[i])
}
return res
}
用ES5实现数组的findIndex方法
Array.prototype.findIndex = function (callback) {
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) {
return i
}
}
return -1
}
实现call/apply
//实现apply只要把下一行中的...args换成args即可
Function.prototype.myCall = function(context = window, ...args) {
// 利用this的上下文特性。
let func = this;
let fn = Symbol("fn");
context[fn] = func;
//重点代码,利用this指向,相当于context.caller(...args)
let res = context[fn](...args);
delete context[fn];
return res;
}
实现Object.assign方法
Object.prototype.assign = function (target, ...args) {
if (target === null || target === undefined) {
throw new TypeError('Cannot convert undefined or null to object')
}
target = Object(target)
for (let nextObj of args) {
for (let key in nextObj) {
nextObj.hasOwnProperty(key) && (target[key] = nextObj[key])
}
}
return target
}
实现Object.create方法
function create(proto) {
function F() {};
F.prototype = proto;
F.prototype.constructor = F;
return new F();
}
实现bind方法
// 对于普通函数,绑定this指向;对于构造函数,要保证原函数的原型对象上的属性不能丢失
Function.prototype.bind = function(context, ...args) {
//this表示调用bind的函数
let self = this;
let fBound = function() {
//this instanceof fBound为true表示构造函数的情况。如new func.bind(obj)
return self.apply(
this instanceof fBound ? this : context || window,
args.concat(Array.prototype.slice.call(arguments))
);
}
//保证原函数的原型对象上的属性不丢失
fBound.prototype = Object.create(this.prototype);
return fBound;
}
实现new关键字
// 创建一个全新的对象,这个对象的__proto__要指向构造函数的原型对象
// 执行构造函数,apply把this指向新对象
// 返回值为object类型则作为new方法的返回值返回,否则返回上述全新对象
function myNew(fn, ...args) {
let instance = Object.create(fn.prototype);
let res = fn.apply(instance, args);
return typeof res === 'object' ? res: instance;
}
实现instanceof的作用
// 原型链的向上查找。
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left);
while(true) {
if(proto == null) return false;
if(proto == right.prototype) return true;
proto = Object.getPrototypeof(proto);
}
}
实现单例模式
// 用闭包和Proxy属性拦截。
function proxy(func) {
let instance;
let handler = {
construct(target, args) {
if(!instance) {
instance = Reflect.construct(func, args);
}
return instance;
}
}
return new Proxy(func, handler);
}
实现数组的flat
// 递归处理
let result = [];
let fn = function(ary) {
for(let i = 0; i < ary.length; i++) {
let item = ary[i];
if (Array.isArray(ary[i])){
fn(item);
} else {
result.push(item);
}
}
}
// 用栈实现
function flattenWithStack(arr) {
const stack = [...arr];
const result = [];
while (stack.length > 0) {
const currentElement = stack.pop();
if (Array.isArray(currentElement)) {
stack.push(...currentElement);
} else {
result.unshift(currentElement);
}
}
return result;
}
// 用 reduce 实现数组的 flat 方法
function flatten(ary) {
return ary.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
实现防抖功能
// 在定时器的时间范围内再次触发,则重新计时。
const debounce = (fn, delay) => {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
};
// react hook版
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(...args);
}, delay);
}, dep)
}
实现节流功能
// 在定时器的时间范围内再次触发,则不予理睬,等当前定时器完成,才能启动下一个定时器。
const throttle = (fn, delay = 500) => {
let flag = true;
return (...args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args);
flag = true;
}, delay);
};
};
用发布订阅模式实现EventEmit
class Subscribe {
constructor() {
//创建容器
this.pond = [];
}
//向容器中增加方法,注意去重
add(fn) {
let pond = this.pond,
isExist = false;
//去重环节
pond.forEach(item => item === fn ? isExist = true : null);
!isExist ? pond.push(fn) : null;
}
remove(fn) {
let pond = this.pond;
pond.forEach((item, index) => {
if(item === fn) {
//提一下我在这里遇到的坑,这里如果写item=null是无效的
//例子:let a = {name: funtion(){}};
//let b = a.name;
//这个时候操作b的值对于a的name属性是没有影响的
pond[index] = null;
}
})
}
fire(...arg) {
let pond = this.pond;
for(let i = 0; i < pond.length; i++) {
let item = pond[i];
//如果itme为空了,最好把它删除掉
if (item === null) {
pond.splice(i, 1);
// 如果用了splice要防止数组塌陷问题,
// 即删除了一个元素后,后面所有元素的索引默认都会减1
i--;
continue;
}
item(...arg);
}
}
}
实现深拷贝
const clone = parent => {
// 判断类型
const isType = (target, type) => `[object ${type}]` === Object.prototype.toString.call(target)
// 处理正则
const getRegExp = re => {
let flags = "";
if (re.global) flags += "g";
if (re.ignoreCase) flags += "i";
if (re.multiline) flags += "m";
return flags;
};
const _clone = parent => {
if (parent === null) return null;
if (typeof parent !== "object") return parent;
let child, proto;
if (isType(parent, "Array")) {
// 对数组做特殊处理
child = [];
} else if (isType(parent, "RegExp")) {
// 对正则对象做特殊处理
child = new RegExp(parent.source, getRegExp(parent));
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
} else if (isType(parent, "Date")) {
// 对Date对象做特殊处理
child = new Date(parent.getTime());
} else {
// 处理对象原型
proto = Object.getPrototypeOf(parent);
// 利用Object.create切断原型链
child = Object.create(proto);
}
for (let i in parent) {
// 递归
child[i] = _clone(parent[i]);
}
return child;
};
return _clone(parent);
};
实现Promise
使用ES5实现类的继承效果
function Parent5 () {
this.name = 'parent5';
this.play = [1, 2, 3];
}
function Child5() {
Parent5.call(this);
this.type = 'child5';
}
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;
实现原生的AJAX请求
const ajax = {
get(url, fn) {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)// 第三个参数异步与否
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
fn(xhr.responeText)
}
}
xhr.send()
},
post(url, data, fn) {
const xhr = new XMLHttpRequest()
xhr.open('POST', url, true)
xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded')
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
fn(xhr.responeText)
}
}
xhr.send(data)
}
}
实现一个柯里化函数
function curry(fn, args) {
length = fn.length;
args = args || [];
return function() {
var _args = args.slice(0),
arg, i;
for (i = 0; i < arguments.length; i++) {
arg = arguments[i];
_args.push(arg);
}
if (_args.length < length) {
return curry.call(this, fn, _args);
}
else {
return fn.apply(this, _args);
}
}
}
var fn = curry(function(a, b, c) {
console.log([a, b, c]);
});
fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]
实现一个compose函数
// 函数嵌套调用执行顺序是先内后外。a(b(c(d()))) ====> d() -> c() -> b() -> a()
// `pipe` 函数与 `compose`函数的共同点是都返回“组合函数”,
// 区别则是执行的顺序不同,前者是从左向右执行,后者则是从右向左执行。
function compose(...funs) {
if (funs.length === 0) {
return arg => arg;
}
if (funs.length === 1) {
return funs[0];
}
return funs.reverse().reduce((a, b) => (...arg) => b(a(arg)));
}
实现一个LRU缓存函数
class LRUCache {
constructor(size) {
this.size = size
this.cache = new Map()
}
get(key) {
const hasKey = this.cache.has(key)
if (hasKey) {
const val = this.cache.get(key)
this.cache.delete(key)
this.cache.set(key, val)
return val
} else {
return -1
}
}
put(key, val) {
const hasKey = this.cache.has(key)
if (hasKey) {
this.cache.delete(key)
}
this.cache.set(key, val)
if (this.cache.size > this.size) {
this.cache.delete(this.cache.keys().next().value)
}
}
}
实现一个快速排序
var quickSort = function(arr) {
if (arr.length <= 1) { return arr; }
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++){
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
};
实现一个倒计时
const useCountdown = (options) => {
// 首次初始化数据,显示清除的数据
const [timeInfo, setTimeInfo] = useState(options.showMillisecond);
useEffect(() => {
let timer = 0;
function countdown() {
const nowTime = Date.now();
// 截止时间 - 当前时间 = 剩余时间
const remainTime = (options.deadlineTime - nowTime) / 1000;
// 剩余时间大于 0 才开始倒计时
if (remainTime > 0) {
// 未结束时直接定时下一次在执行判断 countdown
timer = setTimeout(
countdown,
options.showMillisecond ? 100 : 1000 // 毫秒级则修改定时器时间
);
}
setTimeInfo(remainTime);
}
// 开始倒计时
countdown();
return () => {
// 清除定时器
timer && clearTimeout(timer);
};
}, [options.deadlineTime, options.showMillisecond]);
return timeInfo;
};
原生JS
原生JS灵魂之问(上)juejin.cn/post/684490…
原生JS灵魂之问(中)juejin.cn/post/684490…
原生JS灵魂之问(下)juejin.cn/post/684490…
关于JS中一些重要的api实现 juejin.cn/post/684490…
56个JS高级的手写 juejin.cn/post/702390…
一篇够用的TypeScript总结 juejin.cn/post/698172…