写这篇主要是想回顾一下之前手写的一些JS方法,巩固下JS,也做个记录;有些方法在我之前博客也有单独写过,比如手写Promise、基于发布订阅模式的简单JS事件、深拷贝、Vue2响应式原理等等,在这里也算是总结一下吧

1. 深拷贝 

之前的博客:​​js深拷贝​​ 

function deepClone(source) {
if(typeof source !== 'object') return source
const target = source.constructor === Array ? [] : {}
for(let key in source) {
if(source.hasOwnProperty(key)) {
if(source[key] && typeof source[key] === 'object') {
target[key] = deepClone(source[key])
} else {
target[key] = source[key]
}
}
}
return target
}

2. 基于发布订阅的简单JS事件

之前博客:​​手写一个基于发布订阅模式的简单js事件​​ 

class EventBus {
constructor() {
this.arrays = {}
}
// 订阅
$on(type, fn) {
this.arrays[type] = this.arrays[type] || []
this.arrays[type].push(fn)
}
// 发布
$emit(type, ...args) {
if(!this.arrays[type]) return
this.arrays[type].forEach(callback => {
callback(...args)
})
}
// 解绑
$off(type, fn) {
const args = Array.from(arguments)
if(args.length === 0) {
this.arrays = {}
} else if(args.length === 1) {
if(!this.arrays[type]) return
this.arrays[type] = []
} else if(args.length === 2) {
if(!this.arrays[type]) return
for(let i = this.arrays[type].length - 1; i >= 0; i--) {
if(this.arrays[type][i] === fn) {
this.arrays[type].splice(i, 1)
}
}
}
}
}

3. 手写Promise 

之前博客:​​手写Promise​​ 

class MyPromise {
static PENDING = 'pending'
static FULFILLED = 'fulfilled'
static REJECTED = 'rejected'
constructor(fn) {
this.status = MyPromise.PENDING
this.result = null
this.resolveCallbacks = []
this.rejectCallbacks = []
try {
fn(this.resolve.bind(this), this.reject.bind(this))
} catch(e) {
this.reject(e)
}
}

resolve(data) {
setTimeout(() => {
if(this.status === MyPromise.PENDING) {
this.status = MyPromise.FULFILLED
this.result = data
if(this.resolveCallbacks.length) {
this.resolveCallbacks.forEach(callback => {
callback(data)
})
}
}
})
}

reject(err) {
setTimeout(() => {
if(this.status === MyPromise.PENDING) {
this.status = MyPromise.REJECTED
this.result = err
if(this.rejectCallbacks.length) {
this.rejectCallbacks.forEach(callback => {
callback(err)
})
}
}
})
}

then(ONFULFILLED, ONREJECTED) {
return new MyPromise((resolve, reject) => {
ONFULFILLED = typeof ONFULFILLED === 'function' ? ONFULFILLED : () => {}
ONREJECTED = typeof ONREJECTED === 'function' ? ONREJECTED : () => {}
if(this.status === MyPromise.PENDING) {
this.resolveCallbacks.push(ONFULFILLED)
this.rejectCallbacks.push(ONREJECTED)
}
if(this.status === MyPromise.FULFILLED) {
setTimeout(() => {
ONFULFILLED(this.result)
})
}
if(this.status === MyPromise.REJECTED) {
setTimeout(() => {
ONREJECTED(this.result)
})
}
})
}
}

4. Vue2的响应式核心 

之前博客: ​​Vue2响应式原理​​ 

const data = {
name: 'wft',
info: {
age: 12
},
list: [1,2,3]
}

// 重写数组方法
const oldArrProto = Array.prototype
const newArrProto = Object.create(oldArrProto)
const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse']
methods.forEach(method => {
newArrProto[method] = function() {
oldArrProto[method].call(this, ...arguments)
console.log('更新视图操作----->>>')
}
})

function observer(target) {
if(typeof target !== 'object' || target === null) {
return target
}

if(target.constructor === Array) {
target.__proto__ = newArrProto
}

for(let key in target) {
if(target.hasOwnProperty(key)) {
defineReactive(target, key, target[key])
}
}
}

function defineReactive(target, key, value) {
if(typeof value === 'object') {
observer(value)
}
Object.defineProperty(target, key, {
get() {
return value
},
set(newVal) {
if(typeof newVal === 'object') {
observer(newVal)
}
if(newVal !== value) {
value = newVal
console.log('更新视图操作----->>>')
}
}
})
}

5. 树形结构,根据ID找出它所属的父级集合 

之前博客: ​​js树形结构,根据里层id找出它所属的每层父级集合​​ 

class FindArrsById {
constructor(id, tree) {
this.id = id
this.flatArr = this.flatTreeAndSetLevel.call(this, tree)
this.parentAreas = this.getParentAreas.call(this, this.id, this.flatArr)
this.getParentValuesByKey.bind(this)
}

flatTreeAndSetLevel(tree, level = 0) {
const list = []
tree.forEach(item => {
const o = JSON.parse(JSON.stringify(item))
if(o.children) delete o.children
o.level = level
list.push(o)
if(item.children && item.children.length) {
list.push(...this.flatTreeAndSetLevel(item.children, level + 1))
}
})
return list
}

getParentAreas(pid, list) {
const target = []
let o = list.find(item => item.id == pid) || {}
if(JSON.stringify(o) != '{}') target.push(o)
if(o.parentId) target.push(...this.getParentAreas(o.parentId, list))
return target
}

getParentValuesByKey(key) {
return this.parentAreas.map(item => item[key]).reverse()
}
}

6. 千分位数值转换 

function numberToCurrencyNo(value) {
if (!value) return '0'
if (value === '--') return '--'
value = value - 0
// 将数值截取,保留0位小数
value = value.toFixed(0)
// 获取整数部分
const intPart = Math.trunc(value)
// 整数部分处理,增加,
const intPartFormat = intPart.toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
// 预定义小数部分
let floatPart = ''
// 将数值截取为小数部分和整数部分
const valueArray = value.toString().split('.')
if (valueArray.length === 2) { // 有小数部分
floatPart = valueArray[1].toString() // 取得小数部分
return intPartFormat + '.' + floatPart
}
return intPartFormat + floatPart
}

console.log(numberToCurrencyNo(23423456)) //23,423,456

7. 数组去重 

之前博客:​​js数组去重​​ 

export function noRepeatArr(list) {
if (list.every(item => typeof item !== 'object')) {
return [...new Set(list)]
}
return [...new Set(list.map(item => JSON.stringify(item)))].map(item => JSON.parse(item))
}

8. 手写一个字符串的trim()方法 

function myTrim(str) {
if(typeof str !== 'string') return ''
if(str.split('').every(str => str === ' ')) return ''
let start = 0
let end = str.length - 1
for(let i = 0; i < str.length; i++) {
if(str[i] !== ' ') {
start = i
break
}
}
for(let i = str.length - 1; i >= 0; i--) {
if(str[i] !== ' ') {
end = i
break
}
}
return str.slice(start, end + 1)
}