Node.js-Events 模块总结与源码解析
Events
描述
- 大多数 Node.js API 采用异步事件驱动架构,这些对象都是
EventEmitter
类的实例(Emitter),通过触发命名事件(eventName or type)来调用函数(监听器,listener) - Emitter 触发事件时,可以向监听器函数传递任意数量的参数,所有注册到该事件上的监听器函数都会依次同步执行,函数的返回值会被忽略
事件
- 命名规范:驼峰式字符串级任何有效的 JavaScript 属性键
error
error
事件被视为特殊情况,如果没有注册监听器会导致抛出错误、打印堆栈跟踪并退出 Node.js 进程,应始终为error事件注册监听器
errorMonitor
为
errorMonitor
事件注册的监听器不会消耗 error,如果没有为 error 事件注册监听器,依然会导致抛出错误、打印堆栈跟踪并退出 Node.js 进程。'use strict'
const EventEmitter = require('events').EventEmitter;
const ee = new EventEmitter({ captureRejections: true });
ee.on(EventEmitter.errorMonitor, function () {
console.log('ErrorMonitor, call first');
})
ee.on('error', () => {
console.log('customer error listener');
})
ee.emit('error');
newListener
当有新的监听器被添加时,所有
Emitter
都会触发'newListener'
事件。为该事件注册监听器相等于一个钩子函数,可以获取到
事件的名称
和要添加的监听器的引用
class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter();
// 只处理一次,避免无限循环。
myEmitter.once('newListener', (event, listener) => {
console.log(`为${event}添加事件${listener}`)
})
removeListener
- 当现有的监听器被移除时,所有
Emitter
都会触发'removeListener'
事件。 - 为该事件注册监听器相等于一个钩子函数,可以获取到
事件的名称
和要添加的监听器的引用
监听器
this指向
普通函数中的this是触发事件的Emitter
,箭头函数中的this是{}
const EventEmitter = require('events').EventEmitter;
const ee = new EventEmitter();
function f1() {
console.log('run f1, this = ', this);
}
const f2 = () => {
console.log('run arrow function, this = ', this);
}
ee.on('test', f1);
ee.on('test', f2);
ee.emit('test');
异步监听器
如果添加异步监听器,需要开启captureRejections
选项且实现captureRejectionSymbol
方法
const { EventEmitter, captureRejectionSymbol } = require('events');
const ee = new EventEmitter({ captureRejections: true });
// 3中方式获取 kRejection 的 Symbol 值
ee[ee.constructor.captureRejectionSymbol] = function (err, event, ...args) {
console.log('rejection happened for', event, 'with', err, ...args);
}
ee[EventEmitter.captureRejectionSymbol] = function (err, event, ...args) {
console.log('rejection happened for', event, 'with', err, ...args);
}
ee[captureRejectionSymbol] = function (err, event, ...args) {
console.log('rejection happened for', event, 'with', err, ...args);
}
async function f() {
return Promise.reject('async rejection');
}
ee.on('test', f);
ee.emit('test');
文档中这句话没太明白
The
'error'
events that are generated by thecaptureRejections
behavior do not have a catch handler to avoid infinite error loops: the recommendation is to not useasync
functions as'error'
event handlers.
执行次数
通过
EventEmitter#on()
方式注册的监听器,每次触发命名事件都会执行通过
EventEmitter#once()
方式注册的监听器,触发命名事件只会执行一次触发
once
事件时,先触发removeListener
事件移除监听器,再调用执行。在removeListener监听器
中可拿到once 监听器
,可多次执行const EventEmitter = require('events').EventEmitter; let ee = new EventEmitter() ee.once('test', () => console.log('test')) ee.on('removeListener', (eventName, listener) => {
console.log(`eventName: ${eventName}`)
listener() // 多次执行
listener()
listener()
}) ee.emit('test')
// eventName: test
// test
// test
// test
// test
源码 v16.10
代码注释 https://github.com/lfp1024/node/blob/master/lib/events.js
常量
const kRejection = SymbolFor('nodejs.rejection');
const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
const kMaxEventTargetListenersWarned =
Symbol('events.maxEventTargetListenersWarned');
前缀 k 表示常量 (德语 konstant)
构造函数
function EventEmitter(opts) {
EventEmitter.init.call(this, opts); // 使 init 方法中的 this 指向新创建的实例,而非 EventEmitter 本身
}
module.exports = EventEmitter;
opts
captureRejections
- 类型:Boolean
- 描述:是否开启自动捕获异步监听器的 rejection
- false「默认」不开启
- true 开启
静态属性
captureRejectionSymbol
用来自定义异步监听器 rejection 的处理方法
EventEmitter.captureRejectionSymbol = kRejection;
errorMonitor
事件名称
在该事件上注册的监听器,只监听error
事件,且在常规error
事件监听器调用之前被调用,不消耗 error
EventEmitter.errorMonitor = kErrorMonitor;
captureRejections
是否自动捕获异步监听器的rejection
ObjectDefineProperty(EventEmitter, 'captureRejections', {
get() {
return EventEmitter.prototype[kCapture]; // 返回原型上的 kCapture
},
set(value) {
if (typeof value !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE('EventEmitter.captureRejections',
'boolean', value);
}
EventEmitter.prototype[kCapture] = value; // 设置原型上的 kCapture
},
enumerable: true
});
defaultMaxListeners
单个事件默认最大可注册监听器个数
- 默认情况下,每个事件可以最多注册
10
个监听器 - 可以使用
EventEmitter.defaultMaxListeners
属性改变所有EventEmitter
实例的默认值(包括之前创建的)。 如果此值不是一个正数,则抛出RangeError
let defaultMaxListeners = 10;
ObjectDefineProperty(EventEmitter, 'defaultMaxListeners', {
enumerable: true,
get: function() {
return defaultMaxListeners;
},
set: function(arg) {
if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
throw new ERR_OUT_OF_RANGE('defaultMaxListeners',
'a non-negative number',
arg);
}
defaultMaxListeners = arg;
}
});
静态方法
init
实例初始化
EventEmitter.init = function(opts) {
// new EventEmitter(),在init方法执行之前,先创建了一个对象,this就指向该对象
if (this._events === undefined ||
this._events === ObjectGetPrototypeOf(this)._events) { // 避免给原型添加`_events`属性,导致所有实例共享
this._events = ObjectCreate(null); // 纯粹的键值对存储对象,没有原型
this._eventsCount = 0;
}
// 如果原型上有_maxListener则新实例继承(_events不可以继承,_maxListener可以继承)
this._maxListeners = this._maxListeners || undefined;
if (opts?.captureRejections) {
if (typeof opts.captureRejections !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE('options.captureRejections',
'boolean', opts.captureRejections);
}
this[kCapture] = Boolean(opts.captureRejections);
} else {
// Assigning the kCapture property directly saves an expensive
// prototype lookup in a very sensitive hot path.
this[kCapture] = EventEmitter.prototype[kCapture]; // 默认值「false」
}
};
setMaxListeners
EventEmitter.setMaxListeners = function(n = defaultMaxListeners, ...eventTargets) {
if (typeof n !== 'number' || n < 0 || NumberIsNaN(n))
throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n);
if (eventTargets.length === 0) {
defaultMaxListeners = n;
} else {
if (isEventTarget === undefined)
isEventTarget = require('internal/event_target').isEventTarget;
for (let i = 0; i < eventTargets.length; i++) {
const target = eventTargets[i];
if (isEventTarget(target)) {
target[kMaxEventTargetListeners] = n;
target[kMaxEventTargetListenersWarned] = false;
} else if (typeof target.setMaxListeners === 'function') {
target.setMaxListeners(n);
} else {
throw new ERR_INVALID_ARG_TYPE(
'eventTargets',
['EventEmitter', 'EventTarget'],
target);
}
}
}
};
示例
const { EventEmitter } = require('events');
const ee = new EventEmitter();
console.log(ee.getMaxListeners());
EventEmitter.setMaxListeners(5, ee) // 可以用其静态方法修改某个ee的最大监听器个数
console.log(ee.getMaxListeners());
原型属性
Symbol('kCapture')
在原型和实例上各有一份,节省到原型上查找的开销
是否捕获异步监听器的rejection
const kCapture = Symbol('kCapture');
ObjectDefineProperty(EventEmitter.prototype, kCapture, {
value: false,
writable: true,
enumerable: false
});
// 获取方式
const ee = new EventEmitter();
console.log('ee.kCapture = ', ee[Reflect.ownKeys(ee)[3]]); // false
实例属性
Symbol('kCapture')
在原型和实例上各有一份,节省到原型上查找的开销
是否捕获异步监听器的rejection
// init 方法中
// Assigning the kCapture property directly saves an expensive
// prototype lookup in a very sensitive hot path.
this[kCapture] = EventEmitter.prototype[kCapture]; // 默认值「false」
_events
保存 监听事件 和 注册在该事件上的监听器
EventEmitter.prototype._events = undefined;
_eventsCount
当前实例中监听事件的个数
EventEmitter.prototype._eventsCount = 0;
_maxListeners
当前实例单个事件最大可注册监听器个数
EventEmitter.prototype._maxListeners = undefined;
实例方法
添加
on
同 addListener
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
addListener
把监听器添加到指定事件监听器数组的末尾,多次添加相同的监听器会多次调用
返回实例的引用,以便可以链式调用
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false); // 原型上的方法提出去,将this传入即可
};
function _addListener(target, type, listener, prepend) {
let m;
let events;
let existing;
checkListener(listener); // 检测listener是否为function,否则抛异常
events = target._events;
if (events === undefined) { // 未初始化
events = target._events = ObjectCreate(null);
target._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener !== undefined) { // 是否监听 `newListener` 事件,每次注册监听器都会触发,类似钩子
target.emit('newListener', type,
listener.listener ? listener.listener : listener);
// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
events = target._events; // 重新赋值
}
existing = events[type];
}
if (existing === undefined) { // 之前没有监听该事件
// Optimize the case of one listener. Don't need the extra array object.
events[type] = listener; //events 和 target._events 指向同一个对象
++target._eventsCount;
} else { // 之前已经监听该事件
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] =
prepend ? [listener, existing] : [existing, listener];
// If we've already got an array, just append.
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
// Check for listener leak 检测最大监听器个数
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true; // 是否多余?
// No error code for this since it is a Warning
// eslint-disable-next-line no-restricted-syntax
const w = new Error('Possible EventEmitter memory leak detected. ' +
`${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. Use ` +
'emitter.setMaxListeners() to increase limit');
w.name = 'MaxListenersExceededWarning';
w.emitter = target;
w.type = type;
w.count = existing.length;
process.emitWarning(w); // 发出警告
}
}
return target;
}
prependListener
把监听器添加到指定事件的监听器数组开头
返回实例的引用,以便可以链式调用
EventEmitter.prototype.prependListener = function prependListener(type, listener) {
return _addListener(this, type, listener, true);
};
once
添加单次监听器到指定事件的监听器数组末尾,触发的时候先移除再执行
返回实例的引用,以便可以链式调用
function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn); // 先移除监听器「同步操作」
this.fired = true; // 标记已触发,保证只调用一次。【移除监听器】和【保证只调用一次】是两个分开的逻辑
if (arguments.length === 0) // 再调用监听器
return this.listener.call(this.target);
return this.listener.apply(this.target, arguments);
}
}
function _onceWrap(target, type, listener) {
const state = { fired: false, wrapFn: undefined, target, type, listener };
const wrapped = onceWrapper.bind(state);
wrapped.listener = listener; // 挂载原始监听器,一同传递给 removeListener 方法
state.wrapFn = wrapped; // 挂载包裹后的监听器,用于移除
return wrapped;
}
EventEmitter.prototype.once = function once(type, listener) {
checkListener(listener);
this.on(type, _onceWrap(this, type, listener)); // 通过 once 监听的事件获取监听器的方式为 listener.listener
return this;
};
prependOnceListener
添加单次监听器到指定事件的监听器数组开头
返回实例的引用,以便可以链式调用
EventEmitter.prototype.prependOnceListener = function prependOnceListener(type, listener) {
checkListener(listener);
this.prependListener(type, _onceWrap(this, type, listener));
return this;
};
移除
off
同 removeListener
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
removeListener
从指定事件的监听器数组中移除指定的监听器
在事件触发之后,最后一个监听器执行完成之前, 移除监听器不会影响已触发的监听器执行
返回实例的引用,以便可以链式调用
EventEmitter.prototype.removeListener = function removeListener(type, listener) {
checkListener(listener);
const events = this._events;
if (events === undefined) // 未初始化
return this;
const list = events[type];
if (list === undefined)
return this;
// 事件只有一个 listener,则会发生 list === listener( once 注册的listener 为 list.listener)
if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0) // event实例只有一个监听事件
this._events = ObjectCreate(null);
else { // event实例有多个监听事件
delete events[type]; // 先移除后触发
if (events.removeListener) // 如果监听了 `removeListener` 事件,则触发,类似钩子
this.emit('removeListener', type, list.listener || listener);
}
} else if (typeof list !== 'function') { // events[type] 只可能是函数或数组类型,这里判断非函数,则为数组类型?
let position = -1; // 利用 `-1` 这个标志
for (let i = list.length - 1; i >= 0; i--) {
if (list[i] === listener || list[i].listener === listener) {
position = i;
break;
}
}
if (position < 0)
return this;
if (position === 0)
list.shift();
else {
if (spliceOne === undefined)
spliceOne = require('internal/util').spliceOne; // 类似 Array#splice(),但是速度较快
spliceOne(list, position);
}
if (list.length === 1)
events[type] = list[0]; // 单个listener不用数组
if (events.removeListener !== undefined)
this.emit('removeListener', type, listener);
}
return this;
};
removeAllListeners
移除所有监听器或指定事件的所有监听器
返回实例的引用,以便可以链式调用
EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
const events = this._events;
if (events === undefined) // 未初始化
return this;
// 逻辑1: 没有注册 removeListener 事件
// Not listening for removeListener, no need to emit
if (events.removeListener === undefined) {
if (arguments.length === 0) { // 等价于没传参数,type === undefined,删除所有监听事件
this._events = ObjectCreate(null);
this._eventsCount = 0;
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0) // event实例只有一个监听事件
this._events = ObjectCreate(null);
else
delete events[type];
}
return this;
}
// 逻辑2: 注册了 removeListener 事件
// Emit removeListener for all listeners on all events
if (arguments.length === 0) { // 删除所有监听事件
for (const key of ReflectOwnKeys(events)) { // 较 Object#keys(),Reflect#ownKeys() 包含 Symbol 值的属性名
if (key === 'removeListener') continue; // 把其他事件删除后,再删除。如果先删除,后面的事件就走逻辑1了
this.removeAllListeners(key); // 递归,走逻辑3,然后返回
}
this.removeAllListeners('removeListener'); // 递归,走逻辑3,然后返回
this._events = ObjectCreate(null);
this._eventsCount = 0;
return this;
}
// 逻辑3: 真正删除事件监听器
const listeners = events[type];
if (typeof listeners === 'function') {
this.removeListener(type, listeners);
} else if (listeners !== undefined) {
// LIFO order
for (let i = listeners.length - 1; i >= 0; i--) {
this.removeListener(type, listeners[i]);
}
}
return this;
};
修改
setMaxListeners
修改当前 EventEmitter
实例单个事件最大监听器个数。设为 Infinity
(或 0
)表示不限制监听器的数量
返回实例的引用,以便可以链式调用
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n);
}
this._maxListeners = n;
return this;
};
getMaxListeners
返回当前EventEmitter
实例单个事件最大监听器个数
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
return _getMaxListeners(this); // 把 this(实例)传进去
};
function _getMaxListeners(that) {
if (that._maxListeners === undefined)
return EventEmitter.defaultMaxListeners;
return that._maxListeners;
}
获取
listeners
返回一个数组,包含指定事件的所有监听器(拆包之后)
function _listeners(target, type, unwrap) {
const events = target._events;
if (events === undefined)
return [];
const evlistener = events[type];
if (evlistener === undefined)
return [];
if (typeof evlistener === 'function')
return unwrap ? [evlistener.listener || evlistener] : [evlistener];
return unwrap ?
unwrapListeners(evlistener) : arrayClone(evlistener);
}
EventEmitter.prototype.listeners = function listeners(type) {
return _listeners(this, type, true);
};
rawListeners
返回一个数组,包含指定事件的所有(原始)监听器
如果是通过 once 注册的监听器,则返回的是被包装(wrap)后的监听器
EventEmitter.prototype.rawListeners = function rawListeners(type) {
return _listeners(this, type, false);
};
listenerCount
返回指定事件上注册的监听器个数
EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
const events = this._events;
if (events !== undefined) {
const evlistener = events[type];
if (typeof evlistener === 'function') {
return 1;
} else if (evlistener !== undefined) {
return evlistener.length;
}
}
return 0;
}
eventNames
返回一个数组,包含所有监听事件。元素类型为 String 或 Symbol
EventEmitter.prototype.eventNames = function eventNames() { // 所有监听事件
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
};
触发
emit
触发指定事件并同步调用注册到该事件上的所有监听器
如果事件有监听器,则返回 true
,否则返回 false
EventEmitter.prototype.emit = function emit(type, ...args) {
let doError = (type === 'error'); // error 事件,特殊处理
const events = this._events;
if (events !== undefined) { // 有监听事件
if (doError && events[kErrorMonitor] !== undefined) // 如果是 error 事件,且用户添加了 kErrorMonitor 监听器
this.emit(kErrorMonitor, ...args);
doError = (doError && events.error === undefined); // 如果是 error 事件,且没有对应的 handler
} else if (!doError) // events === undefined && !doError => 未初始化且不是 error 事件,则返回 false
return false;
// 触发 error 事件且没有handler,抛出异常(参数中的error实例或自己生成的error)
// If there is no 'error' event listener then throw.
if (doError) {
// 1. 有监听事件 && 触发error事件 && 没有error事件的 handler
// 2. 未初始化(肯定也没有error事件的 handler) && 触发error事件
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) { // 第一个入参是 Error 实例
try { // 给 er 添加当前栈信息(emit部分)
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
value: enhanceStackTrace.bind(this, er, capture),
configurable: true
});
} catch {}
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}
let stringifiedEr;
const { inspect } = require('internal/util/inspect');
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}
// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
throw err; // Unhandled 'error' event
}
const handler = events[type];
// 如果是 error 事件,至此一定有handler,如果是其他类型事件,仍然需要判断handler的存在性
if (handler === undefined)
return false;
// 单个handler是函数,多个handler是数组
if (typeof handler === 'function') {
const result = handler.apply(this, args); // 参见 https://github.com/nodejs/node/pull/38248
// We check if result is undefined first because that
// is the most common case so we do not pay any perf
// penalty
if (result !== undefined && result !== null) {
addCatch(this, result, type, args); // 如果是handler是异步函数,捕获其rejection异常
}
} else {
const len = handler.length; // 确定监听器的个数,避免监听器中再监听同一个事件造成死循环
const listeners = arrayClone(handler); // 克隆handler数组,防止在事件触发后,已注册监听器的变动
for (let i = 0; i < len; ++i) {
const result = listeners[i].apply(this, args); // 同步依次调用
// We check if result is undefined first because that
// is the most common case so we do not pay any perf
// penalty.
// This code is duplicated because extracting it away
// would make it non-inlineable.
if (result !== undefined && result !== null) {
addCatch(this, result, type, args);
}
}
}
return true;
};
function addCatch(that, promise, type, args) {
if (!that[kCapture]) {
return;
}
// 符合 Promises/A+ 规范的 promise 的属性 then 可能具有 get 方法,二次调用可能会报错,因此采用call方式
// 因此下面采用的是 call 方式调用
// Handle Promises/A+ spec, then could be a getter
// that throws on second use.
try {
const then = promise.then;
if (typeof then === 'function') {
then.call(promise, undefined, function(err) {
// The callback is called with nextTick to avoid a follow-up // follow-up rejection ?
// rejection from this promise.
process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args);
});
}
} catch (err) {
that.emit('error', err);
}
}
function emitUnhandledRejectionOrErr(ee, err, type, args) {
if (typeof ee[kRejection] === 'function') { // 实例(用户)自己实现了 kRejection 函数
ee[kRejection](err, type, ...args);
} else {
// We have to disable the capture rejections mechanism, otherwise
// we might end up in an infinite loop.
// 防止 error 事件 handler 也抛出 error,导致死循环。
// 先保存之前的值,再关闭,如果程序未「退出」再恢复之前的值
const prev = ee[kCapture];
// If the error handler throws, it is not catcheable and it
// will end up in 'uncaughtException'. We restore the previous
// value of kCapture in case the uncaughtException is present
// and the exception is handled.
// 如果没有自定义 rejected promise 的处理函数,则走 error 事件的处理函数
// 如果 error 事件 handler 也抛出 error,则不会被捕获,nodejs 会触发 uncaughtException 事件并「退出」
// 如果用户处理了 uncaughtException 事件(通过 process#on() 处理),则恢复 kRejection 的值
try {
ee[kCapture] = false;
ee.emit('error', err);
} finally {
ee[kCapture] = prev;
}
}
}
其他
primordials
primordials对象用来保证内建模块可以访问真正的不受用户干扰的全局变量
spliceOne
更高效的移除数组中的元素
function spliceOne(list, index) {
for (; index + 1 < list.length; index++)
list[index] = list[index + 1]; // 从被移除元素开始,将后面的元素值依次向前复制
list.pop(); // 移除最后一个元素
}
创建实例未初始化
'use strict';
const { EventEmitter, captureRejectionSymbol } = require('events');
function MyEventEmitter() {
// EventEmitter.call(this);
}
MyEventEmitter.prototype = EventEmitter.prototype;
MyEventEmitter.prototype.constructor = MyEventEmitter
const ee = new MyEventEmitter();
// 上述创建 EventEmitter 实例的方式并没有调用 EventEmitter.init() 初始化,因此实例属性值都是 undefined
console.log('ee._events = ', ee._events);
console.log('ee._eventsCount = ', ee._eventsCount);
ee.on('test', () => { console.log('tttttttt') });
ee.emit('test');
console.log('ee.eventNames = ',ee.eventNames());
参考
通过源码解析 Node.js 中 events 模块里的优化小细节
Node.js-Events 模块总结与源码解析的更多相关文章
- vue系列---Mustache.js模板引擎介绍及源码解析(十)
mustache.js(3.0.0版本) 是一个javascript前端模板引擎.官方文档(https://github.com/janl/mustache.js) 根据官方介绍:Mustache可以 ...
- Android FM 模块学习之四 源码解析(1)
Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE MicrosoftInternetExplorer4 前一章我们了解了FM手动调频,接下 ...
- Android FM模块学习之四源码解析(一)
转自:http://blog.csdn.net/tfslovexizi/article/details/41516149?utm_source=tuicool&utm_medium=refer ...
- Celery 源码解析六:Events 的实现
在 Celery 中,除了远程控制之外,还有一个元素可以让我们对分布式中的任务的状态有所掌控,而且从实际意义上来说,这个元素对 Celery 更为重要,这就是在本文中将要说到的 Event. 在 Ce ...
- [源码解析] 并行分布式框架 Celery 之 worker 启动 (1)
[源码解析] 并行分布式框架 Celery 之 worker 启动 (1) 目录 [源码解析] 并行分布式框架 Celery 之 worker 启动 (1) 0x00 摘要 0x01 Celery的架 ...
- [源码解析] 并行分布式框架 Celery 之 worker 启动 (2)
[源码解析] 并行分布式框架 Celery 之 worker 启动 (2) 目录 [源码解析] 并行分布式框架 Celery 之 worker 启动 (2) 0x00 摘要 0x01 前文回顾 0x2 ...
- Celery 源码解析五: 远程控制管理
今天要聊的话题可能被大家关注得不过,但是对于 Celery 来说确实很有用的功能,曾经我在工作中遇到这类情况,就是我们将所有的任务都放在同一个队列里面,然后有一天突然某个同学的代码写得不对,导致大量的 ...
- Celery 源码解析三: Task 对象的实现
Task 的实现在 Celery 中你会发现有两处,一处位于 celery/app/task.py,这是第一个:第二个位于 celery/task/base.py 中,这是第二个.他们之间是有关系的, ...
- [源码解析] 分布式任务队列 Celery 之启动 Consumer
[源码解析] 分布式任务队列 Celery 之启动 Consumer 目录 [源码解析] 分布式任务队列 Celery 之启动 Consumer 0x00 摘要 0x01 综述 1.1 kombu.c ...
随机推荐
- github文件快速下载
目录 一,提升加载速度 二,提升下载速度 只是想快速下载文件的直接看第二部分. github加载速度慢究其原因还是伟大的墙的存在.我们需要赞美墙,但就算墙很伟大,问题还是要解决的. 有问题就解决问题, ...
- Android学习之CoordinatorLayout+AppBarLayout
•AppBarLayout 简介 AppbarLayout 是一种支持响应滚动手势的 app bar 布局: 基本使用 新建一个项目,命名为 TestAppBarLayout: 修改 activity ...
- js数组reduce解析及使用示例
reduce() 简单说,reduce()可以对数组中的每个元素执行一个由您提供的reducer函数(升序执行),函数的返回值分配给累计器,该返回值在数组的每个迭代中被记住,并最后将其结果汇总为单个返 ...
- 面试准备——计算机网络(http)
一.各种协议与HTTP协议之间的关系 二.URI(统一资源标识符) URI用字符串标识某一互联网资源. URI的格式: 协议方案名:指定访问资源时使用的协议类型. 登录信息(认证):可选,指定用户名和 ...
- 【转载】C# get 与set的一些说明
转载 在面向对象编程(OOP)中,是不允许外界直接对类的成员变量直接访问的,既然不能访问,那定义这些成员变量还有什么意义呢?所以C#中就要用set和get方法来访问私有成员变量,它们相当于外界访问对象 ...
- CSS完成视差滚动效果
一.是什么 视差滚动(Parallax Scrolling)是指多层背景以不同的速度移动,形成立体的运动效果,带来非常出色的视觉体验 我们可以把网页解刨成:背景层.内容层.悬浮层 当滚动鼠标滑轮的时候 ...
- 模拟退火算法(1)Python 实现
1.模拟退火算法 模拟退火算法借鉴了统计物理学的思想,是一种简单.通用的启发式优化算法,并在理论上具有概率性全局优化性能,因而在科研和工程中得到了广泛的应用. 退火是金属从熔融状态缓慢冷却.最终达到能 ...
- OO Unit4总结 & 结课总结
OO Unit4总结 & 结课总结 OO课Unit4 UML解析应用技术回顾 BUAA.1823.邓新宇 2020/6/19 总结本单元三次作业的架构设计 本单元的架构设计主要是两方面. 一方 ...
- Android埋点技术概览
注:本文同步发布于微信公众号:stringwu的互联网杂谈Android无埋点技术概览 本文是Android无埋点系列的开篇---埋点技术概览 1 背景 埋点是数据产品经理(分析师)基于业务需求,对用 ...
- 功能:@Vaild注解使用及扩展
@Vaild注解使用及扩展 一.@Vaild注解介绍 使用@Vaild注解可以简化入参的校验,配合统一异常实现简单快捷的入参校验,具体使用参照以下 二.@Vaild具体使用 1.引入jar包 如果你是 ...