手写instanceof
const newInstanceof = (val, type) => {
// 判断基础类型直接返回false
if (val === null || typeof val !== 'object') return false
// 获取到 val 的原型对象
let valProto = Object.getPrototypeOf(val)
while (true) { //循环往下寻找,直到找到相同的原型对象
if (valProto === null) return false
// 判断构造函数 type的prototype与val的原型相等不
if (valProto === type.prototype) return true
valProto = Object.getPrototypeOf(valProto)
}
}
手写 call
let obj = {
name: '九思'
}
function fn () {
console.log(a+b+this.name)
}
//
Function.prototype.myCall = function (obj, ...args) {
// 1. 将方法挂载到我们传入的obj上下文对象上
// 2. 然后将挂载以后的方法调用(执行对象的这个方法)
// 3. 将添加的这个属性删除
console.log(this) // 是fn 函数 谁调用myCall 谁就是this
// 保证 当前xxx 是唯一的, 防止obj本身就有xxx这个属性
let xxx = Symbol(1)
obj[xxx] = this // 把调用myCall方法的那个fn传给了他
// 1. myCall 的内部this 是指向调用者fn函数(对象的)
// 2. Obj.xxx 就是fn函数,obj对象调用了fn函数因此fn函数内部this指向了obj (this 谁调用指向谁)
obj[xxx](...args)
delete obj[xxx]
}
fn.myCall(obj, 'this','name')
手写 apply
Function.prototype.myApply = function (obj, args = []) {
// 将方法挂载到我们传入的ctx
// 然后将挂载以后的方法调用
// 将添加的这个属性删除
// 判断参数是否是数组
let fn = Symbol(1)
if (args && !(args instanceof Array)) {
// 报错
throw('只接收数组')
}
obj[fn] = this
obj[fn](...args)
delete obj[fn]
}
手写 bind
Function.prototype.myBind = function (obj, ...args) {
let fn = Symbol(1)
return (...args1) => {
obj[fn] = this
obj[fn](...args.concat(args1))
delete obj[fn]
}
}
手写new 操作符
- 创建一个空对象
- 连接该对象,将该对象的原型对象连接到构造函数的原型对象上
- 将创建的对象作为this的上下文
- 若函数没有返回对象则返回this
看到一个通俗的介绍 某开发商先盖了一间样板房(prototype)然后根据用户需求增量修改图纸 (constructor)然后根据图纸制造出个性化的房子(instance)
function myNew(Constructor, ...args) {
// 创建对象 直接继承构造函数的原型对象,相当于设置obj.__proto__ = Constructor.prototype
const obj = Object.create(Constructor.prototype)
const result = Constructor.call(obj, ...args)
// result || obj 防止返回的是 null(因为 typeof null == 'object');
return typeof result === 'object' ? result || obj : obj
}
使用setInterval实现setTimeout
const mySetTimeout = (func, time) => {
let timerId = setInterval(() => {
if (timerId) {
clearInterval(timerId);
}
func();
}, time);
return () => clearInterval(timerId);
};
setTimeout 实现 setInterval
const mySetInterval = (func, time) => {
let timeID = null;
const fn = () => {
timeID = setTimeout(() => {
func();
fn();
}, time);
};
fn();
return () => clearTimeout(timeID);
};
实现深拷贝和浅拷贝
浅拷贝
1. ...运算符
2. Object.assign()
3. slice / concat
4. Array.from
深拷贝
1. 使用JSON.parse(JSON.stringify(a))
// 1. 会忽略 undefined symbol 函数
// 2. 循环引用会报错
// 3. new Date 转化结果不正确会被当作字符串处理 正则会被忽略
// 4. NaN infinity 会被当成null
2. 自定义
function cloneDeep(target) {
if (obj === null) return null;
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Date) return new Date(obj);
if (target && typeof target === 'object') {
// 声明拷贝
let cloneTarget = Array.isArray(target) ? [] : {}
for (let key in target) {
// 判断 target value 是不是对象,是就递归
if (target.hasOwnProperty(key)) {
if (target[key] && typeof target[key] === 'object') {
cloneTarget[key] = cloneDeep(target[key])
} else {
cloneTarget[key] = target[key]
}
}
}
return cloneTarget
} else {
return target
}
}
3. 使用 reduce 函数
const deepCopy = function(obj){
return Object.keys(obj).reduce(function (copy, item){
// 如果对象的 value 类型为 object 就再次执行 deepCopy 来实现深拷贝
copy[item] = typeof obj[item] === 'object' ? deepCopy(obj[item]) : obj[item];
return copy;
// 判断 obj 是 数组类型 还是对象类型
}, Object.prototype.toString.call(obj) === '[object Array]' ? [] : {})
}
4.
const deepClone = (target, hash = new WeakMap()) => {
if (target === null) return target;
if (target instanceof Date) return new Date(target); // 处理日期
if (target instanceof RegExp) return new RegExp(target);
if (target instanceof HTMLElement) return target; // 处理dom 元素
if (typeof target === "function") return target; // 处理函数
if (typeof target !== "object") return target; // 处理原始类型或者函数
if (hash.get(target)) return hash.get(target); // 当需要拷贝当前对象时候,先去存储空间找,找到直接返回
const cloneTarget = new target.constructor(); // 创建一个新的克隆对象或克隆数组
hash.set(target, cloneTarget); // 存储空间没有就存进hash 中
Reflect.ownKeys(target).forEach((key) => {
// 利用Reflect 来处理Symbol 建明
cloneTarget[key] = deepClone(target[key], hash);
});
return cloneTarget;
};
手写防抖
function debounce(fn, ms) {
let timer = null;
return function (...args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn(...args)
timer = null;
}, ms);
}
}
useDebounce
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)
}
手写节流
function throttle(fn, ms) {
let lastTime = 0;
return function (arguments) {
let nowTime = new Date().getTime()
if (nowTime - lastTime > ms) {
fn.apply(this, arguments)
lastTime = nowTime
}
}
}
useThrottle
function useThrottle(fn, delay, dep = []) {
const { current } = useRef({ fn, timer: null });
useEffect(function () {
current.fn = fn;
}, [fn]);
return useCallback(function f(...args) {
if (!current.timer) {
current.timer = setTimeout(() => {
delete current.timer;
}, delay);
current.fn(...args);
}
}, dep);
}
发布订阅模式
基于一个事件中心,要接收通知的对象通过定义事件订阅,然后保存到事件中心,发布通知的对象触发后,通过事件中心执行里面的订阅事件
const Events = {
listenList: {},
age: 16,
listen: (key, fn) => {
if (!Events.listenList[key]) {
Events.listenList[key] = [];
}
Events.listenList[key].push(fn);
},
emit: (key, ...value) => {
const listenList = Events.listenList[key];
if (listenList) {
listenList.forEach((item) => item(...value));
}
},
remove: (key, fn) => {
if (!Events.listenList[key]) return false;
if (!fn) {
delete Events.listenList[key];
} else {
const currentList = Events.listenList[key];
currentList.forEach((fn, i) => {
if (fn === fn) {
currentList.splice(i, 1);
}
});
}
},
};
Events.listen("houser", (args) => {
console.log("args houser", args, "---->housrer");
});
Events.listen("car", (args) => {
console.log("args car", args, "---->car");
});
Events.emit("houser", 2500000);
Events.emit("car", 2500000);
发布订阅模式,一般是先订阅然后发布,但是,发布者发布事件的时候,不关心有没有订阅,如果没有订阅也能发布,订阅者不管心有没有事件发布,只要自己的事件发生,他就执行。所以,发布订阅模式中,发布者和订阅者互不关心对方,耦合度低,订阅者也可以自定义事件处理程序。
缺点: 当事件越来越多时候,命名不规范可能会导致不好维护。
观察者模式
观察者模式分为观察者和被观察者
被观察者相当于发布者, 当被观察者的状态变化的时候,会主动去通知所有的观察者, 将参数传给他们,然后执行让观察者执行他们自己的方法。
观察者和被观察者耦合在一起,必须要被观察者中注册了观察者后,才能和该观察者进行参数传递,并且还是当被观察者状态变化后,主动通知
// 观察者模式 内部基于发布订阅
class Subject {
// 被观察者
constructor() {
this.arr = [];
}
attach(fn) {
// 被观察者要接收观察者
this.arr.push(fn);
}
setState(args) {
this.arr.forEach((item) => item.update(args));
}
}
class Observer {
// 观察者
update(args) {
console.log("被观察者说 " + args, "--->args");
}
}
let observer1 = new Observer("第一个观察者");
let observer2 = new Observer("第二个观察者");
let subject = new Subject();
// 搜集观察者
subject.attach(observer1);
subject.attach(observer2);
subject.setState("啦啦啦");
柯里化
先来介绍几个概念
-
柯里化: 的含义就是让一个函数变的更具体一些, 原则上返回的函数只能接受一个参数,多个参数也暂且认为是柯里化 sum(1)(2)
-
偏函数: 返回一个函数,函数的参数不止一个 sum(1,2)(3,4)(5)
-
反柯里化: 让函数的作用范围变大
// 柯里化函数其实相当于一个高阶函数
// 用高级函数提前把其中一个变量写好。调用的时候只需要填写一个变量就好
// 高阶函数可以暂存变量(内部有闭包)
const add = (x, y, z, m) => {
return x + y + z + m;
};
// console.log(add(1, 2, 3, 4));
const curry = (x) => {
return (y) => {
return (z) => {
return (m) => {
return x + y + z + m;
};
};
};
};
// console.log(curry(1)(2)(3)(4));
// 要判断当前传入函数的参数个数 (args.length)
// 是否大于等于原函数所需参数个数 (fn.length) ,
// 如果是,则执行当前函数;如果是小于,则返回一个函数。
const curryFun = (fn) => {
const curry = (...args) => {
if (args.length >= fn.length) {
return fn(...args);
} else {
return (...a) => curry(...args, ...a);
}
};
return curry;
};
const curry1 = curryFun(add);
console.log(curry1(1, 2, 3)(4));
console.log(curry1(1)(2)(3)(4));
console.log(curry1(1)(2)(3, 4));
手写promise.all
function promiseAll(list) {
return new Promise((resolve, reject) => {
const r = [];
const promiseList = Array.from(list);
let count = 0;
for (let i = 0; i < promiseList.length; i++) {
Promise.resolve(promiseList[i])
.then((o) => {
r[i] = o;
count++;
if (count === promiseList.length) {
return resolve(r);
}
})
.catch((e) => reject(e));
}
});
}
手写promise.race
// 如果是一般值,p1成功,value就是这个值
const p1 = Promise.resolve(2);
// 如果是成功的primie, p2成功,value 就是这个promise成功的value
const p2 = Promise.resolve(Promise.resolve(3));
// 如果是失败的promise. p3失败,reason 是这个promise 的reason
const p3 = Promise.resolve(Promise.reject(4));
// Promise函数对象的reject方法
// 返回一个指定reason的失败的Promise
Promise.reject = function (reason) {
// 返回一个失败的promise
return new Promise((resolve, reject) => {
reject(reason);
});
};
// promise 函数对象的resolve方法
// 返回一个指定结果的成功的promies
Promise.resolve = function (value) {
// 返回一个成功或者失败的promise
return new Promise((resolve, reject) => {
// 如果value 是一个promise
if (value instanceof Promise) {
// 使用value结果作为promise结果
value.then(resolve, reject);
} else {
// value 不是promise => promise 变为成功,数据是value
resolve(value);
}
});
};
// Promise.all
// 返回一个新的promise
Promise.all = function (promises) {
const values = new Array(promises.length); // 用来保存所有成功values的数组
// 记录成功promise的数量
let i = 0;
return new Promise((resolve, reject) => {
// 遍历promise获取每一个promise的结果
promises.forEach((p, index) => {
Promise.resolve(p).then(
(value) => {
// p成功,将成功的value保存到values中
// 不能直接values.push(value)这样不能保证顺序,成功的会先push
values[index] = value;
i++;
// 如果全部成功,将return的promise改变成功
if (i === promises.length) {
resolve(values);
}
},
(reason) => {
// 只要有一个失败的,return的promise就失败
reject(reason);
}
);
});
});
};
// Promise.race
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
// 遍历promise获取每一个promise的结果
promises.forEach((p, index) => {
p.then(
(value) => {
// 一旦有成功的。返回的变为成功
resolve(value);
},
(reason) => {
// 只要有一个失败的,return的promise就失败
reject(reason);
}
);
});
});
};
手写promise
简单实现
class MyPromise {
PromiseStatus = 'pending'
PromiseResult = undefined
// 保存两组回调函数
resolveList = []
rejectList = []
constructor (excetor) {
excetor(this.resolve.bind(this), this.reject.bind(this))
}
resolve (value) {
if (PromiseStatus !== "pending") return
this.PromiseStatus = 'fulfilled'
this.PromiseResult(reason)
while (this.resolveList.length) {
this.resolveList.shift()()
}
}
reject (reason) {
if (PromiseStatus !== "pending") return
this.PromiseStatus = 'reject'
this.PromiseResult(reason)
while (this.rejectList.length) {
this.rejectList.shift()()
}
}
// then 方法本身会返回一个新的promise对象
// 该对象的状态和结果由回调函数的返回值决定
/*
返回值是promise对象
返回值为成功,新promise就是成功
返回值为失败,新promise就是失败
返回值非promise对象
新promise就是成功,他的值就是返回值
*/
then (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason }
const thenPromise = new MyPromise((resolve, reject) => {
const resolvePromise = cb => {
// 异步返回值异常外面补货不到
queueMicrotask(() => {
try {
let s = cb(this.PromiseResult)
if (x === thenPromise) {
throw new Error('不能返回自身')
}
if (s instanceof MyPromise) {
s.then(resolve, reject)
} else {
resolve(s)
}
} catch (error) {
reject(err)
}
})
}
if (this.PromiseStatus === 'fulfilled') {
resolvePromise(onFulfilled)
} else if (this.PromiseStatus === 'rejectd') {
resolvePromise(onRejected)
} else if (this.PromiseStatus === 'pending') {
this.resolveList.push(resolvePromise.bind(this, onFulfilled))
this.rejectList.push( resolvePromise.bind(this, onRejected))
}
})
return thenPromise
}
}
箭头函数和普通函数区别
1.箭头函数不能用来创建生成器(即不能写为生成器函数)