深拷贝和事件总线
引用赋值/浅拷贝/深拷贝的区别和关系
console.log(window.window === window)
const info = {
name: "lzs",
age: 18,
friend: {
name: "kobe"
},
running: function() {},
[Symbol()]: "abc",
// obj: info
}
info.obj = info
// 1.操作一: 引用赋值
// const obj1 = info
// 2.操作二: 浅拷贝
// const obj2 = { ...info }
// // obj2.name = "james"
// // obj2.friend.name = "james"
// // console.log(info.friend.name)
// const obj3 = Object.assign({}, info)
// // obj3.name = "curry"
// obj3.friend.name = "curry"
// console.log(info.friend.name) curry
注意:
1、Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象,继承属性和不可枚举属性是不能拷贝的。
2、针对深拷贝,需要使用其他办法,因为 Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。
3、目标对象自身也会改变
4、异常会打断后续拷贝任务
json实现深拷贝
function deepClone(obj) {
const newObj = JSON.parse(JSON.stringify(info))
return newObj
}
实现深拷贝
基本实现(递归)
function deepCopy(origin) {
if(!isObject(origin)) {
return origin
}
const newObj = {}
for(const key in origin) {
newObj[key] = deepCopy(origin[key])
}
return newObj
}
考虑数组
function deepCopy(originValue) {
// 1.如果是原始类型, 直接返回
if (!isObject(originValue)) {
return originValue
}
// 2.如果是对象类型, 才需要创建对象
const newObj = Array.isArray(originValue) ? []: {}
for (const key in originValue) {
newObj[key] = deepCopy(originValue[key]);
}
return newObj
}
考虑其他数据类型
function deepCopy(originValue) {
// 0.如果值是Symbol的类型
if (typeof originValue === "symbol") {
return Symbol(originValue.description)
}
// 1.如果是原始类型, 直接返回
if (!isObject(originValue)) {
return originValue
}
// 2.如果是set类型
if (originValue instanceof Set) {
const newSet = new Set()
for (const setItem of originValue) {
newSet.add(deepCopy(setItem))
}
return newSet
}
// 3.如果是函数function类型, 不需要进行深拷贝
if (typeof originValue === "function") {
return originValue
}
// 2.如果是对象类型, 才需要创建对象
const newObj = Array.isArray(originValue) ? []: {}
// 遍历普通的key
for (const key in originValue) {
newObj[key] = deepCopy(originValue[key]);
}
// 单独遍历symbol
const symbolKeys = Object.getOwnPropertySymbols(originValue)
for (const symbolKey of symbolKeys) {
newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey])
}
return newObj
}
使用weakmap解决循环引用
-
区别一:WeakMap的key只能使用对象,不接受其他的类型作为key;
-
区别二:WeakMap的key对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象;
function deepCopy(obj, map = new WeakMap()) {
if(typeof obj === "Symbol") {
return Symbol(obj.description)
}
if(!isObject(obj)) {
return obj
}
if(obj instanceof Set) {
let set = new Set()
for(let item of obj) {
set.add(deepCopy(item))
}
return set
}
if(typeof obj === "function") {
return obj
}
if(map.get(obj)) return map.get(obj)
const newObj = Array.isArray(obj)?[] : {}
map.set(obj, newObj)
for(const key in obj){
newObj[key] = deepCopy(obj[key],map)
}
const SymbolKeys = Object.getOwnPropertySymbols(obj)
for(const key of symbolKeys) {
newObj[Symbol(key.description)] = deepCopy(obj[key],map)
}
return newObj
}
事件总线
- class LzsEventBus
- 方法
- on(eventName, eventFn)
- emit(eventName, ...args)
- off(eventName, eventFn)
class LzsEventBus {
constructor() {
this.eventMap = {}
}
on(eventName, eventFn) {
let eventFns = this.eventMap[eventName]
if (!eventFns) {
eventFns = []
this.eventMap[eventName] = eventFns
}
eventFns.push(eventFn)
}
off(eventName, eventFn) {
let eventFns = this.eventMap[eventName]
if (!eventFns) return
for (let i = 0; i < eventFns.length; i++) {
const fn = eventFns[i]
if (fn === eventFn) {
eventFns.splice(i, 1)
break
}
}
if(eventFns.length === 0) {
delete this.eventMap[eventName]
}
}
emit(eventName, ...args) {
let eventFns = this.eventMap[eventName]
if(!eventFns) return
eventFns.forEach(fn => {
fn(...args)
})
}
once(eventName, eventFn) {
const execFn = () => {
eventFn();
this.off(eventName, execFn);
};
this.on(evt, execFn);
}
}
并发的异步调度器
JS 实现一个带并发限制的异度调度器 Scheduler,保证同时运行的任务最多有两个。完善下面代码中的 Scheduler 类,使得以下程序能正确输出。
class Scheduler {
add(promiseMaker) {}
}
const timeout = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time);
});
const scheduler = new Scheduler();
const addTask = (time, order) => {
scheduler.add(() => timeout(time).then(() => console.log(order)));
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
// output:2 3 1 4
// 一开始,1,2两个任务进入队列。
// 500ms 时,2完成,输出2,任务3入队。
// 800ms 时,3完成,输出3,任务4入队。
// 1000ms 时,1完成,输出1。
class Scheduler {
constructor() {
this.waitTasks = []
this.excutingTasks = []
this.max = 2
}
add(promiseMaker) {
if(this.excutingTasks.length < this.max) {
this.run(promiseMaker)
}else{
this.waitTasks.push(promiseMaker)
}
}
run(promiseMaker) {
const len = this.excutingTasks.push(promiseMaker)
const index = len -1
promiseMaker().then(() => {
this.excutingTasks.splice(index,1)
if(this.waitTasks.length > 0){
this.run(this.waitTasks.shift())
}
})
}
}