手写题目
发布订阅
- 组件通信:发布订阅模式可以在不同组件之间通过事件来通信
- 模块通信:不同模块之间需要进行通信
实现思路
- 发布订阅核心,是事件注册on/once、事件触发 emit 方法、
on方法用于注册事件,它接收事件名和回调函数作为参数,将回调函数添加到对应事件的回调列表中。once方法也是注册事件的一种,但是它只会触发一次,即在第一次触发事件时执行完回调后会自动移除监听。
- 定义一个events属性,存储事件的,这个events数据结构是对象,key表示时间名,value是数组方法,因为事件可能被添加多次回调
off方法用于移除事件监听器
//发布订阅
class EventEmitter {
constructor() {
this.events = {};
/*
events对象用来存储事件
events = {
一个事件会可能多个回调 (一个事件可能会被添加多次回调)
add:[fn,fn1,fn2],
delete:[fn,fn1,fn2]
}
*/
}
// 触发event事件,并把参数arg1,arg2,arg3....传给事件处理函数;
emit(event, ...args) {
const cbs = this.events[event];
if (!cbs) {
console.log(`${event} 没有被注册`);
return this;
}
//同步代码,遍历去执行
//使用apply为了改变this,也更好的传递参数给函数去调用
cbs.forEach((cb) => cb.apply(this, args));
return this;
}
//监听event事件,事件触发时调用fn函数
on(event, cb) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(cb);
//如果希望是链式调用的话,返回this
return this;
}
//【注册一次事件】为指定事件注册一个单次监听器,单次监听器最多只触发一次,触发后立即解除监听器;
once(event, cb) {
const func = (...args) => {
//执行回调之前,先移除事件
this.off(event, func);
cb.apply(this, args);
};
this.on(event, func);
return this;
}
//停止监听某个事件,事件的解除
//传2个参数,第二个参数可以指定移除哪个回调
off(event, cb) {
//如果没有传cb,则移除这个事件所有回调
if (!cb) {
this.events[event] = null;
} else {
this.events[event] = this.events[event].filter((item) => item !== cb);
}
return this;
}
}
const ev = new EventEmitter();
ev.once("once", (v) => {
console.log(v);
});
ev.emit("once", "test1");
ev.emit("once", "test2");
call
- call方法,没有指定参数,this指向window
function demo1() {
console.log(this.name)
}
Function.prototype.myCall = function (ctx, ...args) {
//没有指定obj的话,默认指向window
ctx = ctx || window
//定义一个函数,this指向当前调用这个方法的函数
//将方法挂载到ctx
ctx.fn = this
//调用这个函数
const result = ctx.fn(...args)
delete ctx.fn
return result
}
const obj = { name: 'qqqiu' }
demo1.myCall(obj)
apply
Function.prototype.myApply = function (ctx, args = []) {
ctx = ctx || window
ctx.fn = this
const result = ctx.fn(...args)
delete ctx.fn
return result
}
new
-
1、创建一个新对象,将对象的原型指向构建函数的原型对象上
-
2、将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
-
3、执行构造函数中的代码(为这个新对象添加属性)
-
4、返回新对象
- Object.create(proto) , 第一个参数是原型对象
- Object.create()可以返回新对象,使用指定的对象创建新对象的原型
Object.create 方法, 第一个参数是原型对象,以这个原型对象 作为指定的对象来创新新对象的原型
- Object.create(proto) , 第一个参数是原型对象
function myNew(fn, ...args) {
//1.创建一个对象并将obj的原型指向构造函数的原型对象上
//(实例对象的原型__proto__指向它所在的构造函数的原型对象prototype)
let obj = Object.create(fn.prototype);
//2.如果构造函数有返回值,则返回;否则,就会默认返回新对象。
let result = fn.call(obj, ...args);
//3.我们要判断result是不是object类型,如果是object类型,那么就返回result,否则,返回obj
//{},[],function 都属于Object对象
return result instanceof Object? result : obj;
}
compose
// 实现一个compose函数
function fn1(x) {
return x + 1;
}
function fn2(x) {
return x + 2;
}
function fn3(x) {
return x + 3;
}
function compose(...fn) {
return fn.reduce((acc, cur) => {
return (...args) => acc(cur(...args));
});
}
const fn = compose(fn1, fn2, fn3);
console.log(fn(1)); // 1+1+2+3 = 7
tree
const getChildren = (data, pid) => {
var itemArr = [];
for (const item of data) {
if (item.pid === pid) {
const newItem = {...item, children: []};
newItem.children = getChildren(data, item.id);
itemArr.push(newItem);
}
}
return itemArr;
}
const arr = [
{id: 1, name: '部门1', pid: 0},
{id: 2, name: '部门2', pid: 1},
{id: 3, name: '部门3', pid: 1},
{id: 4, name: '部门4', pid: 3},
{id: 5, name: '部门5', pid: 4},
];
const data = getChildren(arr, 0);
console.log('data', data);