- 手写 Promise
- 手写 Reduce
- 手写 funPromiseInSequence(按顺序运行 promise)
- 数组去重
- 手写 pipe
- 手写 compose
- 考察 this
- 考察原型链
- 考察闭包
- 性能相关
- 手写观察者模式
- 手写 EventEmitrer 发布订阅模式
- 手写数组方法
- 实现一个精确的定时器
- 使用 setTimeout 模拟 setInterval
- 轮播图
- usePrevious
- useFetch
- 手写深拷贝
- 使用 class 或原型链实现计算器,要求能够链式调用
- 进制转换
- 向 URL 中插入 query 参数
- 实现 PascalCase 到 camelCase 之间的转换
- 实现一个可以重复调用指定回调的函数,传入参数为回调函数,执行次数,间隔时间
- parseURLParams
- URLSearchParams
- 手写 chunk
- 手写一个异步加法
- 原生手写一个 Modal
- 转换成树形结构
手写 Promise
function IPromise(executor) {this.status = 'pending' // 当前状态this.value = null // resolve后的值this.reason = null // reject后的值this.onFullfilledArray = [] // fullfilled状态数组,用于存放fullfilled状态的回调函数this.onRejectedArray = [] // rejected状态数组,用于存放rejected状态的回调函数const resolve = value => {if (value instanceof IPromise) {return value.then(resolve, reject)}// 模拟微任务setTimeout(() => {if (this.status === 'pending') {this.value = valuethis.status = 'fullfilled'// 遍历执行this.onFullfilledArray.forEach(func => {func(value)})}})}const reject = reason => {setTimeout(() => {if (this.status === 'pending') {this.reason = reasonthis.status = 'rejected'this.onRejectedArray.forEach(func => {func(reason)})}})}try {executor(resolve, reject)} catch (e) {reject(e)}}// 由于变量result既可以是普通值,也可以是promise实例,所以用resolvePromise统一管理const resolvePromise = (promise2, result, resolve, reject) => {// result 和 promise2 相等会出现死循环if (result === promise2) {reject(new TypeError('error due to circular reference'))}let consumed = false // 是否已经执行过let thenableif (result instanceof IPromise) {if (result.status === 'pending') {result.then(function (data) {resolvePromise(promise2, data, resolve, reject)}, reject)} else {result.then(resolve, reject)}return}let isComplexResult = target =>typeof target === 'function' ||(typeof target === 'object' && target !== null)// 如果返回的是疑似 Promise类型if (isComplexResult(result)) {try {thenable = result.then// 判断返回值是否是promise类型if (typeof thenable === 'function') {thenable.call(result,function (data) {if (consumed) {return}consumed = truereturn resolvePromise(promise2, data, resolve, reject)},function (error) {if (consumed) {return}consumed = truereject(error)})} else {resolve(result)}} catch (e) {if (consumed) returnconsumed = truereturn reject(e)}} else {resolve(result)}}IPromise.prototype.then = function (onFullfilled, onRejected) {// promise穿透onFullfilled =typeof onFullfilled === 'function' ? onFullfilled : data => dataonRejected =typeof onRejected === 'function'? onRejected: err => {throw error}let promise2 // 作为then方法的返回值if (this.status === 'fullfilled') {return (promise2 = new IPromise((resolve, reject) => {setTimeout(() => {try {let result = onFullfilled(this.value)resolvePromise(promise2, result, resolve, reject)} catch (e) {reject(e)}})}))}if (this.status === 'rejected') {return (promise2 = new IPromise((resolve, reject) => {setTimeout(() => {try {let result = onRejected(this.reason)resolvePromise(promise2, result, resolve, reject)} catch (e) {reject(e)}})}))}if (this.status === 'pending') {return (promise2 = new IPromise((resolve, reject) => {setTimeout(() => {this.onFullfilledArray.push(value => {try {let result = onFullfilled(value)resolvePromise(promise2, result, resolve, reject)} catch (e) {reject(e)}})this.onRejectedArray.push(reason => {try {let result = onRejected(reason)resolvePromise(promise2, result, resolve, reject)} catch (e) {reject(e)}})})}))}}const p = new IPromise((resolve, reject) => {setTimeout(() => {resolve('ha0ran')}, 2000)})p.then(null).then(data => console.log(data))
手写 Promise.all
该方法接收一个 promise 的 iterable 类型,并且只返回一个 Promise 实例,那个输入的所有 promise 的 resolve 回调的结果是一个数组。
这个 Promise 实例的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。
它的 reject 回调执行是在任意一个输入的 promise 的 reject 执行或者
如果传入的参数不包含任何 promise,则返回一个异步完成的 Promise
其他情况下返回一个处理中的 Promise。返回的数组的值会按照参数内的 promise 顺序排列。
/*** 输入为 iterable 的参数,可以是 Array、Map、Set、String,可能也得包括模块的Iterator* 若输入的可迭代数据里不是 Promise,则也需要原样输出* 返回一个 Promise实例,可以调用 then 和 catch 方法* 输出在then里面体现为保持原顺序的数组* 输出在 catch 里面体现为最早的reject返回值* 空 iterator,resolve 返回空数组*/function all(promises) {// your code hereif (promises == null ||(Array.isArray(promises) && promises.length === 0)) {return Promise.resolve([])}return new Promise((resolve, reject) => {let unSettledPromiseCount = promises.lengthconst result = Array(unSettledPromiseCount)promises.forEach((promise, index) => {Promise.resolve(promise).then(res => {// - result.push(res);// +result[index] = resunSettledPromiseCount--if (unSettledPromiseCount === 0) {resolve(result)}},err => {reject(err)})})})}
Promise.allSettled
字节实习一面
与 Promise.all 类似,会返回一个对象数组,但是该方法会返回所有的 promise 结果,不论 promise 的状态是 fullfilled 还是 rejected。
function allSettled(promises) {// 如果传入一个空对象,返回一个已经完成的Promiseif (promises.length === 0) {return Promise.resolve([])}return new Promise((resolve, reject) => {let unSettledPromiseCount = promises.lengthconst result = Array(unSettledPromiseCount)promises.forEach((promise, index) => {Promise.resolve(promise).then(value => {result[index] = {status: 'fulfilled',value,}unSettledPromiseCount--// 如果全部都 resolve 掉了,就返回结果if (unSettledPromiseCount === 0) {resolve(result)}},reason => {result[index] = {status: 'rejected',reason,}unSettledPromiseCount--if (unSettledPromiseCount === 0) {resolve(result)}})})})}
手写 Promise.race
function PromiseRace(args) {let iteratorIndex = 0return new Promise((resolve, reject) => {for (const item of args) {iteratorIndex++Promise.resolve(item).then(resolve, reject)}if (iteratorIndex === 0) {resolve([])}})}
Promise.any
Promise.then
Promise.resolve
Promise.reject
Promise.catch
Promise.finally
猿辅导二面
Promise.prototype.finally = function (cb) {this.then(val => {return Promise.resolve(cb()).then(() => val)}, err => {return Promise.resolve(cb()).then(() => {throw err})})}
手写 Reduce
Array.prototype.ireduce = function (func, initialValue) {let arr = thislet base = initialValue === 'undefined' ? arr[0] : initialValuelet startPoint = initialValue === 'undefined' ? 1 : 0arr.slice(startPoint).forEach((value, index) => {base = func(base, value, index + startPoint, arr) // pre, cur, index, arr})return base}
手写 funPromiseInSequence(按顺序运行 promise)
const runPromiseInSequence = (array, value) => {return array.reduce((promiseChain, currentFunc) => promiseChain.then(currentFunc),Promise.resolve(value))}
数组去重
// 可去重 NaN & 引用类型function unique(arr) {let newObj = {};let newArr = [];arr.forEach((item) => {if (typeof item !== 'object') {// NaN是唯一一个不等于任何自身的类型if (item !== item) {if (!newObj[item.toString()]) {newArr.push(item);newObj[item.toString()] = true;}} else {if (newArr.indexOf(item) === -1) {newArr.push(item);}}} else {let str = JSON.stringify(item);if (!newObj[str]) {newArr.push(item);newObj[str] = true;}}});return newArr;}
手写 pipe
const pipe =(...funcs) =>input =>funcs.reduce((pre, cur) => cur(pre), input)
手写 compose
function compose(...args) {if (args.length === 0) {return arg => arg}if (args.length === 1) {return args[0]}return args.reduce((a, b) =>(...arg) =>a(b(...arg)))}
写法四:迭代法
var compose = function (funcs) {var len = funcs.lengthvar index = lengthwhile (index--) {if (typeof funcs[index] !== 'function') {throw new TypeError('expected a function')}}return function (...args) {var index = 0var result = length ? funcs.reverse()[index].apply(this, args) : args[0]while (++index < length) {result = funcs[index].call(this, result)}return result}}
考察 this
手写 bind
Function.prototype.bind = function (context) {const me = thisconst args = Array.prototype.slice.call(arguments, 1)let F = function () {}F.prototype = this.prototypelet bound = function () {const innerArgs = Array.prototype.slice.call(arguments)const finalArgs = args.concat(innerArgs)return me.apply(this instanceof F ? this : context || this, finalArgs)}// 因为直接使用 bound.prototype = this.protoype 有个缺点// 修改 bound.prototype 的时候,也会修改 this.prototype// 这里就相当于使用了 Object.createbound.prototype = new F()return bound}
手写 apply
Function.prototype.apply = function (context = window, argsArray = []) {context = new Object(context)const targetFnKey = 'targetFnKey' + Date.now()context[targetFnKey] = thisconst result = context[targetFnKey](...argsArray)delete context[targetFnKey]return result}
手写 call
差不多同上
Function.prototype.call = function (context = window, ...args) {context = new Object(context)const targetKey = 'targetKey' + Date.now()context[targetKey] = thislet result = context[targetKey](...args)delete context[targetKey]return result}
手写 new
function newFun(...args) {// 获得构造函数const constructor = args.shift()// 创建一个空对象,且使这个空对象继承构造函数的prototype属性// obj.__proto__ = constructor.prototype;const obj = Object.create(constructor.prototype)// 执行构造函数const result = constructor.apply(obj, args)return typeof result === 'object' && result !== null ? result : obj}
链式调用
Number.prototype.add = function(num) {let n = +numreturn Number(this + n)}// caselet n = 1n.add(2).add(3) // -> 6
考察原型链
手写 instanceof
function myInstanceof(left, right) {let proto = Object.getPrototypeOf(left), // 获取对象的原型prototype = right.prototype // 获取构造函数的 prototype 对象// 判断构造函数的 prototype 对象是否在对象的原型链上while (true) {if (!proto) return falseif (proto === prototype) return trueproto = Object.getPrototypeOf(proto)}}
手写 Object.create
创建一个对象,使用现有对象作为新创建对象的原型(prototype),新对象继承了原对象
function create(proto) {let F = function () {}F.prototype = protoreturn new F()}
寄生式组合继承
function Person() {}function Child() {Person.call(this)}let prototype = Object.create(Person.prototype) // 创建对象,创建父类原型的一个副本prototype.constructor = Child // 增强对象,解决重写原型导致默认 constructor 丢失的问题Child.prototype = prototype // 赋值对象,将新创建的对象赋值给子类的原型
考察闭包
柯里化
function curry(fn, ...args) {if (args.length >= fn.length) {return fn.apply(null, args)}return (...args2) => curry(fn, ...args, ...args2)}
性能相关
手写 防抖
在一段时间结束之后才执行
应用场景:
- 搜索框:用户最后一次输入完成之后,才发送请求
- 手机号、邮箱等的验证
- 窗口大小的 resize
function debounce(func, wait, immediate) {let timeout = nullreturn function debounced () {const ctx = this, args = argumentsif (timeout) {clearTimeout(timeout)}if (immediate) {// 刚进来的时候callnow = !timeout = truelet callnow = !timeouttimeout = setTimeout(function () {timeout = null // 到时间后 timeout = null, callnow 就为 true,就可以执行了}, wait)if (callnow) {func.apply(context, args)}} else {timeout = setTimeout(function (){func.apply(context, args)}, wait)}}}
手写节流
在一段时间之内连续触发,只执行一次
第一次立即执行
在一段时间间隔内只执行一次
在时间的间隔的末尾也要执行一次(也就是最后一次的操作要触发)
应用:
- 搜索:联想搜索
- 滚动加载
- 高频点击,表单重复提交
// 立即执行var throttle = function (cb, wait, isImmediate = true) {let execTime = +new Date()let timeId = null// ! 为什么要返回一个函数return function () {const context = thisconst args = argumentsif (isImmediate) {cb.apply(context, args)execTime = +new Date()isImmediate = false} else {// * 达到wait时间const currentTime = +new Date()if (currentTime - execTime >= wait) {cb.apply(context, args)execTime = +new Date()} else {// 实现执行最后一次动作// ! 还剩多少时间执行const timeWait = wait - (+new Date() - execTime)timeId && clearTimeout(timeId)timeId = setTimeout(() => {cb.apply(context, args)execTime = +new Date()timer = null}, timeWait)}}}}
useDebounce
function useDebounce<T>(value: T, delay?: number): T {const [debouncedValue, setDebouncedValue] = useState<T>(value)useEffect(() => {const timer = setTimeout(() => {setDebouncedValue(value)}, delay || 500)return () => {clearTimeout(timer)}}, [value, delay])return debouncedValue}
useThrottle
function useThrottle <T>(value: T, ms: number = 200) {const [state, setState] = useState<T>(value)const timeout = useRef<ReturnType<typeof setTimeout>>()const nextValue = useRef(null) as anyconst hasNextValue = useRef(0) as anyuseEffect(() => {if (!timeout.current) {setState(value)const timeoutCallback = () => {if (hasNextValue.current) {hasNextValue.current = falsesetState(nextValue.current)timeout.current = setTimeout(timeoutCallback, ms)} else {timeout.current = undefined}}timeout.current = setTimeout(timeoutCallback, ms)} else {nextValue.current = valuehasNextValue.current = true}return () => {timeout.current && clearTimeout(timeout.current)}}, [value, ms])return state}
限制并发数
猿辅导二面
function sendRequest(urls, max, callback) {let limit = max,n = urls.length,res = [],cnt = 0const tasks = urls.map((url, index) => () =>fetch(url).then(data => {res[index] = data}).catch(err => {res[index] = err}).finally(() => {if (++cnt === n) {return callback(res)}limit++doTask()}))doTask()function doTask() {while (limit > 0 && tasks.length) {limit--const task = tasks.shift()task()}}}
手写带最大请求数的并发请求
猿辅导二面
function bingfa(requestArr, limitNum) {const n = requestArr.lengthconst maxNum = n >= limitNum ? limitNum : nreturn new Promise((resolve, reject) => {const res = Array(n)let runnedCount = 0const recursive = (requestArr, index) => {Promise.resolve(requestArr[index]()).then((val) => {// 第一次执行then的时机是微任务队列里最快的promise完成res[index] = val // 执行结果按顺序记录// 表示requestArr中的请求任务已经全部添加完毕// 并且此时微任务队列里的任务一定是小于等于maxNum// 没有后续添加的任务了,所以可以直接正常执行微任务队列里的任务// 它们会自行按照快慢执行if (runnedCount >= n) {return resolve(res)}// 有一个进到这里(执行了then),说明它执行完毕,需要再往任务队列里添加一个// 补齐manNum个recursive(requestArr, runnedCount++)})}// 经过下面的循环之后,微任务队列里会有maxNum个promise的回调// runnedCount的值等于maxNumfor (let i = 0; i < maxNum; i++) {recursive(requestArr, i)runnedCount++}})}
手写一个带并发限制的异步调度器 Scheduler
class Scheduler {constructor(limitCount) {this.promises = []this.limit = limitCountthis.runnedCount = 0}add(promiseCreator) {return new Promise((resolve, reject) => {promiseCreator.resolve = resolvepromiseCreator.reject = rejectthis.promises.push(promiseCreator)this.run()})}run() {if (this.runnedCount < this.limit && this.promises.length) {this.runnedCount++const promise = this.promises.shift()promise().then(res => {promise.resolve(res)}).catch(err => {promise.reject(err)}).finally(() => {this.runnedCount--this.run()})}}}const scheduler = new Scheduler(2)const timeout = (wait) => {return new Promise((resolve, reject) => {setTimeout(resolve, wait)})}const addTask = (time, content) => {scheduler.add(() => timeout(time)).then(() => console.log(content))}addTask(1000, '1')addTask(500, '2')addTask(300, '3')addTask(400, '4')
手写观察者模式
// 被观察者对象;class Subject {constructor() {this.observerList = []}addObserver(observer) {this.observerList.push(observer)}removeObserver(observer) {this.observerList = this.observerList.filter(o => o.name !== observer.name)}notifyObserver(message) {this.observerList.forEach(observer => observer.notified(message))}}// 观察者;class Observer {constructor(name, subject) {this.name = nameif (subject) {subject.addObserver(this)}}notified(message) {console.log(this.name, message)}}
手写 EventEmitrer 发布订阅模式
class EventEmitter {constructor() {this.listeners = Object.create(null)}/*** 注册事件 监听者* @param {string} eventName* @param {Function} listener*/on(eventName, listener) {if (!this.listeners[eventName]) {this.listeners[eventName] = []}this.listeners[eventName].push(listener)}// 取消订阅off(eventName, listener) {if (!this.hasBind(eventName)) {console.warn(`this event ${eventName} don't exist`)return}// remove all listenerif (!listener) {delete this.listeners[eventName]return}this.listeners[eventName] = this.listeners[eventName].filter(callback => callback !== listener)}// 只订阅一次once(eventName, listener) {function one() {listener.apply(this, arguments)this.off(eventName)}this.on(eventName, one) // 这样就可以用emit触发此函数}emit(eventName, ...args) {if (!this.hasBind(eventName)) {console.warn(`this event ${eventName} don't exist`)return}this.listeners[eventName].forEach(listener =>listener.call(this, ...args))}hasBind(eventName) {return this.listeners[eventName] && this.listeners[eventName].length}}const baseEvent = new EventEmitter()function cb(value) {console.log(`hello ${value}`)}baseEvent.on('click', cb)baseEvent.emit('click', 2022)baseEvent.emit('click', 2023)
手写数组方法
Splice
/*** 计算真实的start* @param {number} start 开始的下标* @param {number} len 数组的长度*/function computeSpliceStartIndex(start, len) {if (start < 0) {start += lenreturn start < 0 ? 0 : start}return start > len - 1 ? len - 1 : start}/*** 计算真实的 deleteCount* @param {number} startIndex 开始删除的下标* @param {number} deleteCount 要删除的个数* @param {number} len 数组长度*/function computeSpliceDeleteCount(startIndex, deleteCount, len) {if (deleteCount > len - startIndex) {deleteCount = len - startIndex}if (deleteCount < 0) deleteCount = 0return deleteCount}/*** 记录删除元素* @param {*} startIndex 开始删除的下标* @param {*} delCount 要删除的个数* @param {*} array 目标数组* @param {array} deletedElements 记录删除的元素*/function recordDeleteElements(startIndex, delCount, array, deletedElements) {for (let i = 0; i < delCount; i++) {deletedElements[i] = array[startIndex + i]}}/*** 移动元素,使可以插入新元素* @param {*} startIndex* @param {*} delCount* @param {*} addCount 要添加的元素个数* @param {*} array*/function moveElement(startIndex, delCount, addCount, array) {let over = addCount - delCount// 后移if (over > 0) {for (let i = array.length - 1; i >= startIndex + delCount; i--) {array[i + over] = array[i]}}// 左移else if (over < 0) {for (let i = startIndex + delCount + over; i <= array.length - 1; i++) {if (i + Math.abs(over) > array.length - 1) {delete array[i]continue}array[i] = array[i + Math.abs(over)]}}}Array.prototype._splice = function (start, deleteCount, ...rest) {let array = Object(this)let len = array.lengthlet addCount = rest.lengthlet startIndex = computeSpliceStartIndex(start, len)let delCount = computeSpliceDeleteCount(startIndex, deleteCount, len)let deletedElements = new Array(delCount)recordDeleteElements(startIndex, delCount, array, deletedElements)if (delCount !== addCount && Object.isSealed(array)) {throw new TypeError('the array is sealed')}if (delCount > 0 && addCount > 0 && Object.isFrozen(array)) {throw new TypeError('the array is frozen')}moveElement(startIndex, delCount, addCount, array)let i = startIndexlet argumentsIndex = 2while (rest.length > argumentsIndex) {array[i++] = rest[argumentsIndex++]}array.length = len - delCount + addCountreturn array}console.log([1, 2, 3, 4]._splice(1, 2))
Map
Array.prototype.myMap = function (callback, thisArg) {// 确保调用者是个数组if (Object.prototype.toString.call(this) !== '[object Array]') {throw new TypeError('调用者必须是数组')}// 确保回调函数是个函数if (typeof callback !== 'function') {throw new TypeError(callback + ' is not a function')}let res = []for (let i = 0; i < this.length; i++) {res[i] = callback.call(this[i], i, this, thisArg)}return res}let arr = [1, 2, 3]arr.map((ele, index) => {console.log(`${ele}, ${index}`)})
filter
Array.prototype.myFilter = function (callback, thisArg) {// 确保调用着是个数组if (Object.prototype.toString.call(this) !== '[object Array]') {throw new TypeError('调用者必须是数组');}// 确保回调函数是个函数if (typeof callback !== 'function') {throw new TypeError(callback + ' is not a function');}let res = [];for (let i = 0; i < this.length; i++) {// this[i]代表原数组当前元素// i 代表 下标// this 代表原数组if (callback.call(this[i], i this, thisArg)) {res.push(this[i]);}}return res;};
some
Array.prototype.isome = function (callback, thisArg) {if (typeof callback !== 'function') {throw new Error(callback + ' is not a function')}if (!Array.isArray(this)) {throw new Error('调用者必须是数组')}for (let i = 0; i < this.length; i++) {if (callback(this[i], i, this, thisArg)) {return true}}}
实现一个精确的定时器
var start = +new Date(),time = 0function instance() {time += 1000var diff = +new Date() - start - timeconsole.log(diff)window.setTimeout(instance, 1000 - diff)}window.setTimeout(instance, 1000)
使用 setTimeout 模拟 setInterval
let count = 0let timerId = nulltimerId = setTimeout(function run() {console.log('run -> ', count)if (count >= 3) {clearTimeout(timerId)return}count += 1timerId = setTimeout(run, 1000)}, 1000)
轮播图
- 首先用一个
div包起来,设定整个轮播图的位置,大小等 - 然后里面第一个
ul里面放图片,第二个ul里面放下面的小点使用绝对定位 - 然后就是左右两个箭头按钮,绝对定位
- js 代码部分,监听
window.onload方法- 获取整个盒子,获取图片所有的
li标签,获取小点所有的li,获取左右箭头 - 定义两个变量,一个是 index 记录当前位置,一个是 timerId 用来清除 定时器
- 定义左滑方法,index —,如果减到比 0 小,则 index 变为最后一个,并展示最后一张图片
- 右滑方法,index++,如果加到比图片个数还多,那么 index = 0,然后展示第一张图片
- 鼠标经过,移除定时器,鼠标移除,添加定时器
- 在外面添加定时器
- run 方法,用来图片自己向右滑 index++,和右滑方法类似
- 展示图片的方法,排他原则,先设置所有不展示,然后把特定 index 的设置为显示,并将 index 设为这个特定的 index
- 获取整个盒子,获取图片所有的
<style>* {list-style: none;}.wrap {width: 590px;height: 400px;margin: 150px auto;position: relative;cursor: pointer;}.pic li {display: none;position: absolute;top: 0;left: 0;z-index: 1;}img {width: 590px;height: 400px;}.num {position: absolute;z-index: 2;bottom: 20px;left: 50%;transform: translateX(-50%);}.num li {float: left;width: 8px;height: 8px;margin: 5px;border-radius: 50%;border: 1px solid #fff;line-height: 20px;background: transparent;text-align: center;}.num li.active {background: #fefefe;}.arrow {z-index: 3;height: 40px;width: 30px;position: absolute;top: 45%;line-height: 40px;background: rgba(0, 0, 0, 0.3);text-align: center;display: none;}.wrap:hover .arrow {display: block;}.arrow:hover {background: rgba(0, 0, 0, 0.7);}#left {left: 0;}#right {right: 0;}</style><div class="wrap"><ul class="pic"><li style="display: none;"><img src="./img/1.jpg" alt="" /></li><li style="display: none;"><img src="./img/2.jpg" alt="" /></li><li style="display: block;"><img src="./img/3.jpg" alt="" /></li><li style="display: none;"><img src="./img/4.jpg" alt="" /></li><li style="display: none;"><img src="./img/5.png" alt="" /></li><li style="display: none;"><img src="./img/6.png" alt="" /></li></ul><ul class="num"><li class="active"></li><li class=""></li><li class=""></li><li class=""></li><li class=""></li><li class=""></li></ul><div class="arrow" id="left"><</div><div class="arrow" id="right">></div></div><script>window.onload = function () {const wrap = document.querySelector('.wrap')const pic = document.querySelector('.pic').getElementsByTagName('li')const num = document.querySelector('.num').getElementsByTagName('li')const onLeft = document.querySelector('#left')const onRight = document.querySelector('#right')let index = 0let timerId = nullconst change = currentIndex => {//将所有都设置为不可见pic.forEach((item, index) => {item.style.display = 'none'num[index].className = ''})pic[currentIndex].style.display = 'block'num[currentIndex].className = 'active'index = currentIndex}onLeft.addEventListener('click', () => {index--if (index < 0) {index = pic.length - 1}change(index)})onRight.addEventListener('click', () => {index++if (index > pic.length - 1) {index = 0}change(index)})wrap.addEventListener('mouseover', () => {clearTimeout(timerId)})wrap.addEventListener('mouseout', () => {timerId = setTimeout(function fn() {run()timerId = setTimeout(fn, 2000)}, 2000)})timerId = setTimeout(function fn() {run()timerId = setTimeout(fn, 2000)}, 2000)const run = () => {index++if (index > pic.length - 1) {index = 0}change(index)}num.forEach((item, index) => {item.index = indexitem.addEventListener('mouseover', function () {change(this.index)})})}</script>
usePrevious
function usePrevious(state) {const ref = useRef()useEffect(() => {ref.current = state}, [state])return ref.current}
useRef 不仅仅用来管理 DOM ref,还可以存放任何变量。可以很好的解决闭包带来的不便性。当 useRef 的内容改变时,不会通知,更改 .current 属性不会导致重新呈现,因为它只是一个引用。
useFetch
function useFetch(url, options) {const cache = useRef({})const cancelRequest = useRef(false)const initialState = {data: undefined,error: undefined,}const fetchReducer = (state, action) => {switch (action.type) {case 'loading':return { ...initialState }case 'fetched':return { ...initialState, data: action.payload }case 'error':return { ...initialState, error: action.payload }default:return state}const [state, dispatch] = useReducer(fetchReducer, initialState)useEffect(() => {if (!url) {return}const fetchData = async () => {dispatch({ type: 'loading' })if (cache.current[url]) {dispatch({ type: 'fetched', payload: cache.current[url] })return}try {const response = await fetch(url, options)if (!response.ok) {throw new Error(response.statusText)}const data = await response.json()cache.current[url] = dataif (cancelRequest.current) returndispatch({ type: 'fetched', payload: data })} catch (error) {if (cancelRequest.current) returndispatch({ type: 'error', payload: error })}}void fetchData()return () => {cancelRequest.current = true}}, [url])}return state}
手写深拷贝
深拷贝和浅拷贝的区别
- 浅拷贝会创建一个新的对象,这个对象有着原有对象 属性值的一份拷贝。如果属性值是基础数据类型,拷贝的就是基础数据类型的值;如果是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。改变浅拷贝的第一层是不会影响原始对象的
- 深拷贝是将一个对象从内存中完整地拷贝一份出来,从堆内存中开辟一个新的区域存放对象,且修改新对象不会影响原始对象。
基础版
function deepClone(obj) {if (obj === null || typeof obj !== 'object') {return}let newObj = Array.isArray(obj) ? [] : {}for (let key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] =typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]}}return newObj}
解决循环引用问题
function deepClone(obj, map = new Map()) {if (obj === null || typeof obj !== 'object') {return}let newObj = Array.isArray(obj) ? [] : {}if (map.has(obj)) {return map.get(obj)}map.set(obj, newObj)for (let key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] =typeof obj[key] === 'object'? deepClone(obj[key], map): obj[key]}}return newObj}
使用 class 或原型链实现计算器,要求能够链式调用
class MyCalculator {constructor(value) {this.value = value}add(num) {this.value += numreturn this}minus(num) {this.value -= numreturn this}multi(num) {this.value *= numreturn this}div(num) {this.value /= numreturn this}pow(num) {this.value = this.value ** numreturn this}}
进制转换
function translate(num) {let remStack = []let digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'let remlet res = ''while (num > 0) {rem = Math.floor(num % 2)remStack.push(rem)rem = Math.floor(num / 2)}while (remStack.length) {res += digits[remStack.pop()]}return res}
向 URL 中插入 query 参数
function resolve(url) {const params = qs.stringify({ a: 1, b: 2 })const urlObj = new URL(url)urlObj.search += urlObj.search ? '&' + params : '?' + paramsreturn urlObj.href}
or
function buildUrl(url, params) {const urlObj = new URL(url)const paramsObj = qs.parse(urlObj.search.slice(1))Object.assign(paramsObj, params)urlObj.search = qs.stringify(paramsObj)return urlObj.href}
实现 PascalCase 到 camelCase 之间的转换
// 实现CamelCase转换为PascalCasefunction camelCase(str) {return str.replace(/^(\w)/, function (l) {return l.toUpperCase()})}// 实现PascalCase转换为CamelCasefunction camelCase(str) {return str.replace(/^(\w)/, function (l) {return l.toLowerCase()})}
实现一个可以重复调用指定回调的函数,传入参数为回调函数,执行次数,间隔时间
//实现一个可以重复调用指定回调的函数,传入参数为回调函数,执行次数,间隔时间function repeat(fn, times, interval) {let i = 0let timer = setInterval(function () {fn()i++if (i === times) {clearInterval(timer)}}, interval)}let fn = () => console.log('hello')repeat(fn, 3, 1000)
parseURLParams
function parseURLParams(url) {let queryParams = {};let reg = /([^?=&]+)=([^?=&]+)/g;url.replace(reg, function () {queryParams[arguments[1]] = arguments[2];});return queryParams;}// result{source: 'gtx',sl: 'auto',tl: 'en',text: '%E7%A9%BA%E6%A0%BC',op: 'translate'}
URLSearchParams
class MyURLSearchParams {/*** @params {string} init*/constructor(init) {this.val = ''if (init.indexOf('http') > -1) {this.val = init} else {this.val = init.slice(init.indexOf('?') + 1)}this.arr = this.val.split('&')// console.log(this.val);}/*** @params {string} name* @params {any} value*/append(name, value) {let tmp = name + '=' + valuethis.arr.push(tmp)return '?' + this.arr.join('&')}/*** @params {string} name*/delete(name) {let res = []for (let i = 0; i < this.arr.length; i++) {if (this.arr[i].indexOf(name) != 0) {res.push(this.arr[i])}}this.arr = resreturn '?' + this.arr.join('&')}/*** @returns {Iterator}*/*entries() {let res = []for (let i = 0; i < this.arr.length; i++) {let tmp = this.arr[i].split('=')yield [tmp[0], tmp[1]]}}/*** @param {(value, key) => void} callback*/forEach(callback) {for (let i = 0; i < this.arr.length; i++) {let tmp = this.arr[i].split('=')let obj = {}callback(tmp[1], tmp[0])}}/*** @param {string} name* returns the first value of the name*/get(name) {let res = nullthis.arr.filter(value => {if (res == null && value.indexOf(name) == 0) {let tmp = value.split('=')res = tmp && tmp[1]}})return res}/*** @param {string} name* @return {string[]}* returns the value list of the name*/getAll(name) {let res = []this.arr.filter(value => {if (value.indexOf(name) == 0) {let tmp = value.split('=')res.push(tmp[1])}})return res}/*** @params {string} name* @return {boolean}*/has(name) {let res = falsethis.arr.filter(value => {if (res == false && value.indexOf(name) == 0) {let tmp = value.split('=')res = tmp.length > 0 ? true : false}})return res}/*** @return {Iterator}*/keys() {let res = []for (let i = 0; i < this.arr.length; i++) {let tmp = this.arr[i].split('=')if (!res.includes(tmp[0])) {res.push(tmp[0])}}return res}/*** @param {string} name* @param {any} value*/set(name, value) {let tmp = 0for (let i = 0; i < this.arr.length; i++) {if (this.arr[i].indexOf(name) == 0) {tmp += 1this.arr[i] = name + '=' + value}}if (tmp == 0) {this.arr.push(name + '=' + value)}return '?' + this.arr.join('&')}// sor all key/value pairs based on the keyssort() {let res = []for (let i = 0; i < this.arr.length; i++) {let tmp = this.arr[i].split('=')res.push(tmp)}res.sort(function (a, b) {if (a[0] > b[0]) {return 1} else {return -1}})let sum = []res.forEach(value => {sum.push(value.join('='))})this.arr = sumreturn res}/*** @return {string}*/toString() {return this.arr.join('&')}/*** @return {Iterator} values*/values() {let res = []for (let i = 0; i < this.arr.length; i++) {let tmp = this.arr[i].split('=')if (!res.includes(tmp[0])) {res.push(tmp[1])}}return res}}
手写 chunk
function chunk(arr, size) {let temp = []let res = []arr.forEach((e, index) => {temp.push(e)if (temp.length === size || index === arr.length - 1) {res.push(temp)temp = []}})return res}
手写一个异步加法
function asyncAdd(a, b, callback) {setTimeout(function () {callback(null, a + b)}, 500)}// 解决方案// 1. promisifyconst promiseAdd = (a, b) =>new Promise((resolve, reject) => {asyncAdd(a, b, (err, res) => {if (err) {reject(err)} else {resolve(res)}})})// 2. 串行处理async function serialSum(...args) {return args.reduce((task, now) => task.then(res => promiseAdd(res, now)),Promise.resolve(0))}// 3. 并行处理async function parallelSum(...args) {if (args.length === 1) return args[0]const tasks = []for (let i = 0; i < args.length; i += 2) {tasks.push(promiseAdd(args[i], args[i + 1] || 0))}const results = await Promise.all(tasks)return parallelSum(...results)}// 测试;(async () => {console.log('Running...')const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)console.log(res1)const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)console.log(res2)console.log('Done')})()
原生手写一个 Modal
<style>#popLayer {display: none;background-color: #b3b3b3;position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 1;opacity: 0.6;}#popBox {display: none;background: #fff;width: 200px;height: 200px;position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);z-index: 10;}#popBox .close {text-align: right;margin-right: 5px;background-color: #f8f8f8;}#popBox .close a {text-decoration: none;color: #2d2c3b;}</style><input type="button" name="popBox" value="弹出框" onclick="openBox()" /><div id="popLayer"></div><div id="popBox"><div class="close"><a href="javascript:void(0)" onclick="closeBox()">关闭</a></div><div class="content">我是弹出层</div></div><script>var popBox = document.getElementById('popBox')var popLayer = document.getElementById('popLayer')function openBox() {popBox.style.display = 'block'popLayer.style.display = 'block'}/*点击关闭按钮*/function closeBox() {popBox.style.display = 'none'popLayer.style.display = 'none'}</script>
转换成树形结构
list2tree
function list2tree(list) {const map = new Map()const tree = []list.forEach((item, index) => {map.set(item.id, item)})list.forEach((item) => {if (item.pid) {const parent = map.get(item.pid)if (parent) {parent.children = parent.children || []parent.children.push(item)}} else {tree.push(item)}})return tree}
function list2tree(list) {const tree = []for (const node of list) {// 如果没有pid就可以认为是根节点if (!node.pid) {let p = { ...node }p.children = getChildren(p.id, list)tree.push(p)}}return tree}function getChildren(id, list) {const children = []for (const node of list) {if (node.pid === id) {children.push(node)}}// 注意这里遍历的是childrenfor (const node of children) {const children = getChildren(node.id, list)if (children.length) {node.children = children}}return children}
function list2tree(list) {list.forEach((child) => {const pid = child.pidif (pid) {list.forEach((parent) => {if (parent.id === pid) {parent.children = parent.children || []parent.children.push(child)}})}})return list.filter((n) => !n.pid)}
