六月第二周(6.8-6.14)的安排

  • 观察者模式 vs 发布订阅模式
  • 函数柯里化
  • prefetch 和 preload区别
  • 实现一个正则表达式(匹配url连接)
  • 内存溢出

观察者模式 vs 发布订阅模式

观察者模式

观察者模式指的是一个对象(Subject)维持一系列依赖于它的对象(Observer),当有关状态发生变更时 Subject 对象则通知一系列 Observer 对象进行更新。
在观察者模式中,Subject 对象拥有添加、删除和通知一系列 Observer 的方法等等,而 Observer 对象拥有更新方法(update)等等。
简单说,就是数据发生改变,对应的处理函数自动执行。
通过一个实例就能很好的明白观察者模式了

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
36
37
38
39
40
41
42
43
// 定义综述主题
function Subject() {
this.observers = []
}
Subject.prototype = {
// 添加observe
add(observer) {
this.observers.push(observer)
},
// 移除observe
remove(observer) {
var observers = this.observers
for(let i = 0; i < observers.length; i ++) {
if (observers[i] === observer) {
observers.splice(i, 1)
}
}
},
// 通知 告诉observe 执行自己的方法
notify() {
var observers = this.observers;
for(var i = 0;i < observers.length;i++){
observers[i].update();
}
}
}
// 观察者定义
function Observer(name) {
this.name = name
}
// 定义观察者里的更新方法
Observer.prototype = {
update() {
console.log(`观察者模式下 name is ${this.name}`);
}
}
// Subject
var sub = new Subject()
var obs1 = new Observer('cpp')
var obs2 = new Observer('chendapeng')
sub.add(obs1)
sub.add(obs2)
sub.notify()

应用:
Vue 通过观察者模式触发视图更新。Vue2.x通过Object.defineProperty劫持data数据,当数据变化后触发setter,setter内部通过订阅器notify消息,notify会调用watcher更新视图

发布订阅模式(Publisher && Subscriber)

希望接受通知的对象基于一个主题通过自定义事件订阅主题
发布者通过调度中心基于一个主题向订阅者发布消息
代码示例

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
// 调度中心
let pubsub = {
list: {}, // 订阅的数组
subscribe(key, fn) { // 订阅
if (!this.list[key]) {
this.list[key] = []
}
this.list[key].push(fn)
},
publish(...args) { // 发布
console.log(args);
console.log(Object.prototype.toString.call(args).slice(8, -1))
const key = args[0]
const fns = this.list[key]
for(let i = 0; i < fns.length; i ++) {
fns[i].apply(this, [args[1]])
}
},
// 取消订阅
unSubscribe(key) {
delete this.list[key]
}
}
pubsub.subscribe('name', (name) => {
console.log('订阅在发布模式下 name is', name);
})
pubsub.publish('name', 'cpp')
pubsub.subscribe('age', (age) => {
console.log('订阅在发布模式下 age is', age);
})
pubsub.publish('age', '18')

区别

观察者模式与发布订阅模式都是定义了一个一对多的依赖关系,当有关状态发生变更时则执行相应的更新。
不同的是,观察者模式依赖于主题(Subject)对象的一系列Observer对象被通知之后只能执行一个特定的更新方法(比如update等),而发布订阅模式阔以通过调度中心,基于不同的主题去执行不同的自定义事件,相对而言,发布订阅模式比观察者模式灵活一些。

原生实现观察者模式

  • es5实现
    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
    private observer(mode: any, old: any, val: any) {
    console.log(`${mode} name属性值从${old} -> ${val}`);
    }
    private es5Implement() {
    const targetObj = {
    age: 28,
    name: ''
    }
    let name = 'cpp'
    // 定义name属性以及其设置方法
    Object.defineProperty(targetObj, 'name', {
    enumerable: true,
    configurable: true,
    get() {
    return name
    },
    set(val) {
    this.observer('es5', name, val)
    name = val
    }
    })
    targetObj.name = 'Martin';
    targetObj.name = 'Lucas';
    console.info('targetObj:', targetObj)
    }
    es6实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class TargetObj {
    constructor(age, name) {
    this.name = name;
    this.age = age;
    }
    set name(val) {
    observer(name, val);
    name = val;
    }
    }

    let targetObj = new TargetObj(1, 'Martin');
    // 定义值改变时的处理函数
    function observer(oldVal, newVal) {
    // 其他处理逻辑...
    console.info('name属性的值从 '+ oldVal +' 改变为 ' + newVal);
    }
    targetObj.name = 'Lucas';

参考链接

函数柯里化(Curry)

什么是curry

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

用途

柯里化实际是把简答的问题复杂化了,但是复杂化的同时,我们在使用函数时拥有了更加多的自由度。 而这里对于函数参数的自由处理,正是柯里化的核心所在。 柯里化本质上是降低通用性,提高适用性。

prefetch 和 preload区别

通过插入一个页面元素来声明一个资源(比如img、script、link)。这种方式会将资源的加载和执行耦合。

用AJAX来加载资源。这种方式只有在时机成熟时才会加载资源,解决了执行时机问题。但是浏览器无法预解析,也就无法提前加载。另外如果页面有大量的阻塞脚本,就会造成延迟。
有没有办法既提前加载资源,又能解耦加载和执行呢?

preload

preload 提供了一种声明式的命令,让浏览器提前加载指定资源(加载后并不执行,加载和执行分离),在需要执行的时候再执行。提供的好处主要是

  • 将加载和执行分离开,可不阻塞渲染和 document 的 onload 事件
  • 提前加载指定资源,不再出现依赖的font字体隔了一段时间才刷出

如何区分 preload 和 prefetch

  • preload 告诉浏览器页面必定需要的资源,browser一定会加载这些资源
  • prefetch 告诉浏览器可能需要的资源,browser不一定会加载这些资源

link标签创建preload

1
2
3
4
5
6
7
8
9
10
<link rel='preload' href='/public/static/theme.css' as='style'>
<link rel='preload' href='/public/static/vue.js' as='script'>
<!-- 或者用script创建 -->
<script>
var link = document.createElement('link')
link.href='/public/static/vue.js'
link.rel = 'preload'
link.as = 'script'
document.head.appendChild(link)
</script>

preload特点

  • 提前加载资源
  • 资源的加载和执行分离
  • 不延迟网页的load事件(除非Preload资源刚好是阻塞 window 加载的资源)

Preload跟其他提前加载资源以及加载和执行分离的方案有什么区别?

vs aysnc

async 脚本是一加载完就立即执行,因此会阻塞window的onload事件。而且目前async仅限于脚本资源。
Preload可以实现async一样的异步加载功能。且不局限于脚本。比如以下代码实现了加载完CSS文件立即作用到网页的功能:

1
<link rel="preload" href="style.css" as="style" onload="this.rel='stylesheet'">

注:如果页面存在同步阻塞脚本,等脚本执行完后,样式才会作用到网页。这样是因为Preload的资源不会阻塞window的onload事件。

vs defer

defer实现了资源的加载和执行分离,并且它能保证defer的资源按照在HTML里的出现顺序执行。跟async一样,目前也只能作用于脚本资源。

使用场景

典型用例:

  • 在单页应用中,提前加载路由文件,提高切换路由时的渲染速度。现在大型的单页应用通常会异步加载路由文件。当用户切换路由时再异步加载相应的模块存在性能问题。可以用Preload提前加载,提升性能。

  • 提前加载字体文件。由于字体文件必须等到CSSOM构建完成并且作用到页面元素了才会开始加载,会导致页面字体样式闪动(FOUT,Flash of Unstyled Text)。所以要用Preload显式告诉浏览器提前加载。假如字体文件在CSS生效之前下载完成,则可以完全消灭FOUT。

参考链接

实现一个正则表达式(匹配url连接)

url: http://niaogege.cn:80/index?title=1#more

1
2
3
const patter = /^(http?:\/\/)([0-9a-zA-Z.]+)([:0-9]+)?([/0-9a-zA-Z.]+)?(\?[0-9a-zA-Z&=]+)?(#[0-9a-zA-Z])?/
// 不区分大小写
const pattern = /^(http?:\/\/)([0-9a-z.]+)([:0-9]+)?([/0-9a-z.]+)?(\?[0-9a-z&=]+)?(#[0-9a-z])?/i

匹配协议

1
^(http?:\/\/)

匹配主域

1
([0-9a-z.]+)

匹配端口

1
([:0-9]+)? // :8080 可有可无 ?

匹配路径

1
([/0-9a-z.]+)? // /index.html 可有可无 ?

匹配查询

1
(\?[0-9a-z&=]+)? // ?query=1&title=1&keyword=2 可有可无 ?

匹配锚点

1
(#[0-9a-z])? // #more 可有可无 ?

常用正则表达

  • 匹配手机号码 /(+86)1\d{10}/
  • 匹配身份证号码 /^(\d{17}(\d|x))$/
  • 匹配邮箱 /^(\w)+(.\w+)@(\w)+((.\w+)+)$/*

参考链接