cosyer's Blog

Blog


  • 首页

  • 友链

  • 留言板

  • 归档

  • 关于

  • 搜索

JS 分类

4月
03
更新于
4月03
2020
JS

实现compose

发表于 2020-04-02 | 热度 ℃
| 字数统计: 1,321 (字) | 阅读时长: 6 (分钟)

实现compose函数的五种思路

  • 面向过程
  • 函数组合(reduce)
  • 函数交织(AOP编程)
  • Promise(sequence)
  • Generator(yield)

阅读全文 »
8月
24
更新于
8月24
2020
JS

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

发表于 2020-03-28 | 热度 ℃
| 字数统计: 778 (字) | 阅读时长: 3 (分钟)

subscribe

从图中可以看出:

  • 观察者与目标(被观察者)是直接进行交互的,包括订阅和触发。
  • 发布订阅模式是透过一个中间者当调度中心,相关的订阅与发布都由调度中心来进行协调。(适合更复杂的场景)

两者的优缺点:

  • 观察者模式:优点就是一一对应,比较直观明了,占用内存资源容易进行回收。缺点就是两者耦合。
  • 发布订阅模式:优点就是一方面实现了发布者与订阅者之间的解耦,中间者可在两者操作之间进行更细粒度的控制。如:条件过滤发布,权限控制等等。缺点就是整一个中间调度会越来越庞大,需要手动清除里面的发布回调。

举个栗子:

  • 观察者模式:彩票中心里,管理员充当目标对象(被观察者),彩民充当观察者,当管理员说公布一等奖号码时,即给各个观察者发布了消息,然后彩民(观察者)就收到发布消息,进行自己的后续操作(兑奖)。

  • 发布订阅模式:每家每户向牛奶订购中心订购了牛奶,但是各家的牛奶品牌不一样,有燕塘、蒙牛等等。当燕塘牛奶来货了,订阅中心就给订购燕塘的各家各户派发燕塘牛奶。同理,当蒙牛到货时,订阅中心发布蒙牛的牛奶。


阅读全文 »
8月
31
更新于
8月31
2020
JS

javascript设计模式

发表于 2019-12-18 | 热度 ℃
| 字数统计: 3,498 (字) | 阅读时长: 15 (分钟)

概念

设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢 的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。

设计原则(5个)

S – Single Responsibility Principle 单一职责原则

  • 一个程序只做好一件事
  • 如果功能过于复杂就拆分开,每个部分保持独立

O – OpenClosed Principle 开放/封闭原则

  • 对扩展开放,对修改封闭
  • 增加需求时,扩展新代码,而非修改已有代码

L – Liskov Substitution Principle 里氏替换原则

  • 子类能覆盖父类
  • 父类能出现的地方子类就能出现

I – Interface Segregation Principle 接口隔离原则

  • 保持接口的单一独立
  • 类似单一职责原则,这里更关注接口

D – Dependency Inversion Principle 依赖倒转原则

  • 面向接口编程,依赖于抽象而不依赖于具
  • 使用方只关注接口而不关注具体类的实现

设计模式的类型

如果从作用上来划分,JavaScript设计模式大概分为五种设计类型:

创建型设计模式 结构型设计模式 行为型设计模式 技巧型设计模式 架构型设计模式
简单工厂模式 外观模式 模板方法模式 链模式 同步模块
工厂方法模式 适配器模式 观察者模式 委托模式 异步模块模式
抽象工厂模式 代理模式 状态模式 数据访问对象模式 Widget模式
建造者模式 装饰者模式 策略模式 节流模式 MVC模式
原型模式 桥接模式 职责链模式 简单模板模式 MVP模式
单例模式 组合模式 命令模式 惰性模式 MVVM模式
享元模式 访问者模式 参与者模式
中介者模式 等待者模式
备忘录模式
迭代器模式
解释器模式

阅读全文 »
11月
25
更新于
11月25
2019
JS

事件机制

发表于 2019-11-24 | 热度 ℃
| 字数统计: 507 (字) | 阅读时长: 3 (分钟)

事件机制的核心就是发布-订阅模式。维护一个对象,对象的 key 存的是事件 type,对应的 value 为触发相应 type 的回调函数,即 listeners,然后 trigger 时遍历通知,即 forEach 进行回调执行。

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
44
45
46
47
48
49
50
51
class EventTarget {
constructor() {
this.listener = [];
}

// 监听事件
on(type, callback) {
// 如果是第一次监听该事件,则初始化数组
if (!this.listeners[type]) this.listeners[type] = [];
this.listeners[type].push(callback);
}

// 只监听一次
once(type, callback) {
if (!this.listeners[type]) this.listeners[type] = [];
// once 只触发一次,触发后 off 即可 回调函数加上标识
callback._once = true;
this.listeners[type].push(callback);
}

// 取消监听
off(type, callback) {
const listeners = this.listeners[type];
if (Array.isArray(listeners)) {
// filter 返回新的数组,会每次对 this.listeners[type] 分配新的空间
// this.listeners[type] = listeners.filter(l => l !== callback);
// 根据 type 取消对应的回调
const index = listeners.indexOf(callback);
// 用 splice 要好些,直接操作原数组
this.listeners[type].splice(index, 1);
// 如果回调为空,删除对该事件的监听
if (this.listeners[type].length === 0) delete this.listeners[type];
}
}

// 执行 扳机社
trigger(event) {
// type 为必传属性
const { type } = event;
if (!type) throw new Error('没有要触发的事件!');
// 判断是否之前对该事件进行监听了
const listeners = this.listeners[type];
if (!listeners) throw new Error(`没有对象监听 ${type} 事件!`);
if (!event.target) event.target = this;
listeners.forEach(l => {
l(event);
// 如果通过 once 监听,执行一次后取消
if (l._once) this.off(type, l);
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 测试
function handleMessage(event) { console.log(`message received: ${ event.message }`); }

function handleMessage2(event) { console.log(`message2 received: ${ event.message }`); }

const target = new EventTarget();

target.on('message', handleMessage);
target.on('message', handleMessage2);
target.trigger({ type: 'message', message: 'hello custom event' }); // 打印 message,message2

target.off('message', handleMessage);
target.trigger({ type: 'message', message: 'off the event' }); // 只打印 message2

target.once('words', handleMessage);
target.trigger({ type: 'words', message: 'hello2 once event' }); // 打印 words
target.trigger({ type: 'words', message: 'hello2 once event' }); // 报错:没有对象监听 words 事件!
7月
10
更新于
7月10
2020
JS

关于requestAnimationFrame

发表于 2019-09-22 | 热度 ℃
| 字数统计: 1,691 (字) | 阅读时长: 7 (分钟)

在JS中,我们可以使用 setTimeout 和 setIntarval 实现动画,但是 H5 的出现,让我们又多了两种实现动画的方式,分别是 CSS 动画(transition、animation)和 H5的canvas 实现。除此以外,H5还提供了一个专门用于请求动画的API,让 DOM 动画、canvas动画、svg动画、webGL动画等有一个专门的刷新机制。

传统的javascript 动画是通过定时器 setTimeout 或者 setInterval 实现的。但是定时器动画一直存在两个问题

  • 动画的循时间环间隔不好确定(推荐的最佳循环间隔是17ms(大多数电脑的显示器刷新频率是60Hz,1000ms/60))
  • 定时器第二个时间参数只是指定了多久后将动画任务添加到浏览器的UI线程队列中,如果UI线程处于忙碌状态,那么动画不会立刻执行

1. 定义

requestAnimationFrame 方法会告诉浏览器希望执行动画并请求浏览器在下一次重绘之前调用回调函数来更新动画。

1
window.requestAnimationFrame(callback)
  • callback:下一次重绘之前更新动画帧所调用的函数,callback仅有一个参数,为DOMHighResTimeStamp参数,表示requestAnimationFrame()开始执行回调函数的时刻。
  • 返回值:一个 long 类型整数,唯一标志元组在列表中的位置,你可以传这个值给cancelAnimationFrame() 以取消动画。

在使用和实现上, requestAnimationFrame 与 setTimeout 类似。举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let count = 0;
let rafId = null;
/**
* 回调函数
* @param time requestAnimationFrame 调用该函数时,自动传入的一个时间
*/
function requestAnimation(time) {
console.log(time); // 打印执行requestAnimation函数的时刻
// 动画没有执行完,则递归渲染
if (count < 5) {
count++;
// 渲染下一帧
rafId = window.requestAnimationFrame(requestAnimation);
}
}
// 渲染第一帧
window.requestAnimationFrame(requestAnimation);

2.怎样执行

  • 首先判断 document.hidden 属性是否可见(true),可见状态下才能继续执行以下步骤
  • 浏览器清空上一轮的动画函数
  • requestAnimationFrame 将回调函数追加到动画帧请求回调函数列表的末尾 注意:当执行 requestAnimationFrame(callback)的时候,不会立即调用 callback 回调函数,只是将其放入回调函数队列而已,同时注意,每个 callback回调函数都有一个 cancelled 标志符,初始值为 false,并对外不可见。
  • 当页面可见并且动画帧请求callback回调函数列表不为空时,浏览器会定期将这些回调函数加入到浏览器 UI 线程的队列中(由系统来决定回调函数的执行时机)。当浏览器执行这些 callback 回调函数的时候,会判断每个元组的 callback 的cancelled标志符,只有 cancelled 为 false 时,才执行callback回调函数。

3. 优点

  1. requestAnimationFrame 自带函数节流功能,采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间的过短,造成过度绘制,增加页面开销,也不会因为间隔时间过长,造成动画卡顿,不流程,影响页面美观。 浏览器的重绘频率一般会和显示器的刷新率保持同步。大多数采用 W3C规范,浏览器的渲染页面的标准频率也为 60 FPS(frames/per second)即每秒重绘60次,requestAnimationFrame的基本思想是 让页面重绘的频率和刷新频率保持同步,即每 1000ms / 60 = 16.7ms执行一次。 通过 requestAnimationFrame 调用回调函数引起的页面重绘或回流的时间间隔和显示器的刷新时间间隔相同。所以 requestAnimationFrame 不需要像 setTimeout 那样传递时间间隔,而是浏览器通过系统获取并使用显示器刷新频率。例如在某些高频事件(resize,scroll 等)中,使用 requestAnimationFrame 可以防止在一个刷新间隔内发生多次函数执行,这样保证了流程度,也节省了开销
  2. 另外,该函数的延时效果是精确的,没有setTimeout或setInterval不准的情况(JS是单线程的,setTimeout 任务被放进异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列的任务是否需要开始执行,造成时间延时)。 setTimeout的执行只是在内存中对图像属性进行改变,这个改变必须要等到下次浏览器重绘时才会被更新到屏幕上。如果和屏幕刷新步调不一致,就可能导致中间某些帧的操作被跨越过去,直接更新下下一帧的图像。即 掉帧 使用 requestAnimationFrame 执行动画,最大优势是能保证回调函数在屏幕每一次刷新间隔中只被执行一次,这样就不会引起丢帧,动画也就不会卡顿
  3. 节省资源,节省开销 在之前介绍requestAnimationFrame执行过程,我们知道只有当页面激活的状态下,页面刷新任务才会开始,才执行 requestAnimationFrame,当页面隐藏或最小化时,会被暂停,页面显示,会继续执行。节省了 CPU 开销。 注意:当页面被隐藏或最小化时,定时器setTimeout仍在后台执行动画任务,此时刷新动画是完全没有意义的(实际上 FireFox/Chrome 浏览器对定时器做了优化:页面闲置时,如果时间间隔小于 1000ms,则停止定时器,与requestAnimationFrame行为类似。如果时间间隔>=1000ms,定时器依然在后台执行)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 在浏览器开发者工具的Console页执行下面代码。
    // 当开始输出count后,切换浏览器tab页,再切换回来,可以发现打印的值从离开前的值继续输出
    let count = 0;
    function requestAnimation() {
    if (count < 100) {
    count++;
    console.log(count);
    requestAnimationFrame(requestAnimation);
    }
    }
    requestAnimationFrame(requestAnimation);
  4. 能够在动画流刷新之后执行,即上一个动画流会完整执行

4. 实现

我们可以使用 requestAnimationFrame 实现setInterval及 setTimeout

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
// setInterval实现
function setInterval(callback, interval) {
let timer
const now = Date.now
let startTime = now()
let endTime = startTime
const loop = () ={
timer = window.requestAnimationFrame(loop)
endTime = now()
if (endTime - startTime >= interval) {
startTime = endTime = now()
callback(timer)
}
}
timer = window.requestAnimationFrame(loop)
return timer
}

let a = 0
setInterval(timer ={
console.log(a)
a++
if (a === 3) window.cancelAnimationFrame(timer)
}, 1000)
// 0
// 1
// 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// setTimeout 实现
function setTimeout(callback, interval) {
let timer
const now = Date.now
let startTime = now()
let endTime = startTime
const loop = () ={
timer = window.requestAnimationFrame(loop)
endTime = now()
if (endTime - startTime >= interval) {
callback(timer)
window.cancelAnimationFrame(timer)
}
}
timer = window.requestAnimationFrame(loop)
return timer
}

let a = 0
setTimeout(timer ={
console.log(a)
a++
}, 1000)
// 0

5. 参考

MDN

2月
24
更新于
2月24
2021
JS

ES6Proxy

发表于 2019-07-31 | 热度 ℃
| 字数统计: 1,586 (字) | 阅读时长: 7 (分钟)

Proxy,代理,是ES6新增的功能,可以理解为代理器(即由它代理某些操作)。

Proxy 对象用于定义或修改某些操作的自定义行为,可以在外界对目标对象进行访问前,对外界的访问进行改写。

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。


阅读全文 »
9月
07
更新于
9月07
2020
JS

EventLoop

发表于 2019-06-16 | 热度 ℃
| 字数统计: 2,091 (字) | 阅读时长: 9 (分钟)

描述事件队列的过程?

  • js是单线程的,会出现阻塞问题,因此有了任务队列的出现
  • 主线程同步执行任务,异步的工作一般会交给其他线程完成,然后回调函数会放到任务队列中
  • 等候主线程执行完毕后再执行任务队列中的操作

event-queue

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。只要异步任务有了运行结果,就在”任务队列”之 中放置一个事件。

task-queue 任务队列中存着的是异步任务,这些异步任务一定要等到执行栈清空后才会执行。

同步就是发出一个请求后什么事都不做,一直等待请求返回后才会继续做事;异步就是发出请求后继续去做其他事,这个请求处理完成后会通知你,这时候就可以处理这个回应了。


阅读全文 »
8月
24
更新于
8月24
2020
JS

PWA手记

发表于 2019-05-26 | 热度 ℃
| 字数统计: 3,523 (字) | 阅读时长: 14 (分钟)

PWA作为2018最火热的技术概念之一,对提升Web应用的安全、性能和体验有着很大的意义,非常值得我们去了解与学习。

PWA是Progressive Web App的英文缩写,也就是渐进式增强WEB应用。目的就是在移动端利用提供的标准化框架,在网页应用中实现和原生应用相近的用户体验。

一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用. 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的

安装和离线等功能。

我们需要理解的是,PWA不是某一项技术,或者某一个新的产物;而是一系列Web技术与标准的集合与应用。通过应用这些新的技术与标准,可以从安

全、性能和体验三个方面,优化我们的Web App。所以,其实PWA本质上依然是一个Web App。

阅读全文 »
6月
15
更新于
6月15
2019
JS

use strict

发表于 2019-05-08 | 热度 ℃
| 字数统计: 1,279 (字) | 阅读时长: 5 (分钟)

严格模式是ES5引入的,更好的将错误检测引入代码的方法。顾名思义,使得JS在更严格的条件下运行。

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
变量必须先声明,再使用
function test(){
"use strict";
foo = 'bar'; // Error
}

不能对变量执行delete操作
var foo = "test";
function test(){}

delete foo; // Error
delete test; // Error

function test2(arg) {
delete arg; // Error
}
对象的属性名不能重复
{ foo: true, foo: false } // Error

禁用eval()

函数的arguments参数
setTimeout(function later(){
// do stuff...
setTimeout( later, 1000 );
}, 1000 );

禁用with(){}

不能修改arguments
不能在函数内定义arguments变量
不能使用arugment.caller和argument.callee。因此如果你要引用匿名函数,需要对匿名函数命名。

严格模式的优点:

  1. 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;

  2. 消除代码运行的一些不安全之处,保证代码运行的安全;

  3. 提高编译器效率,增加运行速度;

  4. 为未来新版本的Javascript做好铺垫。

  • 注:经过测试 IE6,7,8,9 均不支持严格模式。

缺点:

现在网站的 JS 都会进行压缩,一些文件用了严格模式,而另一些没有。这时这些本来是严格模式的文件,被 merge 后,这个串就到了文件的中间,不仅没有指示严格模式,反而在压缩后浪费了字节。


阅读全文 »
7月
07
更新于
7月07
2020
JS

ES6Reflect对象

发表于 2019-05-02 | 热度 ℃
| 字数统计: 1,392 (字) | 阅读时长: 6 (分钟)

介绍

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与处理器对象的方法相同。Reflect不是一个函数对象,因此它是不可构造的。

Reflect这个对象在新版本的chrome是支持的, ff比较早就支持Proxy和Reflect了,要让node支持Reflect可以安装harmony-reflect;

Reflect不是构造函数, 要使用的时候直接通过Reflect.method()调用, Reflect有的方法和Proxy差不多, 而且多数Reflect方法原生的Object已经重新实现了。

与大多数全局对象不同,Reflect没有构造函数。你不能将其与一个new运算符一起使用,或者将Reflect对象作为一个函数来调用。Reflect的所有属性和方法都是静态的(就像Math对象)。


阅读全文 »
123…7
陈宇(cosyer)

陈宇(cosyer)

不去做的话永远也做不到。

159 日志
10 分类
51 标签
RSS
GitHub Twitter E-Mail FB Page
推荐阅读
  • Callmesoul
  • JsTips
  • Personal Site
  • Resume
© 2021 陈宇(cosyer)
终于等到你(UV):   |   欢迎再来(PV):
Blog总字数: 312.5k字
苏ICP备17005342号-1