自定义工具函数库(三)
最终仓库:utils: 自定义工具库
1. 自定义instanceof
- 语法: myInstanceOf(obj, Type)
- 功能: 判断obj是否是Type类型的实例
- 实现: Type的原型对象是否是obj的原型链上的某个对象, 如果是返回true, 否则返回false
之前的笔记:详解原型链
function myInstanceof(obj, fn) {
let prototype = fn.prototype
let proto = obj.__proto__
while (proto) {
if (prototype === proto) {
return true
}
proto = proto.__proto__
}
return false
}
测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>对象相关</title>
<script src="./myInstanceof.js"></script>
</head>
<body>
<script>
function Person() {}
let p = new Person()
console.log(myInstanceof(p, Person))
console.log(myInstanceof(p, Object))
console.log(myInstanceof(Person, Object))
console.log(myInstanceof(Person, Function))
console.log(myInstanceof(p, Function))
</script>
</body>
</html>
2. 对象/数组拷贝
2.1 浅拷贝与深拷贝
深拷贝和浅拷贝只针对Object和Array这样的引用数据类型。
- 浅拷贝:只复制某个对象的引用地址值,而不复制对象本身,新旧对象还是共享同一块内存(即修改旧对象引用类型也会修改到新对象)
- 深拷贝:新建一个一摸一样的对象,新对象与旧对象不共享内存,所以修改新对象不会跟着修改原对象。
2.2 浅拷贝
2.2.1 利用扩展运算符…实现
function shallowClone(target) {
if (typeof target === 'object' && target !== null) {
if (Array.isArray(target)) {
return [...target]
} else {
return { ...target }
}
} else {
return target
}
}
2.2.2 遍历实现
function shallowClone(target) {
if (typeof target === 'object' && target !== null) {
let ret = Array.isArray(target) ? [] : {}
for (let key in target) {
if (target.hasOwnProperty(key)) {
ret[key] = target[key]
}
}
return ret
} else {
return target
}
}
2.3 深拷贝
2.3.1 JSON转换
不能拷贝对象方法
function deepClone(target) {
let str = JSON.stringify(target)
return JSON.parse(str)
}
2.3.2 递归
function deepClone(target) {
if (typeof target === 'object' && target !== null) {
const ret = Array.isArray(target) ? [] : {}
for (const key in target) {
if (target.hasOwnProperty(key)) {
ret[key] = deepClone(target[key])
}
}
return ret
} else {
return target
}
}
测试:
const obj = { x: 'clz', y: { age: 21 }, z: { name: 'clz' }, f: function () { } }
const cloneObj = deepClone(obj)
obj.y.age = 111
console.log(obj, cloneObj)
开开心心收工?有点问题,如果对象中有循环引用,即"你中有我,我中有你"的话,就会导致形成死循环,会导致无法跑出结果,直到超出最大调用堆栈大小
怎么解决这个bug呢?使用map来存取拷贝过的数据,每次调用函数时判断有无拷贝过,有的话,直接返回之前拷贝的数据就行了。而且,这里还有个有意思的地方:递归调用函数需要共享变量时,可以通过添加一个参数,一直传同一个变量
改进后:
function deepClone(target, map = new Map()) {
if (typeof target === 'object' && target !== null) {
const cache = map.get(target)
if (cache) {
return cache
}
const ret = Array.isArray(target) ? [] : {}
map.set(target, ret)
for (const key in target) {
if (target.hasOwnProperty(key)) {
ret[key] = deepClone(target[key], map)
}
}
return ret
} else {
return target
}
}
优化遍历性能:
- 数组: while | for | forEach() 优于 for-in | keys()&forEach()
- 对象: for-in 与 keys()&forEach() 差不多
变更部分:分成数组和对象分别处理,使用更优的遍历方式(个人看不出有什么大的区别,先记一下)
if (Array.isArray(target)) {
target.forEach((item, index) => {
ret[index] = deepClone(item, map)
})
} else {
Object.keys(target).forEach(key => {
ret[key] = deepClone(target[key], map)
})
}
3. 事件
JavaScript事件回顾
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.outter {
position: relative;
width: 200px;
height: 200px;
background-color: red;
}
.inner {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 100px;
height: 100px;
margin: auto;
background-color: blue;
}
</style>
</head>
<body>
<div class="outter">
<div class="inner"></div>
</div>
<script>
const outter = document.querySelector('.outter')
const inner = document.querySelector('.inner')
outter.addEventListener('click', function () {
console.log('捕获 outter')
}, true)
inner.addEventListener('click', function () {
console.log('捕获 inner')
}, true)
outter.addEventListener('click', function () {
console.log('冒泡 outter')
}, false)
inner.addEventListener('click', function () {
console.log('冒泡 inner')
})
</script>
</body>
</html>
3.1 自定义事件委托函数
自定义事件委托函数关键:获取真正触发事件的目标元素,若和子元素相匹配,则使用call调用回调函数(this指向,变更为target)
function addEventListener(el, type, fn, selector) {
el = document.querySelector(el)
if (!selector) {
el.addEventListener(type, fn)
} else {
el.addEventListener(type, function (e) {
const target = e.target
if (target.matches(selector)) {
fn.call(target, e)
}
})
}
}
测试
<ul id="items">
<li>01</li>
<li>02</li>
<li>03</li>
<li>04</li>
<li>05</li>
<p>06</p>
</ul>
<script>
addEventListener('#items', 'click', function () {
this.style.color = 'red'
}, 'li')
</script>
3.2 手写事件总线
on(eventName, listener): 绑定事件监听 emit(eventName, data): 分发事件 off(eventName): 解绑指定事件名的事件监听, 如果没有指定解绑所有
class EventBus {
constructor() {
this.callbacks = {}
}
on(eventName, fn) {
if (this.callbacks[eventName]) {
this.callbacks[eventName].push(fn)
} else {
this.callbacks[eventName] = [fn]
}
}
emit(eventName, data) {
let callbacks = this.callbacks[eventName]
if (callbacks && this.callbacks[eventName].length !== 0) {
callbacks.forEach(callback => {
callback(data)
})
}
}
off(eventName) {
if (eventName) {
if (this.callbacks[eventName]) {
delete this.callbacks[eventName]
}
} else {
this.callbacks = {}
}
}
}
测试:
const eventBus = new EventBus()
eventBus.on('login', function (name) {
console.log(`${name}登录了`)
})
eventBus.on('login', function (name) {
console.log(`${name}又登录了`)
})
eventBus.on('logout', function (name) {
console.log(`${name}退出登录了`)
})
eventBus.emit('login', '赤蓝紫')
eventBus.emit('logout', '赤蓝紫')
4. 自定义发布订阅
class PubSub {
constructor() {
this.callbacks = {}
this.id = 0
}
subscribe(msg, subscriber) {
const token = 'token_' + (++this.id)
if (this.callbacks[msg]) {
this.callbacks[msg][token] = subscriber
} else {
this.callbacks[msg] = {
[token]: subscriber
}
}
return token
}
publish(msg, data) {
const callbacksOfmsg = this.callbacks[msg]
if (callbacksOfmsg) {
Object.values(callbacksOfmsg).forEach(callback => {
callback(data)
})
}
}
unsubscribe(flag) {
if (flag === undefined) {
this.callbacks = {}
} else if (typeof flag === 'string') {
if (flag.indexOf('token') === 0) {
const callbacks = Object.values(this.callbacks).find(callbacksOfmsg => callbacksOfmsg.hasOwnProperty(flag))
delete callbacks[flag]
} else {
delete this.callbacks[flag]
}
} else {
throw new Error('如果传入参数, 必须是字符串类型')
}
}
}
测试:
const pubsub = new PubSub()
let pid1 = pubsub.subscribe('pay', (data) => { console.log('商家接单: ', data) })
let pid2 = pubsub.subscribe('pay', () => { console.log('骑手接单') })
let pid3 = pubsub.subscribe('feedback', (data) => { console.log(`评价: ${data.title}${data.feedback}`) })
pubsub.publish('pay', {
title: '炸鸡',
msg: '预定11:11起送'
})
pubsub.publish('feedback', {
title: '炸鸡',
feedback: '还好'
})
console.log('%c%s', 'color: blue;font-size: 20px', '取消订阅')
pubsub.unsubscribe('pay')
console.log(pubsub)
5. 封装axios
详见:axios笔记
function axios({ url, method = 'GET', params = {}, data = {} }) {
return new Promise((resolve, reject) => {
method = method.toUpperCase()
let queryString = ''
Object.keys(params).forEach(key => {
queryString += `${key}=${params[key]}&`
})
queryString = queryString.slice(0, -1)
url += `?${queryString}`
const xhr = new XMLHttpRequest()
xhr.open(method, url)
if (method === 'GET') {
xhr.send()
} else {
xhr.setRequestHeader('Content-type', 'application/json;charset=utf-8')
xhr.send(JSON.stringify(data))
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
const { status } = xhr
if (xhr.status >= 200 && xhr.status < 300) {
const response = {
status,
data: JSON.parse(xhr.response)
}
resolve(response)
} else {
reject(`${status}`)
}
}
}
})
}
axios.get = (url, options) => {
return axios(Object.assign(options, { url, method: 'GET' }));
}
axios.post = (url, options) => {
return axios(Object.assign(options, { url, method: 'POST' }));
}
axios.put = (url, options) => {
return axios(Object.assign(options, { url, method: 'PUT' }));
}
axios.delete = (url, options) => {
return axios(Object.assign(options, { url, method: 'DELETE' }));
}
测试:
(async function () {
try {
const { data: data1 } = await axios({
url: 'https://api.apiopen.top/getJoke',
method: 'get',
params: {
a: 10,
b: 15
}
})
console.log(data1)
const { data: data2 } = await axios.post('https://api.apiopen.top/getJoke', {
params: {
a: 1,
b: 2
}
})
console.log(data2)
} catch (err) {
console.log(err)
}
})()
|