一、手写一个new函数
这里啰嗦一下new后发生了什么:
- 一般地,new后面几乎是一个约定的、大写开头的、所谓的、构造函数,(es6的class关键字只是它的语法糖),这里称之为Fn; 第一步会先创建一个对象obj。
- Fn构造函数内部的this会指向第一步创建的obj,this即obj。
- js中由于的每一个函数(除了箭头函数,这也是为什么箭头函数不能被new的原因之一)中都存在一个prototype属性,许多人喜欢叫他‘原型’,我不这么认为,因为他会跟真正的原型__proto__混淆,我一般会叫他‘灵魂’! 随后,obj对象的原型__proto__的指向会被修改至Fn的‘灵魂’prototype上。
- 在Fn中如果没有返回其他的对象(比如 return {} 或者 return function(){}),那么就默认的返回第一步所创建的obj对象。
字多不看,删减版:
- 创建一个对象obj。
- obj成为构造函数Fn内部的this。
- obj的原型指向修改至Fn.prototype上。
- 返回obj。
所以你只要理解这几句话,就可以不惧面试官问了(doge)
function myNew (fn, ...args) {
if (!fn.prototype) return new Error('你小子介个样子传?')
let newObj = {}
let res = fn.apply(newObj, args)
Object.setPrototypeOf(newObj, fn.prototype)
return ['function', 'object'].includes(typeof res) ? res : newObj
}
二、手写 typeof
typeof总是返回一个字符,其实现借用 Object.prototype.toString 即可,收一洗~
function myTypeOf (val) {
let type = Object.prototype.toString.call(val)
return type.slice(8, -1).toLowerCase()
}
三、手写instanceof
“对,没错,instanceof就是用来检测是不是人家的子类,或者说是不是通过new人家出来的实例对象。”
啰嗦一下: 一般地,instanceof的左侧通常是一个对象,右侧通常是一个构造函数(es6中是class名), 以 obj instanceof Fn 为例,instanceof 就是在检查:在obj的一整条原型链(__proto__)上是否存在Fn.prototype所指向的对象。
function myInstanceOf (obj, Fn) {
if (!Fn.prototype) return false
let objProto = Object.getPrototypeOf(obj)
while (objProto) {
if (objProto === Fn.prototype) return true
else objProto = Object.getPrototypeOf(objProto)
}
return false
}
四、手写一个解析url的方法
“就是将url的查询参数给扒下来组合成一个对象而已呀~”
这里用到了内置的URL构造函数,他可是一个好东西啊!靓仔可以多多把玩它。
function parserUrl (url) {
try {
let urlParams = new URL(str).searchParams
let res = {}
for (let item of urlParams.entries()) {
let [k, v] = item
if (res[k]) res[k].push(v)
else res[k] = [v]
}
return res
} catch (e) {
return new Error('你小子乱写url是不是?')
}
}
let url = 'https://juejin.cn/?a=666&b=iloveyou&c=888&a=520&d=youloveme'
parserUrl(url) // {"a":["666","520"],"b":["iloveyou"],"c":["888"],"d":["youloveme"]}
五、 手写apply、call
被apply、call的函数会被立即执行,并将传入的对象作为函数内部的this指向。
这两兄弟做的事情是一样的,第一个参数大家都是传入改变this的对象(传Null即默认为window),第二个参数开始就有区别了,前者是接受一个数组,里面装着会被当做参数传入的内容,后者是一系列参数。
“噢,我的上帝!瞧这糟糕的,我打赌肯定有靓仔把它们记岔,我确信!”
Function.prototype.myApply = function (thisArg = window, args) {
let fn = Symbol('fn')
thisArg[fn] = this
let res = thisArg[fn](...args)
delete thisArg[fn]
return res
}
Function.prototype.myCall = function (thisArg = window, ...args) {
let fn = Symbol('fn')
thisArg[fn] = this
let res = thisArg[fn](...args)
delete thisArg[fn]
return res
}
六、手写bind
“为啥现在才说bind?”
“因为我一直确信先有apply、call,后有bind!”(doge)
bind会返回一个新的函数,这个函数会抛弃他原来(当前)的this指向,而是把调用bind时传入的对象绑定到this上。
// myBind方法要做兼容new操作符处理
Function.prototype.myBind = function myCall (thisArg = window, ...args) {
// 形如 foo.bind(obj), 那么此刻的this就是foo.
let fn = this
// 创建一个空函数,等下会设置他的prototype来做一个参照
function __Prototype () {}
function Fbind (...args2) {
return fn.apply(
// 如果使用了new(即 new Fbind()),那么这里的this就是被new时创建的新对象
// 由于new的原理,this instanceof __Prototype 必是true
// 那么就使用被new时创建的新对象作为this
(
this instanceof __Prototype ? this : thisArg
),
args.concat(args2)
)
}
// 设置参照
__Prototype.prototype = this.prototype
// 这里其实可以这样做:Fbind.prototype = this.prototype,
// 再用new包一层的原因是方便以后对返回的Fbind做拓展,
// 可能会涉及到在Fbind.prototype上做新的方法、属性添加,
// 如果不包一层会会对this.prototype产生污染。
Fbind.prototype = new __Prototype()
return Fbind
}
七、手写深拷贝
浅拷贝的话,洒洒水啦
核心思路就是递归进行键值对的复制。
// 希望各位靓仔不要乱搞对象噢
let obj = { a: 1, b: { c: 2, d: 3 }}
obj.b.c = obj
let obj2 = deepClone(obj)
function deepClone(obj) {
if (typeof obj !== 'object' || obj === null) return obj
let weakSet = new WeakSet()
function dc (obj) {
if (weakSet.has(obj)) throw new Error('你个小子乱搞对象是不是?')
weakSet.add(obj)
let keys = Object.getOwnPropertyNames(obj)
let newObj = {}
for (let key of keys) {
newObj[key] = typeof obj[key] === 'object' ? dc(obj[key]) : obj[key]
}
return newObj
}
return dc(obj)
}
八、手写防抖、节流
这是js闭包的用处之一。
各位靓仔都打过lol吧?
“当你完成了一次很帅气的单杀,在敌人的尸体面前疯狂点回城进行装X时,只有最后一次且之后不再点回城时,才不会打断真正的回城行为,这就是防抖”
“当你用亚索在兵线上快乐的e来e去时,即便你在疯狂的按e,也不能改变只有在e的冷却时间好了之后才能继续使用e的事实,这就是节流”
// 防抖
function debounce(fn, dealy) {
let timer = null
return function (...args) {
if (timer) {
clearTimeout(timer)
timer = null
}
timer = setTimeout(fn, dealy, ...args)
}
}
// 节流
function throttle (fn, delay) {
let last = Date.now()
return function (...args) {
if (Date.now() - last >= delay) {
fn.apply(this, args)
last = Date.now()
}
}
}
九、手写一个订阅发布模式
由于鄙人及其反感类的写法,所以我将使用原型委托的方式,本质上还是利用了原型链机制。
const EventBus = Object.freeze({
on (name, cb) {
if (!arguments.length || typeof cb !== 'function') return
if (this.eventStack[name]) this.eventStack[name].push(cb)
else this.eventStack[name] = [cb]
},
remove (name, cb) {
if (!arguments.length || typeof cb !== 'function' || !this.eventStack[name]?.length) return
this.eventStack[name] = this.eventStack[name].filter(fn => fn !== cb)
},
removeMany (nameArr) {
// 不传参数代表移除所有
if (!arguments.length) {
// 由于createEventBus方法内部将eventStack变成了不可写,
// 所以这里要循环清除,不能直接this.eventStack = {}。
for (let key of Object.getOwnPropertyNames(this.eventStack)) {
delete this.eventStack[key]
}
return
}
if (Array.isArray(nameArr)) {
for (let item of nameArr) {
delete this.eventStack[item]
}
}
},
once (name, cb) {
if (!arguments.length || typeof cb !== 'function') return
function _once (...args) {
Promise.resolve().then(() => cb(...args))
this.remove(name, _once)
}
this.on(name, _once)
},
emit (name, ...args) {
if (!this.eventStack[name]?.length) return
for (let i = this.eventStack[name].length - 1; i >= 0; i-- ) {
let fn = this.eventStack[name][i]
Promise.resolve().then(() => fn?.(...args))
}
}
})
function createEventBus () {
return Object.create(EventBus, {
eventStack: {
value: {}
}
})
}
let $EventBus = createEventBus()
...
...
...
十、手写一个并发控制的方法
“我一般用这个东西去控制同时发送请求的量或者别的什么东西......”
想象一个场景,100个人(argList)排队上厕所拉屎(handler),这个厕所坑位只有3个(limit),假定坑位有一种魔法,当空闲时会自动将人安排进坑位来拉屎;首先一开始肯定会安排1、2、3号拉进坑位,当然他们的拉屎速度是不一样的,过了一会,2号拉完了,那么他所在的坑位空闲了后马上安排4号进坑拉屎,又过了一会,1号拉完了,所在坑位马上安排5号进坑拉屎,如此往复直到所有人拉完......
const limitHttp = (reqList = [], limitNum = 3) => {
return new Promise(resolve => {
if (limitNum > reqList.length) limitNum = reqList.length
let currentHandelIdx = 0
let doneCount = 0
const result = []
const handle = (params) => {
result[params.idx] = params
doneCount += 1
if (doneCount === reqList.length) {
resolve(result)
return false
}
if (currentHandelIdx < reqList.length) {
_fetch(currentHandelIdx++)
}
}
const _fetch = i => {
reqList[i]().then((res) => {
handle({
idx: i,
status: 1,
res
})
}).catch(err => {
handle({
idx: i,
status: -1,
res: err
})
})
}
for (let i = 0 ; i < Math.min(limitNum, reqList.length); i++) {
currentHandelIdx = i
_fetch(i)
}
})
}