new
实现原理:
- 创建一个空对象
- 创建的对象的__proto__指向构造函数的原型对象
- 执行这个函数,并将创建的对象作为this的上下文
- 如果改函数没有返回对象,则返回this
function myNew(fn,...args) {
const obj = Object.create(null)
obj.__proto__ = fn.prototype
const result = fn.apply(obj, args)
const isObject = typeof result === 'object' && result !== null
const isFunction = typeof result === 'function'
if(isObject || isFunction) return result
return obj
}
测试
function P() {
const args = Array.prototype.slice.call(arguments, 0)
console.log(args)
}
var p = myNew(P, 1,2,3)
var p2 = new p(1,2,3)
结果
call,apply
call和apply实现思路一样区别就是传入的参数call是展开参数,apply是数组先看实现步骤:
- 改变上下文this的指向
- 执行这个函数
- 并返回运行结果
call
Function.prototype.myCall = function(context,...args) {
if(typeof this!=='function') {
throw new TypeError('not function')
}
context = context || window
context.fn = this
const result = context.fn(...args)
delete context.fn
return result
}
测试
function p(...args) {
console.log(...args,this.a)
}
const obj = {
a: 2
}
p.myCall(obj, 1)
p.call(obj, 1)
结果
apply
Function.prototype.myApply = function(context,args) {
if(typeof this!=='function') {
throw new TypeError('not function')
}
context = context || window
context.fn = this
const result = context.fn(...args)
delete context.fn
return result
}
测试
function p(...args) {
console.log(...args,this.a)
}
const obj = {
a: 2
}
p.myApply(obj, [1,3])
p.apply(obj, [1,3])
结果
bind
实现原理:创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
Function.prototype.myBind = function() {
if (typeof this !== 'function') {
throw new TypeError('not function');
}
var slice = Array.prototype.slice;
var thatFunc = this, // function p
thatArg = arguments[0]; // 获取传入的对象也就是上下文
var args = slice.call(arguments, 1); // 获取传入的参数
return function(){
var funcArgs = args.concat(slice.call(arguments)) // 合并参数
return thatFunc.apply(thatArg, funcArgs); // 使用apply进行调用
};
};
测试
function p() {
console.log(this)
}
const obj = {
a: 1
}
p.bind(obj)()
p.myBind(obj)()
结果
instanceof
原理:判断某个对象是否属于某个类型,或者是该类型的父类型祖先类型。
function myInstanceof(left, right) {
let leftValue = left.__proto__
let rightValue = right.prototype
while(leftValue) {
if(leftValue===rightValue) {
return true
}
leftValue = leftValue.__proto__
}
return false
}
测试
function P() {}
const p = new P()
console.log(p instanceof P)
console.log(myInstanceof(p, P))
结果
Promise.all
核心思路
- 接收一个Promise实例的数组或者具有Iterator接口的对象作为参数
- 这个方法返回一个新的Promsie对象
- 遍历传入的参数,用Promsie.resolve()将参数进行包裹使其变成一个Promsie对象
- 参数所有回调成功才是成功,返回值数组与参数顺序一致,参数数组只要有一个失败则触发失败状态
function promiseAll(promsies) {
return new Promise((resolve, reject)=> {
if(!Array.isArray(promsies)) {
throw new Error('not Array')
}
let len = promsies.length
let count = 0
let result = []
for(let i=0;i<len;i++) {
Promise.resolve(promsies[i]).then(data=> {
result.push(data)
count++
if(count===len) {
return resolve(result)
}
}).catch(err=> {
return reject(err)
})
}
})
}
测试
const p1 = Promise.resolve(1)
const p2 = Promise.resolve(2)
Promise.all([p1, p2]).then(data=> {
console.log(data)
})
promiseAll([p1, p2]).then(data=> {
console.log(data)
})
结果
resolve态
reject态
Array.map, Array.filter
map和filter就太简单了
map
Array.prototype.myMap = function(cb, thisArgs) {
let i = 0
let len = this.length
let result = []
while(i<len) {
let cbResult = cb.call(thisArgs, this[i], index, this)
result.push(cbResult)
i++
}
return result
}
测试
let arr = [1,2,3]
let newArr1 = arr.myMap((item)=> {
return item * 2
})
let newArr2 = arr.map((item)=> {
return item * 2
})
console.log(newArr1)
console.log(newArr2)
结果
filter
Array.prototype.myFilter = function(cb, thisArgs) {
let i = 0
let len = this.length
let result = []
while(i<len) {
let cbResult = cb.call(thisArgs, this[i], i, this)
// result.push(cbResult)
cbResult && result.push(this[i])
i++
}
return result
}
测试
let arr = [1,2,3]
let newArr1 = arr.myFilter((item)=> {
return item > 1
})
let newArr2 = arr.filter((item)=> {
return item > 1
})
console.log(newArr1)
console.log(newArr2)
结果
发布订阅模式
订阅发布是观察者模式的一个变种,主要作用:
- 可以实现模块间通信
- 可以在一定程度上实现异步编程
class Observer {
constructor() {
this.subMap = {}
}
on(eventName,fn) {
this.subMap[eventName] = this.subMap[eventName] || []
this.subMap[eventName].push(fn)
}
emit(eventName, data) {
const fnList = this.subMap[eventName] || []
fnList.forEach(fn=> {
!fn.once&&fn(data)
})
}
off(eventName, fn) {
const newSubMap = this.subMap[eventName] ? this.subMap[eventName].filter(f=>f!==fn) : []
this.subMap[eventName] = newSubMap
}
once(eventName, data) {
const fnList = this.subMap[eventName] || []
fnList.forEach(fn => {
if(!fn.once) {
fn.once=true
fn(data)
}
})
}
}
测试
const fn = new Observer()
function f1(data) {
console.log(data)
}
fn.on('message', f1)
fn.once('message', 111)
fn.emit('message', 222)
结果
sleep函数
Promise实现
const sleep = (times)=> {
return new Promsie((resolve, reject)=> {
setTimeout(resolve,times)
})
}
在这里分享一个面试题吧,之前被坑过的,如何实现以下代码:u.eat('eat').sleep(2000).lunch('lunch').sleep(3000).eat('eat2').lunch('lunch2') 分析:
- 肯定是一个链式调用
- 需要使用sleep函数
- 如何实现呢?
先看看这样的一个函数,是在翻阅
lerna
时候看到:
function sleep(time=3000) {
return new Promise((resolve,reject)=> {
setTimeout(()=> {
resolve('sleep')
}, time)
})
}
let chain = Promise.resolve()
chain = chain.then((data)=> {
console.log(1)
return sleep()
})
chain = chain.then(()=> {
console.log(2)
})
chain = chain.then((data)=> {
return sleep()
})
chain = chain.then(()=> {
console.log(3)
})
以上代码会先输出1,3s后输出2,在3s后输出3,已经接近我们的目标了,只需要将代码改成链式调用即可,具体实现如下:
class Lazy {
constructor() {
this.chain = Promise.resolve()
}
keep(time) {
return new Promise((resolve, reject)=> {
setTimeout(()=> {
resolve()
}, time)
})
}
sleep(time) {
this.chain = this.chain.then(()=>{
return this.keep(time)
})
return this
}
eat(val) {
this.chain = this.chain.then(()=> {
console.log(val)
})
return this
}
lunch(val) {
this.chain = this.chain.then(()=> {
console.log(val)
})
return this
}
}
const u = new Lazy()
u.eat('eat').sleep(3000).lunch('lunch').sleep(3000).eat('eat2').lunch('lunch2')
函数柯理化
函数柯里化是把一次性传参多个转换为一次传单一的参数,并且返回接收余下参数且返回结果的新函数的技术。代码如下:
function add(...args) {
return args.reduce((a,b)=> a+b)
}
function currying(fn) {
let args = []
return function temp(...newArgs) {
if(newArgs.length) {
args = [
...args,
...newArgs
]
return temp
} else {
let val = fn.apply(this, args)
args = []
return val
}
}
}
let addCurry = currying(add)
console.log(addCurry(1)(2)(3)())
防抖节流
防抖
防抖函数原理:
在事件被触发的n秒后再执行回调,如果在n秒内再次触发则重新计算。
适用场景:
- 按钮提交场景:防止多次提交按钮,只执行最后一次提交
- 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似
- 简化版本
function debounce(fn,wait=200) {
let timer = null
return (...agrs)=> {
clearTimeout(timer)
timer = setTimout(()=> {
fn.apply(this, args)
}, wait)
}
}
- 立即执行版本
function debounce(fn,wait=200,immediate) {
let timer = null
return function() {
const context = this
const args = arguments
if(timer) {
clearTimeout()
}
if(immediate) {
const callNow = !timer
timer = setTimout(function() {
timer = null
}, wait)
if(callNow) {
fn.apply(context, args)
}
} else {
timer = setTimeout(function() {
fn.apply(context, args)
},wait)
}
}
}
节流
在规定的单位时间内,只能触发一次函数,如果这个单位时间内触发多次函数,只有一次生效。
fucntion throllte(fn,wait) {
let timer = null
return function() {
const context = this
const args = arguments
if(!timer) {
timer = setTimout(()=> {
timer = null
fn.apply(context, args)
},wait)
}
}
}
写在最后
暂时先到这,后续更新