ZoneJs 源码解析
ZoneJs是什么,它能干什么,它是怎么做到的?
- Zone是为js的执行提供了一个共享的数据上下文。为js函数执行维护了一个逻辑上的调用栈。
- 同时提供了对于函数执行方法的拦截,在函数执行前后,添加一些通用的逻辑(例如日志,异常处理)。
- 统一的任务模型,提供对于宏任务/微任务/事件任务的调度执行。
- Zone提供领域的克隆操作,提供基于父领域的继承复写。
从源码角度来分析Zone的具体实现。以下是依据对于源码的理解,手撸的一个简易的版本,关键的变量方法保持与源码一致。
- ZoneJs中的基本数据类型。
- ZoneTask,是对于Task的封装,记录了Zone,调度,等信息
- ZoneDelegate 它里面的方法是Zone里面方法的镜像,Zone里面的方法负责干活的都委托给了这个实例
- Zone, 它里面包含的逻辑主要是Zone状态的维护,Task状态的维护,具体的干活都委托给了上面这两个。它是一个容器。
- ZoneSpec,这个是定义拦截的地方,如果想对于Zone中方法的执行进行拦截,可以在这个里面定义
class ZoneTask{
type:'MicroTask'|'MacroTask'|'EventTask';
_zone:Zone;
callback:Function;
data:TaskData;
scheduleFn:((task:Task)=>void);
cancelFn:(task:Task)=>void;
public invoke: Function;
constructor(
type: T, source: string, callback: Function, options: TaskData|undefined,
scheduleFn: ((task: Task) => void)|undefined, cancelFn: ((task: Task) => void)|undefined) {
this.type = type;
this.source = source;
this.data = options;
this.scheduleFn = scheduleFn;
this.cancelFn = cancelFn;
if (!callback) {
throw new Error('callback is not defined');
}
this.callback = callback;
const self = this;
if (type === eventTask && options && (options as any).useG) {
this.invoke = ZoneTask.invokeTask;
} else {
this.invoke = function() {
return ZoneTask.invokeTask.call(global, self, this, <any>arguments);
};
}
}
static invokeTask(task:Task,target:any,args:any[]):any{
_numberOfNestedTaskFrames++;
try {
task.runCount++;
return task.zone.runTask(task, target, args);
} finally {
if (_numberOfNestedTaskFrames == 1) {
drainMicroTaskQueue();
}
_numberOfNestedTaskFrames--;
}
}
}
class _ZoneDelegate{
public zone:Zone;
constructor(zone:Zone,parentDelegate:_ZoneDelegate|null,zoneSpec:ZoneSpec){
}
fork(targetZone:Zone,zoneSpec:ZoneSpec){
return this._forkZS?this._forkZS.onFork(this._forkDlgt,this.zone,targetZone,zoneSpec):
new Zone(targetZone,zoneSpec)
}
invoke(targetZone:Zone,callback:Function,applyThis:any,applyArgs:any[],source?:string):any{
return this._invokeZS?this._invokeZS(targetZone,callback,applyThis,applyArgs,source):
callback.apply(applyThis,applyArgs);
}
scheduleTask(targetZone:Zone,task:Task):Task{
let returnTask:Task=task;
if(this._scheduleTaskZS){
returnTask=this._scheduleTaskZS.onScheduleTask(this._scheduleTaskDglt,this._scheduleTaskCurrentZone,targetZone,task);
}else{
if(task.scheduleFn){
task.scheduleFn(task);
}else if(task.type==='MicroTask'){
scheduleMicroTask(task);
}else{
throw new Error('Task is missing scheduleFn.');
}
}
}
invokeTask(targetZone:Zone,task:Task,applyThis?:any,applyArgs:any){
return this._invokeTaskZS?this._invokeTaskZS.onInvokeTask(targetZone,task,applyThis,applyArgs):
task.callback.apply(applyThis,applyArgs);
}
}
class Zone{
_properties:{[key:string]:any};
_parent:Zone|null;
_name:string;
_zoneDelegate:_ZoneDelegate;
public fork(zoneSpec:ZoneSpec):Zone{
if (!zoneSpec) throw new Error('ZoneSpec required!');
return this._zoneDelegate.fork(this, zoneSpec);
}
public runTask(task:Task,applyThis?:any,applyArgs:any):any{
const previousTask = _currentTask;
_currentTask=task;
_currentZoneFrame={parent:_currentZoneFrame,zone:this};
try{
this._zoneDelegate.invokeTask(this,task,applyThis,applyArgs);
}catch(err){
if(this._zoneDelegate.handleError(this,err)){
throw err;
}
}finally{
_currentTask=previousTask;
_currentZoneFrame=_currentZoneFrame.parent;
}
}
scheduleTask(task:Task){
(task as any as ZoneTask<any>)._transitionTo(scheduling, notScheduled);
(task as any as ZoneTask<any>)._zone = this;
task = this._zoneDelegate.scheduleTask(this, task) as T;
if ((task as any as ZoneTask<any>).state == scheduling) {
(task as any as ZoneTask<any>)._transitionTo(scheduled, scheduling);
}
}
}
_microTaskQueue:Task[]=[];
_numberOfNestedTaskFrames:number=0;
_isDrainingMicrotaskQueue:false=false;
function nativeScheduleMicroTask(callback:Function){
Promise.resolve(0).then(callback);
}
function drainMicroTaskQueue(){
if(!_isDrainingMicrotaskQueue){
_isDrainingMicrotaskQueue=true;
while(_microTaskQueue.length>0){
const queue = _microTaskQueue;
_microTaskQueue=[];
for(let i=0;i<queue.length;i++){
const task=queue[i];
try{
task.zone.runTask(task);
}catch(err){
_api.onUnhandledError(error as Error);
}
}
}
_isDrainingMicrotaskQueue=false;
}
}
function scheduleMicroTask(task:Task){
if(_numberOfNestedTaskFrames===0&&_microTaskQueue.length===0){
nativeScheduleMicroTask(drainMicroTaskQueue);
}
task&&_microTaskQueue.push(task);
}
- 在
timer.ts
中提供了patchTimer()
方法来对这些方法进行了拦截。拦截的方式非常暴力,直接将window.setTimeout
给换掉了。下面是对于这些实现的一个简化版
let nativeSettimeout:Funciton;
function customSchedule(task){
task.data.args[0]=function(){
return task.invoke.apply(this,arguments);
}
task.data.handleId = nativeSettimeout.apply(window,data.args);
}
function patchFn(original:Fnction){
return function(args:any){
const callback=args[0];
const options={
isPeriodic:false,
args,
}
const task = Zone.current.scheduleMacroTask(source,callback,options,customSchedule,customCancel);
return task;
}
}
function patchMethod(target:any,name:string,patchFn:(original:Function)=>Function){
const delegate = target[name]
const patchedDelegate=patchFn(delegate);
target[name]=function(){
return patchDelegate(arguments);
}
return delegate;
}
ngZone 它在Zonejs的基础上创建了一个子zone,并对方法的执行进行了拦截。拦截的目的是为了维护当前hasPendingMicroTasks,hasPendingMacroTasks,isStable
,及时发布相应的事件,onMicrotaskEmpty,onStable
.对异常进行捕获处理。例如,它在Invoke前后,添加了onEnter,onLeave
方法来计算状态
zone._inner = zone._inner.fork({
name: 'angular',
properties: <any>{'isAngularZone': true},
onInvokeTask:
(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, applyThis: any,
applyArgs: any): any => {
try {
onEnter(zone);
return delegate.invokeTask(target, task, applyThis, applyArgs);
} finally {
if ((zone.shouldCoalesceEventChangeDetection && task.type === 'eventTask') ||
zone.shouldCoalesceRunChangeDetection) {
delayChangeDetectionForEventsDelegate();
}
onLeave(zone);
}
},
onInvoke:
(delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function, applyThis: any,
applyArgs?: any[], source?: string): any => {
try {
onEnter(zone);
return delegate.invoke(target, callback, applyThis, applyArgs, source);
} finally {
if (zone.shouldCoalesceRunChangeDetection) {
delayChangeDetectionForEventsDelegate();
}
onLeave(zone);
}
},
onHasTask:
(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => {
delegate.hasTask(target, hasTaskState);
if (current === target) {
if (hasTaskState.change == 'microTask') {
zone._hasPendingMicrotasks = hasTaskState.microTask;
updateMicroTaskStatus(zone);
checkStable(zone);
} else if (hasTaskState.change == 'macroTask') {
zone.hasPendingMacrotasks = hasTaskState.macroTask;
}
}
},
onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
delegate.handleError(target, error);
zone.runOutsideAngular(() => zone.onError.emit(error));
return false;
}
});
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
2021-08-19 ResizeObserver 笔记