2023过年前熟悉一下常见手写代码
本文主要总结了前端需要掌握的手写代码。分为 Arry 篇
、Object + Function 篇
、ES6 Set、Map、class 篇
、Promise 篇
、常用函数
共 5 部分内容。
[toc]
1. Array 篇
Array 篇比较简单,基础好的可以略过。
1.1 数组去重
1.1.1 使用 ES6 Set
js
function unique(arr) {
// return Array.from(new Set(arr))
return [...new Set(arr)]
}
1.1.2 使用 ES6 Map
js
function unique(arr) {
const map = new Map()
const res = []
arr.forEach(item => {
if (!map.has(item)) {
map.set(item, true)
res.push(item)
}
})
return res
}
// 使用 reduce
function unique(arr) {
const map = new Map()
return arr.reduce((acc, item) => {
if (!map.has(item)){
map.set(item, true)
acc.push(item)
}
return acc
}, [])
}
本小节属于"新数组 + 方法系列",可以把 map.has 改为 includes/indexOf 等,一样可以实现。
1.1.3 使用 reduce + (indexOf 或 includes)
js
// reduce + includes 为例
function unique(arr) {
return arr.reduce((acc, item) => {
if (!acc.includes(item)) {
acc.push(item)
}
return acc
}, [])
}
1.1.4 使用 filter + indexOf
js
// reduce + Map 为例
function unique(arr) {
return arr.filter(function (item, index, arr) {
return arr.indexOf(item) === index
})
}
1.2 实现 Callback Methods 系列
该系列属于数组的回调函数系列,它们的参数都是固定的,有 2 类
- f(cb(ele, idx, arr), thisArg)
- f(cb(acc, ele, idx, arr), initData)
第一类有every , some , map , filter , find , findIndex , forEach;
第二类有reduce 和 reduceRight
下面实现最具有代表性的 3 个方法:forEach 、 filter 、 reduce
1.2.1 实现 forEach
js
Array.prototype._forEach = function (cb, thisArg) {
if (typeof cb !== 'function') {
throw new TypeError(cb + ' is not a function')
}
for (var i = 0; i < this.length; i++) {
cb.call(thisArg || window, this[i], i, this)
}
}
1.2.2 实现 filter
js
Array.prototype._filter = function (cb, thisArg) {
if (typeof cb !== 'function') {
throw new TypeError(cb + ' is not a function')
}
const res = []
for (var i = 0; i < this.length; i++) {
// 回调函数执行为true
if (cb.call(thisArg || window, this[i], i, this)) {
res.push(this[i])
}
}
return res
}
1.2.3 实现 reduce
js
Array.prototype._reduce = function (cb, initData) {
if (this.length === 0) {
throw new Error('Reduce of empty array with no initial value')
}
let i = 0
let acc
// 判断是否传入初始值
if (initData === undefined) {
// 没有初始值的空数组调用reduce会报错
// 初始值赋值为数组第一个元素
acc = this[0]
i++
} else {
acc = initData
}
for (; i < this.length; i++) {
// 计算结果赋值给初始值
acc = cb(acc, this[i], i, this)
}
return acc
}
1.3 实现 基本 Methods 系列
这一类主要有这些方法:isArray 、 includes 、 indexOf 、 join 、 slice 、 flat。
这里仅实现 isArray 、 indexOf 、join 、 flat 作为例子。
1.3.1 实现 isArray
js
Array._isArray = function (obj) {
return Object.prototype.toString.call(obj).slice(8, -1) === 'Array'
}
1.3.2 实现 indexOf
js
Array.prototype._indexOf = function (val, beginIndex = 0) {
if (this.length < 1 || beginIndex > this.length) {
return -1
}
beginIndex = beginIndex <= 0 ? 0 : beginIndex
for (let i = beginIndex; i < this.length; i++) {
if (this[i] === val) return i
}
return -1
}
1.3.3 实现 join
js
Array.prototype._join = function (sep = ',') {
if (!this.length) {
return ''
}
let str = this[0].toString()
for (let i = 1; i < this.length; i++) {
str += `${sep}${this[i].toString()}`
}
return str
}
1.3.4 实现 flat
js
Array.prototype._flat = function (depth = 1) {
const res = []
for (let i = 0; i < this.length; i++) {
const item = this[i]
if (Array.isArray(item) && depth > 0) {
depth -= 1
res = res.concat(item._flat(depth))
} else {
res = res.concat(item)
}
}
return res
}
2. Object + Function 篇
2.1 实现 keys、 values、 entries
以entries为例,代码如下:
js
Object.prototype._entries = function (obj) {
const res = []
const keys = Reflect.ownKeys(obj)
for (let i = 0; i < keys.length; i++) {
res.push([keys[i], obj[keys[i]]])
}
return res
}
2.2 实现 Object.is
js
// 用处:Object.is(a, b),判断a是否等于b
// NaN相等,+0,-0不相等,如下:
// Object.is(NaN, NaN) ==> true
// Object.is(0,-0) ==> false
// 原本的却是:
// NaN === NaN ==> false
// 0 === -0 ==> true
// 1/0 === 1/-0 ==> false
Object._is = function (x, y) {
if (x === y) {
// 防止 -0 和 +0
return x !== 0 || 1 / x === 1 / y
}
// 防止NaN
return x !== x && y !== y
}
2.3 实现 Object.assign
js
// assign接收多个对象,并将多个对象合成一个对象
// 这些对象如果有重名属性,以后来的对象属性值为准
// assign返回一个对象,这个对象 === 第一个对象
Object._assign = function (target, ...args) {
if (target === null || target === undefined) {
throw new TypeError('Cannot convert undefined or null to object')
}
target = Object(target)
for (let nextObj of args) {
for (let key in nextObj) {
if (nextObj.hasOwnProperty(key)) {
target[key] = nextObj[key]
}
}
}
return target
}
2.4 实现 instanceof
js
// 用处:A instanceOf B,检测一个对象A是不是另一个对象B的实例的原理
// 查看对象B的prototype属性指向的原型对象是否在对象A的原型链上,若在则返回true,若不在则返回false。
const _instanceOf = function (obj, Fn) {
if (typeof Fn !== 'function') {
return false
}
if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
// 非object或者function,统统返回false
return false
}
let proto = Object.getPrototypeOf(obj)
while (true) {
if (proto === null) {
return false
}
// 只有构造函数才有 prototype 属性
if (proto === Fn.prototype) {
return true
}
proto = Object.getPrototypeOf(proto)
}
}
2.5 实现 call、 apply
call,apply 的实现类似,以 apply 为例:
js
Function.prototype._apply = function (context = window, args) {
const fn = Symbol() // Symbol是唯一的,防止重名key
context[fn] = this
const result = context[fn](...args)
delete context[fn] // 用完要删除
return result
}
2.6 实现 new
js
const _new = function (Fn, ...args) {
if (typeof Fn !== 'function') {
throw new TypeError('_new function TypeError: the first param must be a function')
}
// const obj = {}
// Object.setPrototypeOf(obj, Fn.prototype)
// 上面2句可以由下面一句代替
const obj = Object.create(Fn.prototype)
const res = Fn.apply(obj, args)
return typeof res === 'object' ? res : obj
}
2.7 实现 bind
js
Function.prototype._bind = function (context = window, ...args) {
if (typeof this !== 'function') {
throw new Error('Function.prototype.bind - what is trying to be bound is not callable')
}
const self = this
const fbound = function (...innerArgs) {
self.apply(this instanceof self ? this : context, args.concat(innerArgs))
}
// 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承函数的原型中的值
fbound.prototype = this.prototype
return fbound
}
3. ES6 Set
、Map
篇
在实现之前,要先了解 Object 常规对象的键的特性:
- 基本类型,统统转化为字符串
- 对象类型,统统 toString()处理
如下所示:
js
const o = {}
o[1] = 1
console.log(o) // { '1': 1 }
o['1'] = 100
console.log(o) // { '1': 100 }
o[true] = 2
console.log(o) // { '1': 100, true: 2 }
o['true'] = 200
console.log(o) // { '1': 100, true: 200 }
o[{}] = 3
console.log(o) // { '1': 100, true: 200, '[object Object]': 3 }
o[{ name: 'flygo' }] = 300
console.log(o) // { '1': 100, true: 200, '[object Object]': 300 }
o[[]] = 4
console.log(o) // { '1': 100, true: 200, '[object Object]': 300, '': 4 }
o[[1]] = 4
console.log(o) // { '1': 100, true: 200, '[object Object]': 300, '': 4 }
o[[1, 2, 3]] = 4
console.log(o) // { '1': 4, true: 200, '[object Object]': 300, '': 4, '1,2,3': 4 }
o[function () {}] = 5
console.log(o) // { '1': 4, true: 200, '[object Object]': 300, '': 4, '1,2,3': 4, 'function () {}': 5 }
o[
function () {
return 'flygo'
}
] = 500
console.log(o) // =>
/*
{
'1': 4,
true: 200,
'[object Object]': 300,
'': 4,
'1,2,3': 4,
'function () {}': 5,
"function () {\r\n return 'flygo'\r\n }": 5
}
*/
console.log({}.toString()) // [object Object]
console.log({ name: 'flygo' }.toString()) // [object Object]
console.log([].toString()) //
console.log([1].toString()) // 1
console.log([1, 2, 3].toString()) // 1,2,3
console.log(function () {}.toString()) // function () {}
就是说:
- 1)数字
1
与字符串'1'
是不做区分的,布尔值true
与字符串'true'
也是不做区分的。(其他基础类型比较少用,不做研究,感兴趣的可以自己试验一下)- 2)引用类型,统统是经过
toString
处理后的值作为key
。这里要特别注意,一般的Object
执行toString
之后,得到的都是[object Object]
。
3.1 实现 ES6 Set
js
class Set {
//Symbol.iterator 为每个对象定义了默认的迭代器。
//该迭代器可以被for... of循环使用
constructor(iterator = []) {
//传递的对象必须是一个可迭代对象
//所以需要判断传递的参数是否是可迭代对象
if (typeof iterator[Symbol.iterator] !== 'function') {
//不是可迭代对象就抛出一个错误
throw new TypeError(`您所提供的 ${iterator}不是一个可迭代对象`)
}
//创建一个空数组
this._datas = []
//取出数组iterator里面的值,用for of循环
for (const item of iterator) {
// 将值添加到空数组中
this.add(item)
}
}
//判断两个值是否相等
isEqual(data1, data2) {
//1.存在两个都为0的情况
if (data1 === 0 && data2 === 0) {
return true
}
//2.Object.is()方法判断两个值是否为同一个值
return Object.is(data1, data2)
}
//判断数据是否存在数组中
has(data) {
//遍历数组中的值(用for of)
for (const item of this._datas) {
//调用isEqual()方法判断 data(输入的数据)跟item(数组中的数据)
if (this.isEqual(data, item)) {
//相同返回true
return true
}
//不相同返回false
return false
}
}
//添加数据的方法
add(data) {
//首先判断添加的值是否存在在当前数组中,存在的话就默认返回undefined,
//不存在就把数据添加到之前定义的空数组中,
// 此时已经不是空数组,而是存入了item值
if (!this.has(data)) {
//不存在就添加到数组中
this._datas.push(data)
}
return this._datas
}
// 删除数据,返回结果true/false,删除成功/删除失败
delete(data) {
//遍历数组中的数据,i为下标,element为每个数据
for (let i = 0; i < this._datas.length; i++) {
const element = this._datas[i]
//判断data跟element是否相同,相同说明数组中存在数据,可以删除
if (this.isEqual(data, element)) {
//删除数据利用splice()方法
this._datas.splice(i, 1)
//删除成功
return true
}
}
//删除失败
return false
}
//清除数据
clear() {
//数组长度为0
this._datas.length = 0
return this._datas
}
//获取数组长度
get size() {
return this._datas.length
}
//forEach方法(里层用for of)
forEach(callback) {
for (const item of this._datas) {
callback(item, item, this)
}
}
values() {
return this._datas
}
entries() {
return this._datas.map(item => [item, item])
}
//*[Sysbol.iterator]
*[Symbol.iterator]() {
for (const item of this._datas) {
yield item
}
}
}
const s = new Set([1, 1, '1'])
console.log([...s]) // [ 1, '1' ]
console.log(s.size) // 2
s.clear() // 清空重新来
console.log(s.size) // 0
s.add(1)
console.log(s.size) // 1
s.add(1) // 检测重复
console.log(s.size) // 1
s.add('1') // 检测 数字1 与 字符串 '1'
console.log(s.size) // 2
console.log(s.values()) // [ 1, '1' ]
s.add(2)
console.log(s.size) // 3
console.log(s.values()) //[ 1, '1', 2 ]
console.log(s.entries()) // [ [ 1, 1 ], [ '1', '1' ], [ 2, 2 ] ]
console.log([...s]) // [ 1, '1', 2 ]
s.delete(1)
console.log(s.size) // 2
s.clear()
console.log(s.size) // 0
3.2 实现 ES6 Map
js
class Map {
//Symbol.iterator 为每个对象定义了默认的迭代器。
//该迭代器可以被for... of循环使用
constructor(iterator = []) {
//传递的对象必须是一个可迭代对象
//所以需要判断传递的参数是否是可迭代对象
if (typeof iterator[Symbol.iterator] !== 'function') {
//不是可迭代对象就抛出一个错误
throw new TypeError(`您所提供的 ${iterator}不是一个可迭代对象`)
}
//创建一个空数组
this._datas = []
//取出数组iterator里面的值,用for of循环
for (const item of iterator) {
const [k, v] = item
// 将值添加到空数组中
this.set(k, v)
}
}
//判断两个值是否相等
isEqual(data1, data2) {
//1.存在两个都为0的情况
if (data1 === 0 && data2 === 0) {
return true
}
//2.Object.is()方法判断两个值是否为同一个值
return Object.is(data1, data2)
}
//判断数据是否存在数组中
has(key) {
//遍历数组中的值(用for of)
for (const [k, _] of this._datas) {
//调用isEqual()方法判断 data(输入的数据)跟item(数组中的数据)
if (this.isEqual(key, k)) {
//相同返回true
return true
}
//不相同返回false
return false
}
}
//添加数据的方法
set(key, val) {
//首先判断添加的值是否存在在当前数组中,存在的话就默认返回undefined,
//不存在就把数据添加到之前定义的空数组中,
// 此时已经不是空数组,而是存入了item值
if (!this.has(key)) {
//不存在就添加到数组中
this._datas.push([key, val])
} else {
const item = this._datas.find(([k, _]) => k === key)
item[1] = val
}
return this._datas
}
//添加数据的方法
get(key) {
//首先判断添加的值是否存在在当前数组中,存在的话就默认返回undefined,
//不存在就把数据添加到之前定义的空数组中,
// 此时已经不是空数组,而是存入了item值
if (!this.has(key)) {
//不存在就添加到数组中
return undefined
}
const item = this._datas.find(([k, _]) => k === key)
return item[1]
}
// 删除数据,返回结果true/false,删除成功/删除失败
delete(key) {
if (!this.has(key)) {
//不存在返回false
return false
}
const idx = this._datas.findIndex(([k, _]) => k === key)
//删除数据利用splice()方法
this._datas.splice(idx, 1)
//删除成功,返回true
return true
}
//清除数据
clear() {
//数组长度为0
this._datas.length = 0
return this._datas
}
//获取数组长度
get size() {
return this._datas.length
}
//forEach方法(里层用for of)
forEach(callback) {
for (const [k, v] of this._datas) {
callback(v, k, this)
}
}
keys() {
return this._datas.reduce((acc, cur) => {
acc.push(cur[0])
return acc
}, [])
}
values() {
return this._datas.reduce((acc, cur) => {
acc.push(cur[1])
return acc
}, [])
}
entries() {
return this._datas.reduce((acc, cur) => {
acc.push([cur[0], cur[1]])
return acc
}, [])
}
//*[Sysbol.iterator]
*[Symbol.iterator]() {
for (const item of this._datas) {
yield item
}
}
}
const m = new Map([[1], [2, 3]])
console.log([...m]) // [ [ 1, undefined ], [ 2, 3 ] ]
console.log(m.keys()) // [ 1, 2 ]
console.log(m.values()) // [ undefined, 3 ]
console.log(m.entries()) // [ [ 1, undefined ], [ 2, 3 ] ]
console.log(m.size) // [ [ 1, undefined ], [ 2, 3 ] ]
m.clear()
m.set(1, 2)
console.log(m.entries()) // [ [ 1, 2 ] ]
m.set(1, 3)
console.log(m.entries()) // [ [ 1, 3 ] ]
m.delete(1)
console.log(m.entries()) // []
4. Promise 篇
4.1 实现 Promise
直接看一位大佬写的文章,一步一步带你实现符合 Promise A+
规范的 Promise
!
ITEM 大佬: 从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节 juejin.cn/post/694531...
js
// MyPromise.js
// 先定义三个常量表示状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// 新建 MyPromise 类
class MyPromise {
constructor(executor) {
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
// 储存状态的变量,初始值是 pending
status = PENDING
// 成功之后的值
value = null
// 失败之后的原因
reason = null
// 存储成功回调函数
onFulfilledCallbacks = []
// 存储失败回调函数
onRejectedCallbacks = []
// 更改成功后的状态
resolve = value => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED
// 保存成功之后的值
this.value = value
// resolve里面将所有成功的回调拿出来执行
while (this.onFulfilledCallbacks.length) {
// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
this.onFulfilledCallbacks.shift()(value)
}
}
}
// 更改失败后的状态
reject = reason => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED
// 保存失败后的原因
this.reason = reason
// resolve里面将所有失败的回调拿出来执行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
}
then(onFulfilled, onRejected) {
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
const realOnRejected =
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason
}
// 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 获取成功回调函数的执行结果
const x = realOnFulfilled(this.value)
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}
const rejectedMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 调用失败回调,并且把原因返回
const x = realOnRejected(this.reason)
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}
// 判断状态
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
// 等待
// 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
// 等到执行成功失败函数的时候再传递
this.onFulfilledCallbacks.push(fulfilledMicrotask)
this.onRejectedCallbacks.push(rejectedMicrotask)
}
})
return promise2
}
// resolve 静态方法
static resolve(parameter) {
// 如果传入 MyPromise 就直接返回
if (parameter instanceof MyPromise) {
return parameter
}
// 转成常规方式
return new MyPromise(resolve => {
resolve(parameter)
})
}
// reject 静态方法
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 如果相等了,说明return的是自己,抛出类型错误并返回
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// 判断x是不是 MyPromise 实例对象
if (x instanceof MyPromise) {
// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
// x.then(value => resolve(value), reason => reject(reason))
// 简化之后
x.then(resolve, reject)
} else {
// 普通值
resolve(x)
}
}
// 作者:ITEM
// 链接:https://juejin.cn/post/6945319439772434469
// 来源:稀土掘金
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
4.2 实现 Promise.all
js
Promise._all = function PromiseAll(promises){
return new Promise((resolve, reject)=>{
if(!Array.isArray(promises)){
throw new TypeError("promises must be an array")
}
const result = []
let count = 0
promises.forEach((promise, index) => {
promise.then((res)=>{
result[index] = res
count++
count === promises.length && resolve(result)
}, (err)=>{
reject(err)
})
})
})
}
4.3 实现 Promise.allSettled
js
Promise._allSettled = function allSettled(promises) {
if (promises.length === 0) return Promise.resolve([])
const _promises = promises.map(item => (item instanceof Promise ? item : Promise.resolve(item)))
return new Promise((resolve, reject) => {
const result = []
let unSettledPromiseCount = _promises.length
_promises.forEach((promise, index) => {
promise.then(
value => {
result[index] = {
status: 'fulfilled',
value,
}
unSettledPromiseCount -= 1
// resolve after all are settled
if (unSettledPromiseCount === 0) {
resolve(result)
}
},
reason => {
result[index] = {
status: 'rejected',
reason,
}
unSettledPromiseCount -= 1
// resolve after all are settled
if (unSettledPromiseCount === 0) {
resolve(result)
}
},
)
})
})
}
4.4 实现 Promise.race
js
Promise._race = function promiseRace(promiseArr) {
return new Promise((resolve, reject) => {
promiseArr.forEach(p => {
Promise.resolve(p).then(
val => {
resolve(val)
},
err => {
reject(err)
},
)
})
})
}
4.5 实现 Promise.any
js
Promise._any = function promiseAny(promiseArr) {
let index = 0
return new Promise((resolve, reject) => {
if (promiseArr.length === 0) return
promiseArr.forEach((p, i) => {
Promise.resolve(p).then(
val => {
resolve(val)
},
err => {
index++
if (index === promiseArr.length) {
reject(new Error('All promises were rejected'))
}
},
)
})
})
}
5. 常用函数
5.1 Promise
版 Ajax
js
const ajax = ({ url = null, method = 'GET', async = true }) => {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open(method, url, async)
xhr.onreadystatechange = () => {
if (xhr.status < 200 || xhr.status >= 400) return
if (xhr.readyState === 4) {
let result = xhr.responseText
resolve(result)
}
}
xhr.onerror = err => {
reject(err)
}
xhr.send()
})
}
5.2 Promise
版 jsonp
js
const jsonp = ({ url, params, callbackName }) => {
const generateUrl = () => {
let dataSrc = ''
for (let key in params) {
if (params.hasOwnProperty(key)) {
dataSrc += `${key}=${params[key]}&`
}
}
dataSrc += `callback=${callbackName}`
return `${url}?${dataSrc}`
}
return new Promise((resolve, reject) => {
const scriptEle = document.createElement('script')
scriptEle.src = generateUrl()
document.body.appendChild(scriptEle)
window[callbackName] = data => {
resolve(data)
document.removeChild(scriptEle)
}
})
}
5.3 Promise
版 sleep
js
const sleep = ms => {
return new Promise(resolve => {
const timer = setTimeout(() => {
resolve()
clearTimeout(timer)
}, ms)
})
}
5.4 函数柯里化
js
const curry = fn => {
const len = fn.length
const judge = (...args1) => (args1.length >= len ? fn(...args1) : (...args2) => judge(...args1, ...args2))
return judge
}
// 另一种
const curry = fn => {
const len = fn.length //获取原函数的参数数量
return function result(...args) {
if (args.length < len) {
//使用bind的用处是将此次的参数带到下一次result的参数里面,且不执行该函数
return result.bind(null, ...args)
} else {
return fn(...args)
}
}
}
5.5 深拷贝
js
const getType = obj => Object.prototype.toString.call(obj)
const isObject = target => (typeof target === 'object' || typeof target === 'function') && target !== null
const canTraverse = {
'[object Map]': true,
'[object Set]': true,
'[object Array]': true,
'[object Object]': true,
'[object Arguments]': true,
}
const mapTag = '[object Map]'
const setTag = '[object Set]'
const boolTag = '[object Boolean]'
const numberTag = '[object Number]'
const stringTag = '[object String]'
const symbolTag = '[object Symbol]'
const dateTag = '[object Date]'
const errorTag = '[object Error]'
const regexpTag = '[object RegExp]'
const funcTag = '[object Function]'
const handleRegExp = target => {
const { source, flags } = target
return new target.constructor(source, flags)
}
const handleFunc = func => {
// 箭头函数直接返回自身
if (!func.prototype) return func
const bodyReg = /(?<={)(.|\n)+(?=})/m
const paramReg = /(?<=\().+(?=\)\s+{)/
const funcString = func.toString()
// 分别匹配 函数参数 和 函数体
const param = paramReg.exec(funcString)
const body = bodyReg.exec(funcString)
if (!body) return null
if (param) {
const paramArr = param[0].split(',')
return new Function(...paramArr, body[0])
} else {
return new Function(body[0])
}
}
const handleNotTraverse = (target, tag) => {
const Ctor = target.constructor
switch (tag) {
case boolTag:
return new Object(Boolean.prototype.valueOf.call(target))
case numberTag:
return new Object(Number.prototype.valueOf.call(target))
case stringTag:
return new Object(String.prototype.valueOf.call(target))
case symbolTag:
return new Object(Symbol.prototype.valueOf.call(target))
case errorTag:
case dateTag:
return new Ctor(target)
case regexpTag:
return handleRegExp(target)
case funcTag:
return handleFunc(target)
default:
return new Ctor(target)
}
}
const deepClone = (target, map = new WeakMap()) => {
if (!isObject(target)) return target
const type = getType(target)
let cloneTarget
if (!canTraverse[type]) {
// 处理不能遍历的对象
return handleNotTraverse(target, type)
} else {
// 这波操作相当关键,可以保证对象的原型不丢失!
const ctor = target.constructor
cloneTarget = new ctor()
}
if (map.get(target)) return target
map.set(target, true)
if (type === mapTag) {
//处理Map
target.forEach((item, key) => {
cloneTarget.set(deepClone(key, map), deepClone(item, map))
})
}
if (type === setTag) {
//处理Set
target.forEach(item => {
cloneTarget.add(deepClone(item, map))
})
}
// 处理数组和对象
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop], map)
}
}
return cloneTarget
}
本文由mdnice多平台发布