试读angular源码第三章:初始化zone
直接看人话总结
前言
承接上一章
项目地址
文章地址
angular 版本:8.0.0-rc.4
欢迎看看我的类angular框架
文章列表
试读angular源码第一章:开场与platformBrowserDynamic
试读angular源码第二章:引导模块bootstrapModule
试读angular源码第三章:初始化zone
angular触发脏检查的历程
angularjs 时代,通过触发 $scope.$apply
$scope.$digest
来通知进行脏检查并更新视图。
从 angularjs 的行为中,谷歌大佬们发现,所有的视图变更都来自于下面几种行为:
- 浏览器事件:
onclick
,onmouseover
,onkeyup
- 定时器:
setInterval
,setTimeout
,setImmediate
- 异步api:
ajax
,fetch
,Promise.then
- 生命周期
angular 便把在 Dart 中实践过的 zone 的技术在 JavaScript 中实现了一次。
什么是zone
Dart 中的异步操作是无法被当前代码 try/cacth
的,而在 Dart 中你可以给执行对象指定一个 zone,类似提供一个上下文执行环境 。
而在这个执行环境内,你就可以全部可以捕获、拦截或修改一些代码行为,比如所有未被处理的异常。
用人话说,zone 就是一个类似 JavaScript 中的执行上下文,提供了一个环境或者过程。
每一个异步方法的执行都在 zone 都被当做为一个Task,并在Task的基础上,zone 为开发者提供了执行前后的钩子函数,来获得执行前后的信息。
Zone
大概讲下 Zone
:
zone.js/lib/zone.ts
const Zone: ZoneType = (function(global: any) {...class Zone implements AmbientZone {...}...let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)};...return global['Zone'] = Zone;
})(global);
复制代码
Zone
是个自执行函数
执行的时候会创建一个 parent
和 zoneSpec
都是 null
,并且 name
是 <root>
的 Zone
实例,所以 Zone
是一颗有唯一根节点的树
执行的末尾通过把 class Zone
赋值给顶层变量的 Zone
属性。
ZoneDelegate
了解下 zone 的代理 ZoneDelegate
zoneSpec
是 fork
子 zone 的时候传入的配置对象
当 Zone
初始化自己的代理 ZoneDelegate
时,会把 Zone
实例 和父级的 zoneSpec
传入
在 Zone
初始化时,会同步初始化一个代理 ZoneDelegate
:
zone.js/lib/zone.ts
class Zone implements AmbientZone {constructor(parent: Zone|null, zoneSpec: ZoneSpec|null) {this._parent = parent;this._name = zoneSpec ? zoneSpec.name || 'unnamed' : '<root>';this._properties = zoneSpec && zoneSpec.properties || {};this._zoneDelegate =new ZoneDelegate(this, this._parent && this._parent._zoneDelegate, zoneSpec);}
}
复制代码
ZoneDelegate
的构造函数把通过 fork
方法创建 Zone
时传入的配置和钩子函数初始化到 ZoneDelegate
代理实例上:
zone.js/lib/zone.ts
class ZoneDelegate implements AmbientZoneDelegate {...constructor(zone: Zone, parentDelegate: ZoneDelegate|null, zoneSpec: ZoneSpec|null) {...this._forkZS = zoneSpec && (zoneSpec && zoneSpec.onFork ? zoneSpec : parentDelegate!._forkZS);this._forkDlgt = zoneSpec && (zoneSpec.onFork ? parentDelegate : parentDelegate!._forkDlgt);this._forkCurrZone = zoneSpec && (zoneSpec.onFork ? this.zone : parentDelegate!.zone);...}fork(targetZone: Zone, zoneSpec: ZoneSpec): AmbientZone {return this._forkZS ? this._forkZS.onFork!(this._forkDlgt!, this.zone, targetZone, zoneSpec) :new Zone(targetZone, zoneSpec);}...
}
复制代码
最后,实际上代理执行钩子的时候,比如 zone.fork
的时候的配置对象内有钩子函数,那么就会调用钩子函数来执行
每个 Zone
都会有一个 ZoneDelegate
代理实例,主要为 Zone
调用传入的回调函数,建立、调用回调函数中的异步任务,捕捉异步任务的错误
Zone.__load_patch
zone 通过 monkey patch 的方式,暴力将浏览器内的异步API进行封装并替换掉,这一块就在这里。
这样当在 Zone 的上下文内运行时,并可以通过 Zone.current
来通知 angular 进行到了哪里并进行变更检测(其实也就是所谓的触发脏检查)(至于如果触发的我们放到下章再讲)
这部分在打包 zonejs 的时候就将这几个替换API操作的文件根据平台打包到一起了,举个浏览器的例子:
zone.js/lib/browser/browser.ts
Zone.__load_patch('timers', (global: any) => {const set = 'set';const clear = 'clear';patchTimer(global, set, clear, 'Timeout');patchTimer(global, set, clear, 'Interval');patchTimer(global, set, clear, 'Immediate');
});
复制代码
具体加载定时器补丁的方法:
zone.js/lib/common/timers.ts
export function patchTimer(window: any, setName: string, cancelName: string, nameSuffix: string) {let setNative: Function|null = null;let clearNative: Function|null = null;...setNative =patchMethod(window, setName, (delegate: Function) => function(self: any, args: any[]) {...});...
}
复制代码
通过 patchMethod
将原生API替换为被 zone 封装过的API来获得与 Zone
通信并触发钩子函数的能力:
zone.js/lib/common/utils.ts
export function patchMethod(target: any, name: string,patchFn: (delegate: Function, delegateName: string, name: string) => (self: any, args: any[]) =>any): Function|null {let proto = target;while (proto && !proto.hasOwnProperty(name)) {proto = ObjectGetPrototypeOf(proto);}if (!proto && target[name]) {// somehow we did not find it, but we can see it. This happens on IE for Window properties.proto = target;}const delegateName = zoneSymbol(name);let delegate: Function|null = null;if (proto && !(delegate = proto[delegateName])) {delegate = proto[delegateName] = proto[name];// check whether proto[name] is writable// some property is readonly in safari, such as HtmlCanvasElement.prototype.toBlobconst desc = proto && ObjectGetOwnPropertyDescriptor(proto, name);if (isPropertyWritable(desc)) {const patchDelegate = patchFn(delegate!, delegateName, name);proto[name] = function() {return patchDelegate(this, arguments as any);};attachOriginToPatched(proto[name], delegate);if (shouldCopySymbolProperties) {copySymbolProperties(delegate, proto[name]);}}}return delegate;
}
复制代码
最后用一个全局变量 patches: {[key: string]: any}
存储打过的补丁,可以用来判断 zone 是否已经上过补丁等。
zone.js/lib/zone.ts
const patches: {[key: string]: any} = {};class Zone implements AmbientZone {static __load_patch(name: string, fn: _PatchFn): void {if (patches.hasOwnProperty(name)) {if (checkDuplicate) {throw Error('Already loaded patch: ' + name);}} else if (!global['__Zone_disable_' + name]) {const perfName = 'Zone:' + name;mark(perfName);patches[name] = fn(global, Zone, _api);performanceMeasure(perfName, perfName);}}
}
复制代码
Task
在 zone 中,每种异步都被称为任务 :Task
type TaskType = 'microTask'|'macroTask'|'eventTask';
type TaskState = 'notScheduled'|'scheduling'|'scheduled'|'running'|'canceling'|'unknown';
interface Task {type: TaskType;state: TaskState;source: string;invoke: Function;callback: Function;data?: TaskData;scheduleFn?: (task: Task) => void;cancelFn?: (task: Task) => void;readonly zone: Zone;runCount: number;cancelScheduleRequest(): void;
}
复制代码
Task
分为三种:
- MicroTask:在当前task结束之后和下一个task开始之前执行的,不可取消,如
Promise,MutationObserver、process.nextTick
- MacroTask:一段时间后才执行的task,可以取消,如
setTimeout, setInterval, setImmediate, I/O, UI rendering
- EventTask:监听事件,可能执行0次或多次,执行时间是不确定的
只有这三种,所以像 DOM0 级别事件如 img.onload=()=>{}
,在 angular里面是无法触发脏检查的。
Task
的状态则有 'notScheduled'|'scheduling'|'scheduled'|'running'|'canceling'|'unknown';
而设置执行运行时的钩子则需要在 zone.fork
时设置配置,看这里:
zone.js/lib/zone.ts
interface ZoneSpec {.../*** Allows interception of task scheduling.*/onScheduleTask?:(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task) => Task;onInvokeTask?:(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task,applyThis: any, applyArgs?: any[]) => any;/*** Allows interception of task cancellation.*/onCancelTask?:(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task) => any;/*** Notifies of changes to the task queue empty status.*/onHasTask?:(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,hasTaskState: HasTaskState) => void;
}
复制代码
onScheduleTask
创建异步任务onInvokeTask
执行异步任务onCancelTask
取消异步任务onHasTask
通知任务队列空状态的更改
通过设置这几种钩子,angular 就能知道某些异步任务执行的哪一步,也可以通过钩子去触发脏检查
实例化ngZone
angular 启动 zonejs 是在上文说过的 bootstrapModule
阶段:
angular/packages/core/src/application_ref.ts
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions):Promise<NgModuleRef<M>> {// Note: We need to create the NgZone _before_ we instantiate the module,// as instantiating the module creates some providers eagerly.// So we create a mini parent injector that just contains the new NgZone and// pass that as parent to the NgModuleFactory.const ngZoneOption = options ? options.ngZone : undefined;const ngZone = getNgZone(ngZoneOption);const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}];// Attention: Don't use ApplicationRef.run here,// as we want to be sure that all possible constructor calls are inside `ngZone.run`!return ngZone.run(() => {...});
}
复制代码
在实例化模块工厂之前,通过 getNgZone
获取了一个 NgZone
实例:
angular/packages/core/src/application_ref.ts
function getNgZone(ngZoneOption?: NgZone | 'zone.js' | 'noop'): NgZone {let ngZone: NgZone;if (ngZoneOption === 'noop') {ngZone = new NoopNgZone();} else {ngZone = (ngZoneOption === 'zone.js' ? undefined : ngZoneOption) ||new NgZone({enableLongStackTrace: isDevMode()});}return ngZone;
}
复制代码
ngZone
angular/packages/core/src/zone/ng_zone.ts
export class NgZone {...constructor({enableLongStackTrace = false}) {if (typeof Zone == 'undefined') {throw new Error(`In this configuration Angular requires Zone.js`);}Zone.assertZonePatched();const self = this as any as NgZonePrivate;self._nesting = 0;self._outer = self._inner = Zone.current;if ((Zone as any)['wtfZoneSpec']) {self._inner = self._inner.fork((Zone as any)['wtfZoneSpec']);}if ((Zone as any)['TaskTrackingZoneSpec']) {self._inner = self._inner.fork(new ((Zone as any)['TaskTrackingZoneSpec'] as any));}if (enableLongStackTrace && (Zone as any)['longStackTraceZoneSpec']) {self._inner = self._inner.fork((Zone as any)['longStackTraceZoneSpec']);}forkInnerZoneWithAngularBehavior(self);}...run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T {return (this as any as NgZonePrivate)._inner.run(fn, applyThis, applyArgs) as T;}}
复制代码
- 在实例化 ngZone 的时候,首先调用了 zone 的一个静态方法
assertZonePatched
,确认下 zone 是否已经打过补丁(是否替换过原生 API 至于为什么我们往下面再说)
zone.js/lib/zone.ts
class Zone implements AmbientZone {static __symbol__: (name: string) => string = __symbol__;static assertZonePatched() {if (global['Promise'] !== patches['ZoneAwarePromise']) {throw new Error('Zone.js has detected that ZoneAwarePromise `(window|global).Promise` ' +'has been overwritten.\n' +'Most likely cause is that a Promise polyfill has been loaded ' +'after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. ' +'If you must load one, do so before loading zone.js.)');}}
}
复制代码
- 初始化 zone
_nesting
为 Zone
执行栈的层数(这个放后面说)
angular/packages/core/src/zone/ng_zone.ts
class NgZone {constructor({enableLongStackTrace = false}) {if (typeof Zone == 'undefined') {throw new Error(`In this configuration Angular requires Zone.js`);}Zone.assertZonePatched(); // 注释:确认是否已经上过zone补丁const self = this as any as NgZonePrivate;self._nesting = 0;self._outer = self._inner = Zone.current; // 注释:此时是 root zone...}
}
复制代码
_outer
和 _inner
为当前全局的 zone Zone.current
,
zone.js/lib/zone.ts
interface ZoneType {/*** @returns {Zone} Returns the current [Zone]. The only way to change* the current zone is by invoking a run() method, which will update the current zone for the* duration of the run method callback.*/current: Zone;
}
复制代码
Zone.current
是 zone 上的一个静态属性,用来保存全局此刻正在使用的 zone,只能通过 zone.run
来 更改
- 调用
forkInnerZoneWithAngularBehavior
从当前的 zone(其实此时就是根<root>Zone
) fork 出一份 angular zone,并设置钩子
angular/packages/core/src/zone/ng_zone.ts
function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) {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 {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 {onLeave(zone);}},onHasTask:(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => {delegate.hasTask(target, hasTaskState);if (current === target) {// We are only interested in hasTask events which originate from our zone// (A child hasTask event is not interesting to us)if (hasTaskState.change == 'microTask') {zone.hasPendingMicrotasks = hasTaskState.microTask;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;}});
}
复制代码
zone.fork
在上面,getNgZone
的时候会 new NgZone
,
而在 NgZone
构造函数的结尾,forkInnerZoneWithAngularBehavior
中执行了 zone._inner.fork
:
angular/packages/core/src/zone/ng_zone.ts
export class NgZone {constructor({enableLongStackTrace = false}) {...forkInnerZoneWithAngularBehavior(self);}
}
function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) {zone._inner = zone._inner.fork({name: 'angular',...});
}
复制代码
zone.fork
主要是创建一个子 Zone
实例,而 fork
方法主要调用构造函数中实例化的 ZoneDelegate
实例的 fork
方法:
zone.js/lib/zone.ts
class Zone implements AmbientZone {constructor(parent: Zone|null, zoneSpec: ZoneSpec|null) {this._parent = parent;this._name = zoneSpec ? zoneSpec.name || 'unnamed' : '<root>';this._properties = zoneSpec && zoneSpec.properties || {};this._zoneDelegate =new ZoneDelegate(this, this._parent && this._parent._zoneDelegate, zoneSpec);}public fork(zoneSpec: ZoneSpec): AmbientZone {if (!zoneSpec) throw new Error('ZoneSpec required!');return this._zoneDelegate.fork(this, zoneSpec);}
}
复制代码
每个 Zone
都会有一个 ZoneDelegate
代理实例,主要为 Zone
调用传入的回调函数,建立、调用回调函数中的异步任务,捕捉异步任务的错误
这里通过调用 ZoneDelegate
实例的 fork
方法从根 Zone
创建了一个 Zone
:
zone.js/lib/zone.ts
class ZoneDelegate implements AmbientZoneDelegate {fork(targetZone: Zone, zoneSpec: ZoneSpec): AmbientZone {return this._forkZS ? this._forkZS.onFork!(this._forkDlgt!, this.zone, targetZone, zoneSpec) :new Zone(targetZone, zoneSpec);}
}
复制代码
所以,当初始化 ngZone
的时候,这个 zone._inner
就是 Zone.current
,也就是 let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)};
时候创建的 new Zone(null, null)
root zone。
因此此时的 zone._inner
就是 Zone.current
其实也是 <root> Zone
所以angular zone
是从 <root>Zone
fork 出的子 zone。
ngZone.run
当初始化好 Zone
和 ZoneDelegate
,angular 调用了 ngZone.run
angular/packages/core/src/zone/ng_zone.ts
export class NgZone {run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T {return (this as any as NgZonePrivate)._inner.run(fn, applyThis, applyArgs) as T;}
}
复制代码
ngZone.run
又调用了 zone.run
zone.js/lib/zone.ts
interface _ZoneFrame {parent: _ZoneFrame|null;zone: Zone;
}let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)};class Zone implements AmbientZone {public run<T>(callback: (...args: any[]) => T, applyThis?: any, applyArgs?: any[], source?: string): T {_currentZoneFrame = {parent: _currentZoneFrame, zone: this};try {return this._zoneDelegate.invoke(this, callback, applyThis, applyArgs, source);} finally {_currentZoneFrame = _currentZoneFrame.parent!;}}
}
复制代码
_currentZoneFrame
是一个全局对象,保存了当前系统中的 zone 帧链
在初始化的时候,会创建一个 parent: null, zone: new Zone
的根 _currentZoneFrame
,因此根 Zone
就是在这里被创建的
它有两个属性:
parent
指向了父zoneFrame
zone
指向了当前激活的zone对象
所以 _currentZoneFrame
并不是固定不变的。
ngZone.run
又触发了 this._zoneDelegate.invoke
zoneDelegate.invoke
zone 是通过 this._zoneDelegate.invoke
执行一个函数:
angular/packages/core/src/zone/ng_zone.ts
class ZoneDelegate implements AmbientZoneDelegate {private _invokeZS: ZoneSpec|null;constructor(zone: Zone, parentDelegate: ZoneDelegate|null, zoneSpec: ZoneSpec|null) {this._invokeZS = zoneSpec && (zoneSpec.onInvoke ? zoneSpec : parentDelegate!._invokeZS);}invoke(targetZone: Zone, callback: Function, applyThis: any, applyArgs?: any[], source?: string): any {return this._invokeZS ? this._invokeZS.onInvoke!(this._invokeDlgt!, this._invokeCurrZone!, targetZone, callback,applyThis, applyArgs, source) :callback.apply(applyThis, applyArgs);}
}
复制代码
invoke
方法接受4个参数:
targetZone: Zone
当前调用ZoneDelegate
的Zone
实例callback: Function
回调函数,其实就是zone.run(callback)
传入的那个函数,实例化模块组件的函数applyThis: any
需要绑定的this
applyArgs?: any[]
回调函数的参数source?: string
资源暂时不知道干嘛的
invoke
方法的作用就是:如果 this._invokeZS
存在并且有 onInvoke
钩子就用 this._invokeZS.onInvoke
执行回调,否则仅仅调用回调函数。
所以回到一开始实例化 ngZone
的最后 forkInnerZoneWithAngularBehavior
的代码:
angular/packages/core/src/zone/ng_zone.ts
function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) {zone._inner = zone._inner.fork({name: 'angular',....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 {onLeave(zone);}},...});
}
复制代码
所以当 onInvoke
钩子调用了 run
的回调的时候,会先后触发 onEnter(zone);
onLeave(zone);
:
angular/packages/core/src/zone/ng_zone.ts
function onEnter(zone: NgZonePrivate) {zone._nesting++;if (zone.isStable) {zone.isStable = false;zone.onUnstable.emit(null);}
}function onLeave(zone: NgZonePrivate) {zone._nesting--;checkStable(zone);
}
复制代码
当进入执行栈时,ngZone._nesting
++
离开 --
钩子函数 onInvoke
又调用了 delegate: ZoneDelegate
既 angular zone
的父级 <Zone>zone
的 invoke
方法:
zone.js/lib/zone.ts
class ZoneDelegate implements AmbientZoneDelegate {invoke(targetZone: Zone, callback: Function, applyThis: any, applyArgs?: any[],source?: string): any {return this._invokeZS ? this._invokeZS.onInvoke!(this._invokeDlgt!, this._invokeCurrZone!, targetZone, callback,applyThis, applyArgs, source) :callback.apply(applyThis, applyArgs);}
}
复制代码
但是因为 <root>Zone
没有 ZoneDelegate
,所以只是执行了 callback.apply(applyThis, applyArgs);
那么为什么这么做呢,让 onInvoke
递归调用 delegate.invoke
?
因为 Zone
实例其实是个树形结构
我猜测 angular 想让执行时经过层层传递触发每一个父级 Zone
的代理对象并触发相应的钩子函数调起对应的操作,最后交由根代理对象来执行真正的回调函数。
所以稍微总结下:
ngZone.run
其实就是调用了创建的 angular zone 的run
方法zone.run
又调用了 fork angular 的时候传入的配置onInvoke
钩子- 配置
onInvoke
钩子又执行了传入run
的回调即:onInvoke
钩子执行了创建模块和组件的函数 - 类似 AOP ,在
onInvoke
执行回调时切入切面,会通过onEnter(zone);
onLeave(zone);
来用EventEmitter
通知 angular
到此为止,初始化好了 zone 的运行环境
总结
用人话总结下:
- 在 zone.js 被引入时,自执行函数
Zone
创建<root>Zone
- 在 zone.js 被引入时,执行替换原生异步API的补丁
bootstrapModuleFactory
在引导根模块时,先会用getNgZone
从<root>Zone
出一个angular Zone
,并设置几个钩子- 调用
ngZone.run
并传入实例化模块工厂和组件的回调函数 ngZone.run
调用angular Zone
的run
方法angular Zone
的run
方法调用fork
出angular Zone
时传入的配置中的onInvoke
执行angular Zone
的onInvoke
触发进入/离开切面的操作,并调起父级 zone 的代理的onInvoke
钩子函数- 由子
Zone
到祖Zone
递归执行onInvoke
钩子,触发对应的切面函数 - 最后由
<root>Zone
执行实例化模块工厂和组件的回调函数
试读angular源码第三章:初始化zone相关推荐
- 读Python源码(三)Python列表的表示
简介: 在家过了个春节快不知道自己是干啥的了:),今天收拾一下心情继续读一下python的源码.这一节打算探究一下Python中列表是如何实现的. 1.Python列表创建 首先来到listobjec ...
- 读 zepto 源码之工具函数
对角另一面 读 zepto 源码之工具函数 Zepto 提供了丰富的工具函数,下面来一一解读. 源码版本 本文阅读的源码为 zepto1.2.0 $.extend $.extend 方法可以用来扩展目 ...
- 读zepto源码之工具函数
2019独角兽企业重金招聘Python工程师标准>>> Zepto 提供了丰富的工具函数,下面来一一解读. 源码版本 本文阅读的源码为 zepto1.2.0 $.extend $.e ...
- 读Zepto源码之Deferred模块
Deferred 模块也不是必备的模块,但是 ajax 模块中,要用到 promise 风格,必需引入 Deferred 模块.Deferred 也用到了上一篇文章<读Zepto源码之Callb ...
- 读Lodash源码——chunk.js
The time is out of joint: O cursed spite, That ever I was born to set it right. --莎士比亚 最艰难的第一步 最近学习遇 ...
- 读spring源码(一)-ClassPathXmlApplicationContext-初始化
工作来几乎所有的项目都用到了spring,却一直没有系统的读下源码,从头开始系统的读下吧,分章也不那么明确,读到哪里记到哪里,仅仅作为个笔记吧. 先看ClassPathXmlApplicationCo ...
- mybatis源码阅读(三):mybatis初始化(下)mapper解析
转载自 mybatis源码阅读(三):mybatis初始化(下)mapper解析 MyBatis 的真正强大在于它的映射语句,也是它的魔力所在.由于它的异常强大,映射器的 XML 文件就显得相对简单. ...
- 【读fastclick源码有感】彻底解决tap“点透”,提升移动端点击响应速度
前言 近期使用tap事件为老夫带来了这样那样的问题,其中一个问题是解决了点透还需要将原来一个个click变为tap,这样的话我们就抛弃了ie用户 当然可以做兼容,但是没人想动老代码的,于是今天拿出了f ...
- java 事件分发机制_读Android源码之事件分发机制最全总结
原标题:读Android源码之事件分发机制最全总结 本文源码来自andorid sdk 22,不同版本会有细微差别,但核心机制是一致的 一.概述 事件分发有多种类型, 本文主要介绍Touch相关的事件 ...
最新文章
- python中的死锁
- Java的反射(二)
- linux添加自己的键盘映射,Linux 键盘映射
- SAP License:对一次性’客户‘的了解
- vac服务器未响应,win7玩csgo官方匹配提示vac无法验证您的游戏会话怎么办
- java excel 导入导出_java中excel文件的导入和导出
- GitHub 的 Pull Request
- Web自动化测试框架-PO模式
- AOV网与拓扑排序、拓扑排序算法
- 计算机质量监督检验报告,质量监督检验(检测)报告自动生成管理系统软件
- 用计算机制作课程表,怎么用word做表格-Word制作课程表的方法,学习必备表格,简单易学...
- CSA发布 | 《区块链的十大攻击、漏洞及弱点》
- ANO匿名飞控分析(1)— 遥控器解码
- 联想一体机电源键不亮_联想力压华为拿下6·18 PC“六冠王”,背后有何秘诀?...
- Java序列中如果有些字段不想被序列化,怎么办
- 学计算机科学与技术会秃顶吗,学计算机真的会秃顶吗?听听过来人怎么说
- doNet面试宝典-常见整理(重复率高)
- 2022年SCI期刊最新影响因子正式发布
- 安卓手机测评_2018最全安卓模拟器跑分测评
- c语言10以内四则运算,C语言-四则运算
热门文章
- 马斯克称特斯拉AutoPilot方案宛如“超人”,首要任务是“不撞车”;网友:???...
- 中关村壹号官兰兰:连接AI企业和传统企业,打造硬科技服务联盟 | MEET2020
- 1、RabbitMQ入门
- 【随想】_无关技术_你是合格的项目经理人吗?
- Template Method (C++实现)
- 网络中最常用的网络命令(6)-完整参数
- 使用JSON替代XML
- mybatis 乐观锁_MybatisPlus新特性之逻辑删除、乐观锁、自动填充
- Prometheus — 安装部署(主机安装)
- Intel x710万兆 SR-IOV 网卡驱动升级