zone.js由入门到放弃之四——Angular对zone.js的应用
啸达同学的zone.js系列分享第四篇,新鲜出炉,终于等到了Angular对zone.js的使用
zone.js系列往期文章
- zone.js由入门到放弃之一——通过一场游戏认识zone.js
- zone.js由入门到放弃之二——zone.js API大练兵
- zone.js由入门到放弃之三——zone.js 源码分析【setTimeout篇】
NgZone
我在《zone.js由入门到放弃之一》中简述过zone.js和NgZone的关系,我说ngZone生于zone.js;长于Angular。在这里我再解释一下这句话的意思:首先zone.js维护了一个执行上下文栈,可以帮助开发者追踪异步任务、并通过生命周期勾子注入业务。NgZone实际上就是一个从root zone中fork出来的子zone。只不过这个子zone是专门为Angular量身定做的,并被当作一个可注入的服务被集成到Angular开发工具中。我见过有些文章中说Angular封装了zone.js从而构建出NgZone。其实这种说法是不准确的,个人理解,Angular其实并没有对zone.js的框架或是核心做任何改动,只是利用zone.js的执行上下文来监听异步事件,从而指导Angular在合适的时机进行变更检测。
NgZone的前半生
本文的开始,我们先看下NgZone是在何时何处构造出来的:
首先,一个Angular的工程的入口文件是main.ts
。在main.ts
中,大家大多都会见到这么一句platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err));
,我这里简单叙述一下这里到底执行了些啥。Angular是支持跨平台运行的,那么Angular在执行前需要确定当前工程到底是运行在哪一个平台下的:浏览器?服务端(SSR)?WebWorker?或是移动端?
platformBrowserDynamic()
方法返回的实际就是一个平台对象PlatformRef
,PlatformRef
中定义了如何引导启动一个Angular应用。在这里我多扯几句Angular实现化平台运行的原理。Angular工程在初始化的时候会注入很多基础服务,比如Renderer2、Compiler等等很多。这些服务其实都是一些抽象类,对外提供了统一的API,对内会屏蔽了不同平台之间的差异。当我们的Angular应用运行在不同平台时,Angular都会有一套相对应的实现逻辑;就像设计模式中的适配器一样,不同平台有不同平台的adapter。这也就是为什么,我们在浏览器时使用BrowserModule
启动应用;而在SSR中使用AppServerModule
启动应用。
那么在浏览器模式下,platformBrowserDynamic()
返回的平台信息在application_ref.ts
这个文件下。这个服务对外暴漏了一个bootstrapModuleFactory
方法,当我们通过bootstrapModule
启动Angular应用的时候,bootstrapModule
最终会调用到bootstrapModuleFactory
。而从这个bootstrapModuleFactory
开始,我们将第一次在Angular中看到NgZone的身影。
@Injectable({providedIn: 'platform'}) export class PlatformRef { ... constructor(private _injector: Injector) {} bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions): Promise<NgModuleRef<M>> { ... } ... }
bootstrapModuleFactory何许人也
bootstrapModuleFactory
中通过getZone方法构建了ngZone服务。getZone方法也比较简单,它会实例化一个NgZone服务。
function getNgZone(ngZoneToUse: NgZone|'zone.js'|'noop'|undefined, options: NgZoneOptions): NgZone { let ngZone: NgZone; // 留着后面讲 if (ngZoneToUse === 'noop') { ngZone = new NoopNgZone(); } else { ngZone = (ngZoneToUse === 'zone.js' ? undefined : ngZoneToUse) || new NgZone(options); 👈 } return ngZone; }
而下面就是我简化过后NgZone的构建逻辑,是不是一下子看到很多熟悉的勾子函数。正向前文说的,NgZone就是一个特殊的Zone,而帮助Angular进行变更检测的所有逻辑都集中在ZoneSpec中定义的这几个勾子中,了解了这些内容会对掌握Angular变更检测原理提供很大帮助。不过本期不会对这几个勾子进行详细讲解,下一篇文章,我会step by step地演示这其中的逻辑,对这块感兴趣的可以关注一下。
export class NgZone { constructor() { self._outer = self._inner = Zone.current; zone._inner = zone._inner.fork({ name: 'angular', properties: <any>{ isAngularZone: true }, onInvokeTask: (...): any => { ... }, onInvoke: (...): any => { ... }, onHasTask: (...): any => { ... }, onHandleError: (...): any => { ... }, }); } }
所以本期讲解到此结束...
哈哈,我当然不会这么敷衍的,其实到这里这次的NgZone分享才刚刚开始。NgZone有个非常有意思的属性叫做_outer
,因为大家在使用Angular的时候很少会直接跟zone.js接触,而这个_outer,它也是一个zone的实例,它在Angular中的存在感要远比zone.js多的多。后文中,我们统一把_outer
称之为OuterZone,而把_inner
称之为InnerZone。
export class NgZone { constructor() { self._outer = self._inner = Zone.current; 👈 zone._inner = zone._inner.fork({...}); } }
OuterZone
曾经有一个作者这么评价zone.js对Angular的贡献:
作为 Angular 开发者,我们每个人都欠 Zone.js 一顿饭:多亏了有 Zone 的协助,我们能够以魔术般的方式使用 Angular;事实上,大部分时候我们只是修改了一个属性,Angular 就会自动渲染组件,确保视图总是及时更新。非常酷!
话虽如此,但是如果我说OuterZone的出现就是为了让Angular可以摆脱zone.js的控制而运行,这会不会显得很打脸。Angular团队解释这么做是为了性能。因为zone.js会在初始化时将很多异步方法Patch了,从而可以监控到这些异步任务,并通知Angular在适当的时机进行变更检测。但是有的时候,我们有些业务并不需要触发变更检测,毕竟每进行一次变更检测在时间和空间上都是有消耗的。尤其是像拖拽、鼠标移动、滚动条这种事件,他们会在短时间被触发多次。如果每次事件触发都需要进行变更检测,那就太浪费了。所以,Angular团队以及zone.js的作者都开始想办法,让开发者的一些动作可以不受zone.js的“监管”。这里我总结了几种办法:
- 使用noop代替zone.js,让Angular完全与zone.js脱离关系
- 使用OnPush策略
- 让zone.js停止对某些异步方法进行跟踪
- 使用OuterZone
让Angular完全与zone.js脱离关系
这一点其实Angular团队已经写到官方指导中了,Angular团队同时也给出了代码案例说明了脱离zone.js后应该如何进行变更检测。这里我就不过多介绍这部分内容了,毕竟修改起来也就2行代码的事。
OnPush策略
由于上面脱离方式过于暴烈,Angular同时又提供了OnPush策略用来进行组件级的性能优化。其实按道理讲,OnPush策略其实跟zone.js并没有什么关系,放在这里只不过是想说明一下,这也是一种让代码“脱离”变更检测的方式(OnPush策略并不是完全脱离)。同时,Angular也建议在使用OnPush策略的时候,配合ChangeDetection一起使用,这样能让你在需要变更检测的时候也能恢复变更检测。对于OnPush策略的文献也很多,我这里也不做展开了,感兴趣的可以自己搜一下。
让zone.js停止对某些异步方法进行跟踪
我在《zone.js由入门到放弃之二》中介绍过如何让zone.js放弃对setTimeout进行Patch,当我设置了global.__Zone_disable_timers = true;
后,setTimeout就不会被Patch了。诸如这样的配置有很多,需要的可以点击这里。👈
使用OuterZone
首先,我们明确一下ngZone的构造过程中生成了两个Zone,InnerZone是负责跟Angular配合进行变更检测的;而OuterZone实际就是Zone.current,它并不会参与Angular的变更检测。NgZone中定义了一个runOutsideAngular的方法,这个方法会调用OuterZone.run方法,让参数中的fn
可以执行在OuterZone中。
export class NgZone { ... runOutsideAngular<T>(fn: (...args: any[]) => T): T { return (this as any as NgZonePrivate)._outer.run(fn); } }
举个例子,假设你有一个setTimeout方法。当这个方法在Angular中执行时,由于zone.js对setTimeout进行过打包,所以zone.js会追踪setTimeout的各个执行阶段并触发对应的钩子函数。又由于InnerZone是rootZone的一个子Zone,同时InnerZone中设置了大量了的勾子函数,所以InnerZone也可以感知到setTimeout的执行过程,并在特定的情况下触发便变检测。在Angular中,大多的异步过程都是这么执行的。
当我们有一天不希望某个setTimeout方法再触发变更时,我们可以让这个setTimeout执行在runOutsideAngular
中。此时,因为OuterZone没有设置任何勾子函数,也不会通知Angular应用进行变更检测。所以,runOutsideAngular
实际上相当于给你提供一块世外桃源,让你可以“安静”地运行一些异步任务。
Show me your code
上面讲了这么多概念,下面我想用一个简单的性能优化案例来串一下今天所有的知识点。在本期示例中,我们要做一个自动登出的界面。界面每过5s会检查一次页面上是否有鼠标操作。如果有,则页面保持登录状态;如果没有,界面自动登出。在这里,我们仅对界面的登录、登出状态做简单处理——通过isLogined
控制登录状态。
// app.component.html <div class="hotspot"> <h1>{{isLogined ? '欢迎来到自动登出系统!' : '期待您下次光临!'}}!</h1> </div>
需求澄清
- Origin:界面每5s进行一次检测
- Origin:5s内有鼠标移动、鼠标滚轮事件触发,则界面保持登录态;反之,界面自动登出
V1版本
V1版本中,app组件在构造时启动定时器,定时器每过5s检查页面状态isDirty
。isDirty === trye
,则页面有鼠标事件触发;isDirty === false
,则页面自动登出。鼠标事件通过@HostListener
监听;同时通过ngDoCheck
勾子,观测页面进行变更检测的频次。
// app.component.v1.ts export class AppComponent { isLogined = true; timer: any; isDirty = false; constructor() { this.startTiming(); } // 变更检测时会被触发 ngDoCheck() { console.log('rendering...'); } startTiming() { console.log('startTiming!'); this.timer = setInterval(() => { // 当检测没到有鼠标事件触发过,则停止检测 if (!this.isDirty) { this.stopTiming(); } this.isDirty = false; }, 5000); } stopTiming() { clearInterval(this.timer); this.isLogined = false; } // 鼠标移动监听 @HostListener('mousemove') mouseLisener() { this.isDirty = true; console.log('mousemoved'); } // 鼠标滚轮监听 @HostListener('window:scroll') onScrollEvent() { this.isDirty = true; console.log('scrollmoved'); } ngOnDestroy(): void { this.timer ?? clearInterval(this.timer); } }
通过Angular提供的DevTools可以方便地监控到Angular应用执行过程中的性能情况,当页面加载后,只要稍微动一动鼠标,Angular的变更检测就会疯狂执行(每一个小柱子代表一次CD)。
下图是界面中控制台的疯狂输出,这里每当界面上有鼠标滚轮或是鼠标移动事件发生后,都会引起Angular进行一次变更检测。
V2版本
从V1版本的日志图中我们可以发现,每次scrollmoved
执行过后都会紧跟一个rendering...
打印。所以在V2版本中,我们先对需求做一些调整,我们删除对滚轮事件的监听。为了能尽量全地演示这些性能提升手段,V2版本中,我们通过zone.js中的屏蔽手段屏蔽对鼠标滚轮事件的检测。
- Origin:界面每5s进行一次检测
- Changed:5s内有鼠标移动、
鼠标滚轮事件触发,则界面保持登录态;反之,界面自动登出
我在《zone.js由入门到放弃之二》中讲过对setTimeout方法的屏蔽方法,这里我也把zone.js提供的所有屏蔽API分享出来,大家可以按需使用。
👇👇👇
在Angular中屏蔽很简单,但是有坑。
STEP1
增加一个zone-flag.v2.ts
文件(文件名随便取),内容就一行如下:
(window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll'];
STEP2
在polyfills.ts
文件中写入:
import './zone-flag.v2'; import 'zone.js/dist/zone';
切记,这里必须这么写,直接把
(window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll'];
写到pollyfills是不行,想问为啥,问就是变量提升的坑。👈
此时,重新运行Angular应用,这时你会发现,scrollmoved
日志后面没有再紧跟rendering...
日志了,这意味着鼠标的scroll事件已经不会触发变更检测了。
既然鼠标滚轮事件已经移出我们的监听范围,则我们也可以修改一下app中的代码,将对scroll的监听移除。
// app.component.v2.ts export class AppComponent { // 鼠标滚轮监听 // @HostListener('window:scroll') // onScrollEvent() { // this.isDirty = true; // console.log('scrollmoved'); // } }
V3版本
V2中,我们已经通过zone.js让鼠标的scroll事件脱离了变更检测。接下来,我们使用ngZone的runOutsideAngular
方法,让mousemove也脱离Angular的变更检测。V3的代码,分别注入了Renderer2
,ElementRef
,NgZone
服务。通过runOutsideAngular
注册监听可以让事件不触发变更检测。同时,我们通过mouseMoveUnsub
保存事件的注销方法,在界面登出后注销事件监听。
// app.component.v3.ts export class AppComponent { isLogined = true; timer: any; isDirty = false; mouseMoveUnsub: any; constructor(private render2: Renderer2, private elementRef: ElementRef, private zone: NgZone) { this.startTiming(); // 在OutZone中注册监听事件,事件触发时不会引起变更检测 this.zone.runOutsideAngular(() => { this.mouseMoveUnsub = this.render2.listen(this.elementRef.nativeElement, 'mousemove', this.mouseLisener.bind(this)); }); } // 变更检测时会被触发 ngDoCheck() { console.log('rendering...'); } startTiming() { console.log('startTiming!'); this.timer = setInterval(() => { // 当检测没到有鼠标事件触发过,则停止检测 if (!this.isDirty) { this.stopTiming(); } this.isDirty = false; }, 5000); } stopTiming() { clearInterval(this.timer); this.isLogined = false; // 解除事件监听 this.mouseMoveUnsub(); } // 鼠标移动监听 // @HostListener('mousemove') mouseLisener() { this.isDirty = true; console.log('mousemoved'); } ngOnDestroy(): void { this.timer ?? clearInterval(this.timer); } }
这是优化后的控制台,在保证功能不变的情况下,Console控制台也清净了不少。除了setInterval
会间歇性地触发变更检测,其它的鼠标事件已经都不会触发变更检测了。
接下来,我们把V3的性能profile拿出来对比看一下。在V3版本下,不管你在界面上如何操作鼠标,都不会触发变更检测了。从图上我们也能看出,Angular变更检测的周期基本上每隔5s才会触发一次,与setInterval的执行周期一致,这也是符合预期的。将V3的火焰图跟V1的对比一下你就会发现,此时变更检测的次数远小于之前。我大致看了一下,每次变更检测的耗时大概在0.1ms~0.6ms之间。这么看,同样的功能,性能之间的差异有着天壤之别!
V4
最后一版我们再把最后一点小问题优化掉,从V3图上我们还能零星看到几次rendering...
日志,之前说了,这是由于setInterval
导致的。V4版本就是要把setInterval
产生的变更检测也优化掉。
STEP1
在经过前面的优化学习之后,这里的处理对大家说应该十分好理解了。这里,我们同样通过runOutsideAngular
方法处理setInterval
的执行。
// app.component.v4.ts export class AppComponent { // ... constructor(private render2: Renderer2, private elementRef: ElementRef, private zone: NgZone, private cd: ChangeDetectorRef) { // 在OutZone中注册监听事件,事件触发时不会引起变更检测 this.zone.runOutsideAngular(() => { // 进一步消除setInterval的变更检测 this.startTiming(); this.mouseMoveUnsub = this.render2.listen(this.elementRef.nativeElement, 'mousemove', this.mouseLisener.bind(this)); }); } // ... }
但是,当我们把this.startTiming();
放到runOutsideAngular
后,我们发现如果5s没有对界面操作,界面也不会变成期待您下次光临!!
。这里希望大家能先自己想想这是为什么,然后再往下看。
STEP2
NgZone的本质是用来配合执行变更检测的,当我们使用runOutsideAngular
后,回调函数的执行将会脱离变更检测。又由于在zone.js中,Zone的执行上下文是会传递的;当setInterval中的回调执行是,它依旧会在OutZone中执行。试想一下,当this.stopTiming();
执行在OutZone中的时候,this.isLogined = false;
根本不会引起变更检测,则UI也不会进行渲染。此时,你会发现,当你脱离变更检测的时候,双向绑定的魔力也会消失。
此时,我们就需要手动唤醒变更检测。这里唤醒变更的方式有多钟:
- 通过NgZone.run方法可以让被执行方法回到InnerZone中执行,从而触发变更检测
- 通过ChangeDetectorRef.detectChanges手动进行变更检测
// app.component.v4.ts export class AppComponent { isLogined = true; timer: any; isDirty = false; mouseMoveUnsub: any; constructor(private render2: Renderer2, private elementRef: ElementRef, private zone: NgZone, private cd: ChangeDetectorRef) { // 在OutZone中注册监听事件,事件触发时不会引起变更检测 this.zone.runOutsideAngular(() => { // 进一步消除setInterval的变更检测 this.startTiming(); this.mouseMoveUnsub = this.render2.listen(this.elementRef.nativeElement, 'mousemove', this.mouseLisener.bind(this)); }); } // 变更检测时会被触发 ngDoCheck() { console.log('rendering...'); } startTiming() { console.log('startTiming!'); this.timer = setInterval(() => { // 当检测没到有鼠标事件触发过,则停止检测 if (!this.isDirty) { this.stopTiming(); } this.isDirty = false; this.cd.detectChanges(); }, 5000); } stopTiming() { clearInterval(this.timer); // 方法一:通过this.zone.run恢复变更检测 // this.zone.run(() => { // this.isLogined = false; // }); // 方法二:通过this.zone.run恢复变更检测 this.isLogined = false; this.cd.detectChanges(); // 解除事件监听 this.mouseMoveUnsub(); } // 鼠标移动监听 // @HostListener('mousemove') mouseLisener() { this.isDirty = true; console.log('mousemoved'); } ngOnDestroy(): void { this.timer ?? clearInterval(this.timer); } }
总结
本期内容先概念,后示例,通过一个性能优化案例把本期所学的知识实践了一下。从案例中可以看到,当场景适合时,“摆脱”变更检测带来的性能提升是巨大的。同时,你还会发现,我们其实可以不经过zone.js就触发变更检测,而且性能还不错,这是不是说明我们可以抛弃zone.js了呢?
这其实是一个很有意思的话题,尤其在lvy和OnPush策略推出后。在此,我有些个人观点想分享一下。我个人认为虽然我们有很多途径可以摆脱zone.js和变更检测,但是这些“摆脱”都很临时。尤其当我们清楚了zone.js在背后作出的努力后,我们就知道完全让用户自己去控制变更检测是多么恐怖,就好像一夜回到了ajax + jQuery的时代,每一次的UI渲染都需要用户手动执行。所以说,这些“摆脱”方法其实是在前端业务复杂化到一定程度后,同时人们对极致性能的追求到一定程度后所催生的一种产物;是Angular团队为了迎合更广泛的需求上的一种调整。zone.js毕竟给大家带来了太多便利,想要完全放弃会有不少困难。
我之前看到过一个Angular的大佬在讲lvy,视频最后的问答阶段,有位观众问Angular会不会抛弃zone.js?这位大佬大概的回答是:他不认为zone.js会消失。他们只是建议大家在“傻瓜”节点中通过OnPush策略减少对zone.js的使用,但是针对大多数应用,zone.js不会消失。
油管链接奉上,我就不一句一句给大家翻译了。
最后的最后,下一期我会详细讲一下InnerZone的执行原理,喜欢的请持续关注~~~
OpenTiny 社区招募贡献者啦
OpenTiny Vue 正在招募社区贡献者,欢迎加入我们🎉
你可以通过以下方式参与贡献:
- 在 issue 列表中选择自己喜欢的任务
- 阅读贡献者指南,开始参与贡献
你可以根据自己的喜好认领以下类型的任务:
- 编写单元测试
- 修复组件缺陷
- 为组件添加新特性
- 完善组件的文档
如何贡献单元测试:
- 在
packages/vue
目录下搜索it.todo
关键字,找到待补充的单元测试 - 按照以上指南编写组件单元测试
- 执行单个组件的单元测试:
pnpm test:unit3 button
如果你是一位经验丰富的开发者,想接受一些有挑战的任务,可以考虑以下任务:
- ✨ [Feature]: 希望提供 Skeleton 骨架屏组件
- ✨ [Feature]: 希望提供 Divider 分割线组件
- ✨ [Feature]: tree树形控件能增加虚拟滚动功能
- ✨ [Feature]: 增加视频播放组件
- ✨ [Feature]: 增加思维导图组件
- ✨ [Feature]: 添加类似飞书的多维表格组件
- ✨ [Feature]: 添加到 unplugin-vue-components
- ✨ [Feature]: 兼容formily
参与 OpenTiny 开源社区贡献,你将收获:
直接的价值:
- 通过参与一个实际的跨端、跨框架组件库项目,学习最新的
Vite
+Vue3
+TypeScript
+Vitest
技术 - 学习从 0 到 1 搭建一个自己的组件库的整套流程和方法论,包括组件库工程化、组件的设计和开发等
- 为自己的简历和职业生涯添彩,参与过优秀的开源项目,这本身就是受面试官青睐的亮点
- 结识一群优秀的、热爱学习、热爱开源的小伙伴,大家一起打造一个伟大的产品
长远的价值:
- 打造个人品牌,提升个人影响力
- 培养良好的编码习惯
- 获得华为云 OpenTiny 团队的荣誉和定制小礼物
- 受邀参加各类技术大会
- 成为 PMC 和 Committer 之后还能参与 OpenTiny 整个开源生态的决策和长远规划,培养自己的管理和规划能力
- 未来有更多机会和可能
关于 OpenTiny
OpenTiny 是一套企业级组件库解决方案,适配 PC 端 / 移动端等多端,涵盖 Vue2 / Vue3 / Angular 多技术栈,拥有主题配置系统 / 中后台模板 / CLI 命令行等效率提升工具,可帮助开发者高效开发 Web 应用。
核心亮点:
跨端跨框架
:使用 Renderless 无渲染组件设计架构,实现了一套代码同时支持 Vue2 / Vue3,PC / Mobile 端,并支持函数级别的逻辑定制和全模板替换,灵活性好、二次开发能力强。组件丰富
:PC 端有100+组件,移动端有30+组件,包含高频组件 Table、Tree、Select 等,内置虚拟滚动,保证大数据场景下的流畅体验,除了业界常见组件之外,我们还提供了一些独有的特色组件,如:Split 面板分割器、IpAddress IP地址输入框、Calendar 日历、Crop 图片裁切等配置式组件
:组件支持模板式和配置式两种使用方式,适合低代码平台,目前团队已经将 OpenTiny 集成到内部的低代码平台,针对低码平台做了大量优化周边生态齐全
:提供了基于 Angular + TypeScript 的 TinyNG 组件库,提供包含 10+ 实用功能、20+ 典型页面的 TinyPro 中后台模板,提供覆盖前端开发全流程的 TinyCLI 工程化工具,提供强大的在线主题配置平台 TinyTheme
联系我们:
- 官方公众号:
OpenTiny
- OpenTiny 官网:https://opentiny.design/
- OpenTiny 代码仓库:https://github.com/opentiny/
- Vue 组件库:https://github.com/opentiny/tiny-vue (欢迎 Star)
- Angluar组件库:https://github.com/opentiny/ng (欢迎 Star)
- CLI工具:https://github.com/opentiny/tiny-cli (欢迎 Star)
更多视频内容也可以关注OpenTiny社区,B站/抖音/小红书/视频号。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
zone.js由入门到放弃之三——zone.js 源码分析【setTimeout篇】
Delegate是个好东西,看看孙啸达 同学对ZoneDelegate的介绍吧,这是他关于zone.js系列文章的第三篇~ zone.js系列往期文章 zone.js由入门到放弃之一——通过一场游戏认识zone.js zone.js由入门到放弃之二——zone.js API大练兵 zone.js源码分析 接下来的全是干货,从头到尾,一干到底 一点前置:Zone 和 ZoneDelegate 在前文中,我们一直在回避讲解Zone和ZoneDelegate之间的区别。尤其在上篇文章讲API的时候,我甚至让大家把这两者当成一回事。其实这两者并不是完全相等的。单从Delegate这个单词你也能看出,虽然Zone和ZoneDelegate的API很像,但是真正干活的是ZoneDelegate。我简单节选几段Zone的源码,大家不难发现,大多数Zone的API都直接或间接通过代理中相对应的API完成的。 public fork(zoneSpec: ZoneSpec): AmbientZone { // 此处省略成吨源码 return this._zoneDelegate.fork(this, zo...
- 下一篇
如何在uniapp框架中集成H.265流媒体视频播放器EasyPlayer.js?
H5无插件流媒体播放器EasyPlayer属于一款高效、精炼、稳定且免费的流媒体播放器,可支持多种流媒体协议播放,可支持H.264与H.265编码格式,性能稳定、播放流畅,能支持WebSocket-FLV、HTTP-FLV,HLS(m3u8)、WebRTC等格式的视频流,并且已实现网页端实时录像、在iOS上实现低延时直播等功能。 安防视频监控EasyPlayer是一个支持多框架集成的播放器,我们在前期的文章中也介绍了如何在React框架下集成流媒体视频播放器EasyPlayer.js,感兴趣的用户可以翻阅我们往期的文章进行了解。 有用户反馈,在uniapp框架下集成EasyPlayer.js播放器,但是集成的过程中会报一个“video.js,flv.js找不到”的错。 那么今天我们来详细介绍下在uniapp中如何集成流媒体播放器EasyPlayer.js,具体步骤如下: 1)首先,在HBuider X下创建一个项目; 2)在npm上下载easyplayer.js,下载地址如下: https://www.npmjs.com/package/@easydarwin/easyplayer,找...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,CentOS7官方镜像安装Oracle11G
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Linux系统CentOS6、CentOS7手动修改IP地址
- Docker安装Oracle12C,快速搭建Oracle学习环境