loading

前端基础 - 手写系列

磨刀不误砍柴工 🕵🕵🕵

# 实现 call、apply、bind

# call

function myCall(ctx = window, ...args) {
  ctx = ctx || window;
  // 为context 创建一个 Symbol(保证不会重名)属性,将当前函数赋值给这个属性
  const fn = Symbol();
  ctx[fn] = this;
  // 处理参数,传入第一个参数后的其余参数
  const res = ctx[fn](...args);
  // 调用函数后即删除该Symbol属性
  delete ctx[fn];
  return res;
}
Function.prototype.call = function (context, ...args) {

  var context = context || window;

  context.fn = this;

  var result = eval('context.fn(...args)');

  delete context.fn

  return result;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# apply

function myApply(ctx = window, args) {
  ctx = ctx || window;
  const fn = Symbol();
  ctx[fn] = this;
  if (Array.isArray(args)) {
    result = ctx[fn](...args);
  } else {
    result = ctx[fn]();
  }
  delete ctx[fn];
  return res;
}
Function.prototype.apply = function (context, args) {

  let context = context || window;

  context.fn = this;

  let result = eval('context.fn(...args)');

  delete context.fn

  return result;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# bind

function myBind(ctx, ...args1) {
  const _this = this;
  return function F(...args2) {
    if (this instanceof F) {
      // 判断是否为构造函数调用,如果是则使用new调用当前函数
      return new _this(...args1, ...args2)
    } else {
      // 如果不是,使用apply,将context和处理好的参数传入
      return _this.apply(ctx, args1.concat(args2))
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 实现发布订阅、观察者模式

观察者模式与订阅发布模式的区别 (opens new window)

一句话总结: 发布-订阅模式是面向调度中心编程的,而观察者模式则是面向目标和观察者编程的。前者用于解耦发布者和订阅者,后者用于耦合目标和观察者

# 发布订阅

class PubSub {
	constructor() {
		this.subers = [];
	}

	sub(topic, callback) {
		let callbacks = this.subers[topic];
		if (!callbacks) {
			this.subers[topic] = [callback];
		} else {
			callbacks.push(callback);
		}
	}

	pub(topic, ...args) {
		let callbacks = this.subers[topic];
		callbacks.forEach(callback => callback(...args))
	}
}

const aEvent = (msg) => {
	console.log(msg + 'aaa');
}

const bEvent = (msg) => {
	console.log(msg + 'bbb');
}

let pubsub = new PubSub();

pubsub.sub('a', aEvent)
pubsub.sub('b', bEvent)

pubsub.pub('a', 'A订阅者')
pubsub.pub('b', 'B订阅者')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 观察者模式

class Sub {
	constructor() {
		this.obers = []
	}

	add(ob) {
		this.obers.push(ob)
	}

	notify(...args) {
		this.obers.forEach(ob => ob.update(...args))
	}
}

class Ob {
	update(...args) {
		console.log(...args)
	}
}

let ob1 = new Ob();
let ob2 = new Ob();

let sub = new Sub();

sub.add(ob1)
sub.add(ob2)


sub.notify('目标发生了变化')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# debounce

一段时间内只执行最后一次

function debounce(fn, delay=500) {
    // timer 写在闭包中,因此防抖也是闭包的一个应用
    let timer = null;

    return function() {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments);
            timer = null;
        }, delay)
    }
}

// 验证
input1.addEventListener('keyup', debounce(() => {
    console.log(input1.value);
}), 600)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# throttle

一段时间内只执行第一次

function throttle(fn, delay = 100) {
    let timer = null

    return function() {
        if (timer) {
            return
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            clearTimeout(timer) 
            timer = null
        }, delay)
    }
}

div1.addEventListener(('drag', throttle(function (e) {
    console.log(e.offsetX, e.offsetY)
})))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
最近更新时间: 2021/05/01 17:06:28
最近更新
01
2023/02/08 00:00:00
02
2023/01/03 00:00:00
03
2022/12/21 00:00:00