最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Zone.js源码简读

    正文概述 掘金(madlife0412)   2021-01-07   496

    简介

    Angular 引入 Zone.js 以处理变更检测。Zone.js 使 angular 可以决定何时需要刷新UI。

    Zone.js有Node和Web的不同版本,仅描述Web版本。

    Zone.js采用Monkey-patch的方式对默认方法进行替换,目前有标准Api和非标准Api。

    两种Patch方式:Wrap和Task

    对于Api的Patch方式不同,控制的颗粒度是不同的:

    Wrap方式:onInvokeonIntercept

    Task方式:onScheduleTask(Zone内配置了Task就会触发)onInvokeTask(Task任务触发前), onCancelTask(Task任务取消前), onHasTask(Zone内Task状态变化后触发)

    如果想尝试更多的新功能,需要单独引入patch

    Zone.js源码简读

    解决了什么问题?

    为异步任务保留了上下文环境,实现了生命周期钩子。

    基本用法和示例

    概念

    1. Zone区域,通过fork创建一个新的Zone
    2. Zone对平常会用得到的异步操作都做了“替换”,将他们纳入生命周期管理中,提供了颗粒度更细的钩子

    实例

    通过钩子函数模拟一个监控MacroTask耗时的函数

    const perfomanceZone = (function (params) {
        let start = 0;
        let timer = performance ?
            performance.now.bind(performance) : Date.now.bind(Date);
        return {
            onInvokeTask: function (delegate, _, target, task) {
                start = timer();
                delegate.invokeTask(target, task);
            },
            onHasTask: function (delegate, _, target, hasTaskState) {
                if (!hasTaskState.macroTask) {
                    console.log(timer() - start);
                }
            }
        }
    }())
    
    function perfomanceFn(asyncFn) {
        Zone.current.fork(perfomanceZone).run(asyncFn);
    }
    
    perfomanceFn(function name(params) {
        setTimeout(() => {
            // Do Something
        }, 1000)
    })
    

    多个Zone的隔离与嵌套

    const ZoneA = Zone.current.fork({
        name: 'ZoneA',
        onScheduleTask: function (parentZoneDelegate, currentZone, targetZone, task) {},
        onInvokeTask: function (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) {}
    })
    
    const ZoneB = ZoneA.fork({
        name: 'ZoneA',
        onScheduleTask: function (parentZoneDelegate, currentZone, targetZone, task) {},
        onInvokeTask: function (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) {}
    })
    
    ZoneB.run(function () {
        setTimeout(() => {
            console.log(123);
        }, 1000);
    })
    

    在异步任务之间传递上下文

    每一个Zone都有一个properties对象,在Zone内部可以通过Zone.current.get方法得到

    源码探索

    前置准备

    1.Performance

    mark(): 标记

    measure(): 测量两个mark的difference

    2.Patch

    暴力替换

    Zone.js源码简读

    timers的方法,调用了patchTimer

    Zone.js源码简读

    patchTimer里,调用了patchMethod

    Zone.js源码简读

    patchMethod里,先检测是否有该方法,然后检测该方法是否可被重新赋值(writable属性):

    Zone.js源码简读

    isPropertyWritable返回true的话:重写proto的该方法,并且把原方法换一个新名字(加上symbol_name)保存起来。

    这样就完成了patch的load工作。

    以setTimeout为例,走一下内部流程

    Zone

    Zone.js源码简读

    1. 创建Zone时会定义的name和properties;
    2. 这里可以看到很多常用属性的查找逻辑,比如'root','current';
    3. Zone内部通过_currentZoneFrame实现当前Zone的状态保存和初始化的工作;

    Zone.js源码简读

    Delegate

    Zone内部方法的调用都是通过它来定义和执行。

    Zone.js源码简读

    Delegate类定义了钩子函数的执行规则:冒泡。fork时传入ZoneSpec的话,parent的Delegate就会被保存。

    (依照此规则的话,parentDelegate肯定存在,有root的Delegate兜底)

    Zone.js源码简读

    在ZoneSpec里定义onInvokeTask时,第一个参数delegate被传入的是父级的delegate。所以如果当前ZoneSpec没有定义或执行完毕,就执行嵌套父级的方法,直至rootZone执行默认方法,执行原方法内容。Zone.js源码简读

    默认的钩子函数定义如下:Zone.js源码简读

    Fork

    调用Fork创建新Zone。这里的自定义配置称作zoneSpec

    Zone.js源码简读

    源码里,调用Zone的fork方式是通过调用delegate的fork方法:

    Zone.js源码简读

    delegate的fork方法定义如下:

    Zone.js源码简读

    一个三元判断,这里的_forkZS来源自zoneSpec里是否定义了onFork方法,就是你是否自定义了fork的实现方式。如果没有,走 new Zone(targetZone, zoneSpec)。Zone的fork方法传的this就是这里的targetZone,这样就形成了嵌套关系。

    Run

    Run方法会调整_currentZoneFrame的状态。

    Zone.js源码简读

    同时会调用delegate的invoke方法:

    Zone.js源码简读

    这里的_invokeZS同上面的_forkZS,当嵌套链上的zoneSpec有定义onInvoke钩子函数时,就会先调用钩子函数,而是否要继续执行这个task是在钩子函数里我们自定义的。

     onInvoke: function (delegate, _, target, task, applyThis, applyArgs) {
            delegate.invoke(target, task);
        }
    

    如果不手动执行 delegate.invokeTask(target, task),task不会执行。

    这里是从整体上控制run方法的整体,而控制单个异步任务的思路是相同。

    钩子

    Zone.js通过__load_patch替换了默认方法的实现,比如常用的setTimeout等:

    Zone.js源码简读

    1. onSchedule

    patchTimer(触发patch) -> patchMethod(patch) -> scheduleMacroTaskWithCurrentZone(触发schedule)-> Zone.current.scheduleMacroTask(转化为ZoneTask) -> Zone.scheduleTask -> Delegate.scheduleTask

    Zone.js源码简读

    Zone.js源码简读

    Zone.js源码简读

    在Delegate.scheduleTask里会执行定义任务时的函数scheduleFn

    Zone.js源码简读

    还是以setTimeout为例,patchTimer的方法体内是这样定义scheduleFn

    Zone.js源码简读

    在这里,以setTimeout为例,setNative就是原生的setTimeout方法,data.args包含两个内容:原方法体和**原延迟时间。**通过apply调用,传入原来的延迟时间,就会在相同的时间间隔后被替换为ZoneTask的task,进而触发task的invoke方法。

    2. onInvoke

    invoke方法与上面的onSchedule流程类似,会触发onInvoke钩子:

    Zone.js源码简读

    如果我们在某一个父级的ZoneSpec里的onInvokeTask没有手动触发delegate.invokeTask时,就可以阻止异步任务的执行了(当然,我们可以手动调用task.callback,主动跳出冒泡过程直接执行原方法体)。

    总结

    可以看到,在Zone.js内部,Zone,ZoneDelegate和ZoneTask三个类完成了所需的工作。通过分析setTimeout的过程了解到Zone.js的思路大致是暴力替换了全局的方法,但是Zone之外我们调用这些被替换的方法时,并不会触发钩子。

    整个流程简单分为两部分看:

    1. Run方法调整_currentZoneFrame(即Zone.current)的状态
    2. task-ZoneTask-Hooks-task的过程

    Zone.js源码简读

    Zone.js通过patch的方式实现了插件化,其封装和抽象逻辑值得好好研究和学习,例如可追踪的stack信息(需引入long-stack-trace-zone.js)

    // 无Zone 
    main();
    
    // 有Zone
     Zone.current.fork({
         name: 'error',
         onHandleError: function (parentZoneDelegate, currentZone, targetZone, error) {
             console.log(error.stack);
         }
     }).fork(Zone.longStackTraceZoneSpec).run(main);
    

    有Zone:

    Zone.js源码简读

    无Zone:

    Zone.js源码简读

    资料

    Github:github.com/angular/ang…

    CDN:cdnjs.com/libraries/z…

    博客教程:

    1. www.cnblogs.com/whitewolf/p…;

    2. www.imwhite.com.cn/2019/10/zon…;


    起源地 » Zone.js源码简读

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元