每日消息!学会JavaScript手写代码秘籍14道常用api
愿意花更多的时间、倾注更多的精力来跟大家一起努力。
目录apply
async await
(资料图片)
bind
call
concurrent-request
debounce
deep-copy
event-bus
继承
instanceof
new
object-create
promise
throttle
参与
apply为函数绑定执行上下文
原理:将函数设置为执行上下文的一个方法,然后调用执行上下文的方法
tx 指定的函数执行上下文
args 剩余参数组成的数组
any 返回函数的执行结果
// 为函数绑定执行上下文// 原理:将函数设置为执行上下文的一个方法,然后调用执行上下文的方法// ctx 指定的函数执行上下文// args 剩余参数组成的数组// any 返回函数的执行结果Function.prototype.myApply = function (ctx, args) { // fn.myApply(ctx, [arg1, arg2]) // this是正在执行的函数 const fn = this // 保证 ctx[key] 的唯一性,避免和用户设置的 context[key] 冲突 const key = Symbol() // 将执行函数设置到指定的上下文对象上 ctx[key] = fn // 执行函数 const res = ctx[key](...args) // 删除上下文上的 fn 方法 delete ctx[key] // 返回函数的执行结果 return res}复制代码async await
async await 是 Generator 的语法糖,其本质是 Generator + 自动执行器
Generator 函数
执行 generator 函数,拿到 yield 表达式的执行结果 => { next: () => void }
自动执行器
{ value: any, done: boolean }
说明 yield 后面跟的是 Promise 实例
// async await 是 Generator 的语法糖,其本质是 Generator + 自动执行器// Generator 函数module.exports = function asyncAwait(generatorFn) { // 执行 generator 函数,拿到 yield 表达式的执行结果 => { next: () => void } const yieldExpRet = generatorFn() // 自动执行器 function autoActuator() { // { value: any, done: boolean } const ret = yieldExpRet.next() if (!ret.done) { if (object.prototype.toString.call(ret?.value?.then) === "[object Function]") { // 说明 yield 后面跟的是 Promise 实例 ret.value.then(() => { autoActuator() }) } else { // 同步 autoActuator() } } } autoActuator()}复制代码bind
为函数绑定执行上下文
原理:将函数设置为执行上下文的一个方法,然后调用执行上下文上的方法
ctx 指定的函数执行上下文
args 剩余参数组成的数组
fn.myBind(ctx, [arg1, arg2])
this是正在执行的函数
保证 ctx[key] 的唯一性,避免和用户设置的 context[key] 冲突
将执行函数设置到指定的上下文对象上
返回一个可执行函数
bind 方法支持预设一部分参数,剩下的参数通过返回的函数设置,具有柯里化的作用
执行函数
// 为函数绑定执行上下文// 原理:将函数设置为执行上下文的一个方法,然后调用执行上下文上的方法// ctx 指定的函数执行上下文// args 剩余参数组成的数组Function.prototype.myBind = function (ctx, ...args) { // fn.myBind(ctx, [arg1, arg2]) // this是正在执行的函数 const fn = this // 保证 ctx[key] 的唯一性,避免和用户设置的 context[key] 冲突 const key = Symbol() // 将执行函数设置到指定的上下文对象上 ctx[key] = fn // 返回一个可执行函数 // bind 方法支持预设一部分参数,剩下的参数通过返回的函数设置,具有柯里化的作用 return function(...otherArgs) { // 执行函数 return ctx[key](...args, ...otherArgs) }}复制代码call
为函数绑定指定上下文
原理:将函数设置为执行上下文的一个方法,然后调用执行上下文上的方法
ctx 指定的函数执行上下文
args 剩余参数组成的数组
any 返回函数的执行结果
fn.myCall(ctx, arg1, arg2)
this是正在执行的函数
保证 ctx[key] 的唯一性,避免和用户设置的 context[key] 冲突
将执行函数设置到指定的上下文对象上
执行函数
删除上下文上的fn方法
返回函数的执行结果
// 为函数绑定指定上下文// 原理:将函数设置为执行上下文的一个方法,然后调用执行上下文上的方法// ctx 指定的函数执行上下文// args 剩余参数组成的数组// any 返回函数的执行结果Function.prototype.myCall = function (ctx, ...args) { // fn.myCall(ctx, arg1, arg2) // this是正在执行的函数 const fn = this // 保证 ctx[key] 的唯一性,避免和用户设置的 context[key] 冲突 const key = Symbol() // 将执行函数设置到指定的上下文对象上 ctx[key] = fn // 执行函数 const res = ctx[key](...args) // 删除上下文上的fn方法 delete ctx[key] // 返回函数的执行结果 return res}复制代码concurrent-request
并发请求,控制请求并发数
taskQueues 一个个请求任务组成的数组
concurrentNum 请求的并发数
存放所有任务的执行结果
开始先发送指定数量的并发请求
当每个请求完成后再递归的调用自身,发送任务队列的下一个请求
递归终止条件(任务队列为空)
从任务队列中弹出一个任务
执行任务
当任务完成后递归调用 req, 发送队列中的下一个请求
并将任务结果 push 进结果数组中
// 并发请求,控制请求并发数// taskQueues 一个个请求任务组成的数组// concurrentNum 请求的并发数module.exports = function concurrentRequest(taskQueues = [], concurrentNum = 1) { return new Promise(resolve => { // 存放所有任务的执行结果 const taskRet = [] // 开始先发送指定数量的并发请求 while (concurrentNum > 0) { req() concurrentNum-- } // 当每个请求完成后再递归的调用自身,发送任务队列的下一个请求 function req() { // 递归终止条件(任务队列为空) if (!taskQueues.length) return Promise.allSettled(taskRet).then(res => { resolve(res) }) // 从任务队列中弹出一个任务 const task = taskQueues.shift() // 执行任务 const ret = task() // 当任务完成后递归调用 req, 发送队列中的下一个请求 res.then(() => { req() }) // 并将任务结果 push 进结果数组中 taskRet.push(ret) } })}复制代码debounce
防抖
原理:事件被触发 wait 毫秒后执行回调fn, 如果在wait期间再次触发事件,则重新计时
fn 事件触发后的回调函数
wait 延迟时间,wait 毫秒后执行fn
返回经过包装后的事件处理函数
定时器,这里用到了闭包
返回经过包装后的事件处理函数
如果 timer 为不为空,则说明在 wait 时间内已经触发过该事件了,而且事件处理函数仍未被调用
说明在wait事件内事件被重复触发了,则需要进行防抖处理,即清除之前的定时器,这样上一次事件触发后的回调就不会被执行
定时器也会重新设置
通过定时器来实现事件触发后在 wait 毫秒后执行事件处理函数
需要给回调绑定上下文this,即触发事件的目标对象
// 防抖// 原理:事件被触发 wait 毫秒后执行回调fn, 如果在wait期间再次触发事件,则重新计时// fn 事件触发后的回调函数// wait 延迟时间,wait 毫秒后执行fn// 返回经过包装后的事件处理函数function debounce(fn, wait = 50) { // 定时器,这里用到了闭包 let timer = null // 返回经过包装后的事件处理函数 return function(...args) { // 如果 timer 为不为空,则说明在 wait 时间内已经触发过该事件了,而且事件处理函数仍未被调用 // 说明在wait事件内事件被重复触发了,则需要进行防抖处理,即清除之前的定时器,这样上一次事件触发后的回调就不会被执行 // 定时器也会重新设置 if (timer) { clearTimeout(timer) } // 通过定时器来实现事件触发后在 wait 毫秒后执行事件处理函数 timer = setTimeout(() => { // 需要给回调绑定上下文this,即触发事件的目标对象 fn.apply(this, args) timer = null }, wait) }}复制代码deep-copy
深拷贝
src 原数据
返回拷贝后的数据
拷贝原始值,直接返回原始值本身
解决循环引用的问题
拷贝数组
拷贝 Map 对象
拷贝函数
拷贝对象
判断数据是否为原始值类型(Number, Boolean,String,Symbol ,BigInt ,Null ,Undefined)
Number,Boolean,String,Symbol,BigInt,Null,Undefined,Object,Array,Function,Date...
// 深拷贝// src 原数据// 返回拷贝后的数据module.exports = function deepCopy(src, cache = new WeakMap()) { // 拷贝原始值,直接返回原始值本身 if (isPrimitiveType(src)) return src // 解决循环引用的问题 if (cache.has(src)) return src cache.set(src, true) // 拷贝数组 if (isArray(src)) { const ret = [] for (let i = 0, len = src.length; i < len; i++) { ret.push(deepCopy(src[i], cache)) } return ret } // 拷贝 Map 对象 if (isMap(src)) { const ret = new Map() src.forEach((value, key) => { ret.set(key, deepCopy(value, cache)) }) return ret } // 拷贝函数 if (isFunction(src)) { copyFunction(src) } // 拷贝对象 if (isObject(src)) { // 获取对象上的所有key const keys = [...Object.keys(src), ...Object.getOwnPropertySymbols(src)] const ret = {} // 遍历所有的key,递归调用 deepCopy 拷贝 obj[key] 的值 keys.forEach(item => { ret[item] = deepCopy(src[item], cache) }) // 返回拷贝后的对象 return ret }}// 判断数据是否为原始值类型(Number, Boolean,String,Symbol ,BigInt ,Null ,Undefined)function isPrimitiveType(data) { const primitiveType = ["Number", "Boolean", "String", "Symbol", "BigInt", "Null", "Undefined"] return primitiveType.includes(getDataType(data))}// 判断数据是否为Object类型function isObject(data) { return getDataType(data) === "Object"}// 判断数据是否为函数function isFunction(data) { return getDataType(data) === "Function"}// 判断数据是否为数组function isArray(data) { return getDataType(data) === "Array"}// 判断数据是否为Mapfunction isMap(data) { return getDataType(data) === "Map"}// 获取数据类型// Number,Boolean,String,Symbol,BigInt,Null,Undefined,Object,Array,Function,Date...function getDataType(data) { return Object.prototype.toString.apply(data).slice(8, -1)}// 拷贝函数function copyFunction(src) { const fnName = src.name let srcStr = src.toString() // 匹配function fnName, 比如 function fnName() {} const fnDecExp = new RegExp(`function (${fnName})?`) // 切除匹配内容,srcStr = (xxx) {} 或 (xxx) => {} srcStr = srcStr.replace(fnDecExp, "") // 匹配函数参数 const argsExg = /\((.*)\)/ let args = argsExg.exec(srcStr) // 函数体 const fnBody = srcStr.replace(argsExg, "").trim() // { return "test" } => return "test" const fnBodyCode = /^{(.*)}$/.exec(fnBody) // 得到了函数的名称,参数,函数体,重新声明函数 return new Function(...args[1].split(","), fnBodyCode[1])}复制代码event-bus
Event bus
发布订阅设计模式的应用,node.js 的基础模块,也是前端组件通信的一种手段,比如Vue的
以事件名为key,事件处理函数组成的数组为value
监听事件
eventName 事件名
cb 事件处理函数
// Event bus// 发布订阅设计模式的应用,node.js 的基础模块,也是前端组件通信的一种手段,比如Vue的$on和$emitfunction EventBus() { // 以事件名为key,事件处理函数组成的数组为value this.events = {}}module.exports = EventBus// 监听事件// eventName 事件名// cb 事件处理函数EventBus.prototype.$on = function(eventName, cb) { if (!Array.isArray(cb)) { cb = [cb] } this.events[eventName] = (this.events[eventName] || []).concat(cb)}EventBus.prototype.$emit = function(eventName, ...args) { this.events[eventName].foEach(fn => { fn.apply(this, args) })}复制代码继承
JavaScript 的继承方式有很多,比如简单的基于 Object.create 实现的继承,每种方式或多或少都有些缺陷
这种缺陷是语言层面导致的,避免不了,即使是 class 语法(糖)。
组合式继承,class 语法糖的本质
在this上继承父类的属性
继承父类的方法
恢复子类的构造函数,上面一行会将 Child.prototype.constructor 改为 Parent.prototype.constructor
// JavaScript 的继承方式有很多,比如简单的基于 Object.create 实现的继承,每种方式或多或少都有些缺陷,// 这种缺陷是语言层面导致的,避免不了,即使是 class 语法(糖)。// 组合式继承,class 语法糖的本质function Parent(...args) { this.name = "Parent name" this.args = args}Parent.prototype.parentFn = function() { console.log("name = ", this.name) console.log("args = ", this.args)}function Chid(args1,args2) { // 在this上继承父类的属性 Parent.call(this, args1, args2) this.childName = "child name"}// 继承父类的方法Child.prototype = Object.create(Parent.prototype)// 恢复子类的构造函数,上面一行会将 Child.prototype.constructor 改为 Parent.prototype.constructorChild.prototype.constructor = Childmodule.exports = Child复制代码instanceof
instanceof运算符
定义:判断对象是否属于某个构造函数的实例
原理:判断构造函数的原型对象是否出现在对象的原型链上
// instanceof运算符// 定义:判断对象是否属于某个构造函数的实例// 原理:判断构造函数的原型对象是否出现在对象的原型链上module.exports = function customINstanceof (ins, constructor) { const proto = Object.getPrototypeOf(ins) if (proto === constructor.prototype) return true if (!proto) return false return customINstanceof(proto, constructor)}复制代码new
new 运算符
作用:负责实例化一个类(构造函数)
1.创建一个构造函数原型对象为原型的对象
2.以第一步的对象为上下文执行构造函数
3.返回值,如果函数有返回值,则返回函数的返回值,否则返回第一步创建的对象。
// new 运算符// 作用:负责实例化一个类(构造函数)// 原理:// 1.创建一个构造函数原型对象为原型的对象// 2.以第一步的对象为上下文执行构造函数// 3.返回值,如果函数有返回值,则返回函数的返回值,否则返回第一步创建的对象。// Function 构造函数// Array 构造函数的其他参数组成的数组// 对象实例module.exports = function newOperator(constructor, ...args) { const ins = Object.create(constructor.prototype) const res = constructor.apply(ins, args) return res || ins}复制代码object-create
proto 新对象的原型对象
props Object.defineProperties 的第二个参数,要定义其可枚举属性或修改的属性描述符的对象。对象中存在的属性描述符:数据描述符和访问器描述符
// Object.create// proto 新对象的原型对象// props Object.defineProperties 的第二个参数,要定义其可枚举属性或修改的属性描述符的对象。对象中存在的属性描述符:数据描述符和访问器描述符Object.myCreate = function(proto, props) { if (typeof proto !== "object") { console.error("Object prototype may only be an Object or null") return } // 创建的空对象 const obj = {} // 设置原型对象 Object.setPrototypeOf(obj, proto) // 设置对象的初始数据 if (props) { Object.defineProperties(obj, props) } return obj}复制代码promise
Promise,解决了回调地狱的问题
executor 同步执行
promise 状态不可逆
then 回调必须在 promise 状态改变后执行
promise 链式调用,后一个回调的参数是前一个回调的返回值
实例化 Promise 时 executor 被同步执行
// Promise,解决了回调地狱的问题// executor 同步执行// promise 状态不可逆// then 回调必须在 promise 状态改变后执行// promise 链式调用,后一个回调的参数是前一个回调的返回值// 实例化 Promise 时 executor 被同步执行function MyPromise(executor) { // 缓存this实例 const _self = this this.status = "pending" this.value = undefined this.reason = undefined this.fulfilledCb = () => {} this.rejectedCb = () => {} function resolve(value) { setTimeout(() => { // 状态不可逆 if (_self.status === "pending") { _self.status = "fulfilled" _self.value = value _self.fulfilledCb(value) } }) } function reject(errMsg) { setTimeout(() => { // 状态不可逆 if (_self.status === "pending") { _self.status = "rejected" _self.reason = errMsg _self.rejectedCb(errMsg) } }) } try { executor(resolve, reject) } catch (err) { reject(err) }}MyPromise.prototype.then = function(fulfilledCb, rejectedCb) { const _self = this return new MyPromise((resolve, reject) => { _self.fulfilledCb = function (value) { resolve(fulfilledCb(value)) } _self.rejectedCb = function (reason) { reject(rejectedCb(reason)) } })}MyPromise.race = function (promiseArr) { return new MyPromise((resolve, reject) => { for (let i = 0, len = promiseArr.length; i < len; i++) { const p = promiseArr[i] p.then(resolve, reject) } })}MyPromise.all = function (promiseArr) { return new MyPromise((resolve, reject) => { const len = promiseArr.length const result = [] for (let i = 0; i < len; i++) { const p = promiseArr[i] p.then((res) => { result.push(res) if (result.length === len) { resolve(result) } }, (errMsg) => { reject(errMsg) }) } })}module.exports = MyPromise复制代码throttle
节流
原理:事件被频繁触发时,事件回调函数会按照固定频率执行,比如1s 执行一次,只有上个事件回调被执行之后下一个事件回调才会执行
事件回调函数
wait 事件回调的执行频率,每wait毫秒执行一次
// 节流// 原理:事件被频繁触发时,事件回调函数会按照固定频率执行,比如1s 执行一次,只有上个事件回调被执行之后下一个事件回调才会执行// 事件回调函数// wait 事件回调的执行频率,每wait毫秒执行一次function throttle(fn, wait = 500) { let timer = null return function(...args) { if (timer) return timer = setTimeout(() => { fn.apply(this, args) timer = null }, wait) }}复制代码
go!go!go!
相关阅读
-
世界热推荐:今晚7:00直播丨下一个突破...
今晚19:00,Cocos视频号直播马上点击【预约】啦↓↓↓在运营了三年... -
NFT周刊|Magic Eden宣布支持Polygon网...
Block-986在NFT这样的市场,每周都会有相当多项目起起伏伏。在过去... -
环球今亮点!头条观察 | DeFi的兴衰与...
在比特币得到机构关注之后,许多财务专家预测世界将因为加密货币的... -
重新审视合作,体育Crypto的可靠关系才能双赢
Block-987即使在体育Crypto领域,人们的目光仍然集中在FTX上。随着... -
简讯:前端单元测试,更进一步
前端测试@2022如果从2014年Jest的第一个版本发布开始计算,前端开发... -
焦点热讯:刘强东这波操作秀
近日,刘强东发布京东全员信,信中提到:自2023年1月1日起,逐步为...