node.js事件驅動_了解Node.js事件驅動架構

node.js事件驅動

by Samer Buna

通過Samer Buna

了解Node.js事件驅動架構 (Understanding Node.js Event-Driven Architecture)

Update: This article is now part of my book “Node.js Beyond The Basics”.

更新:這篇文章現在是我的書《超越基礎的Node.js》的一部分。

Read the updated version of this content and more about Node at jscomplete.com/node-beyond-basics.

jscomplete.com/node-beyond-basics中閱讀此內容的更新版本以及有關Node的更多信息。

Most of Node’s objects — like HTTP requests, responses, and streams — implement the EventEmitter module so they can provide a way to emit and listen to events.

Node的大多數對象(例如HTTP請求,響應和流)都實現EventEmitter模塊,因此它們可以提供一種發出和偵聽事件的方式。

The simplest form of the event-driven nature is the callback style of some of the popular Node.js functions — for example, fs.readFile. In this analogy, the event will be fired once (when Node is ready to call the callback) and the callback acts as the event handler.

事件驅動性質的最簡單形式是一些流行的Node.js函數的回調樣式,例如fs.readFile 。 以此類推,事件將被觸發一次(當Node準備好調用回調時),并且回調充當事件處理程序。

Let’s explore this basic form first.

讓我們首先探討這種基本形式。

準備好后給我打電話,Node! (Call me when you’re ready, Node!)

The original way Node handled asynchronous events was with callback. This was a long time ago, before JavaScript had native promises support and the async/await feature.

Node處理異步事件的原始方式是使用回調。 這是很久以前的事情,當時JavaScript還沒有原生的Promise支持和異步/等待功能。

Callbacks are basically just functions that you pass to other functions. This is possible in JavaScript because functions are first class objects.

回調基本上只是傳遞給其他函數的函數。 在JavaScript中這是可能的,因為函數是第一類對象。

It’s important to understand that callbacks do not indicate an asynchronous call in the code. A function can call the callback both synchronously and asynchronously.

重要的是要了解回調不會在代碼中指示異步調用。 函數可以同步和異步調用回調。

For example, here’s a host function fileSize that accepts a callback function cb and can invoke that callback function both synchronously and asynchronously based on a condition:

例如,下面是一個宿主函數fileSize ,它接受一個回調函數cb并可以根據條件同步和異步調用該回調函數:

function fileSize (fileName, cb) {if (typeof fileName !== 'string') {return cb(new TypeError('argument should be string')); // Sync}fs.stat(fileName, (err, stats) => {if (err) { return cb(err); } // Asynccb(null, stats.size); // Async});
}

Note that this is a bad practice that leads to unexpected errors. Design host functions to consume callback either always synchronously or always asynchronously.

請注意,這是一種不良做法,會導致意外錯誤。 設計主機函數以始終同步或始終異步使用回調。

Let’s explore a simple example of a typical asynchronous Node function that’s written with a callback style:

讓我們研究一下用回調樣式編寫的典型異步Node函數的簡單示例:

const readFileAsArray = function(file, cb) {fs.readFile(file, function(err, data) {if (err) {return cb(err);}const lines = data.toString().trim().split('\n');cb(null, lines);});
};

readFileAsArray takes a file path and a callback function. It reads the file content, splits it into an array of lines, and calls the callback function with that array.

readFileAsArray采用文件路徑和回調函數。 它讀取文件內容,將其拆分為一個行數組,然后使用該數組調用回調函數。

Here’s an example use for it. Assuming that we have the file numbers.txt in the same directory with content like this:

這是一個示例用法。 假設我們在同一目錄中有文件numbers.txt ,其內容如下:

10
11
12
13
14
15

If we have a task to count the odd numbers in that file, we can use readFileAsArray to simplify the code:

如果我們有一個任務來計算該文件中的奇數,則可以使用readFileAsArray簡化代碼:

readFileAsArray('./numbers.txt', (err, lines) => {if (err) throw err;const numbers = lines.map(Number);const oddNumbers = numbers.filter(n => n%2 === 1);console.log('Odd numbers count:', oddNumbers.length);
});

The code reads the numbers content into an array of strings, parses them as numbers, and counts the odd ones.

該代碼將數字內容讀入字符串數組,將其解析為數字,然后對奇數進行計數。

Node’s callback style is used purely here. The callback has an error-first argument err that’s nullable and we pass the callback as the last argument for the host function. You should always do that in your functions because users will probably assume that. Make the host function receive the callback as its last argument and make the callback expect an error object as its first argument.

純粹在這里使用Node的回調樣式。 回調函數的錯誤優先參數err可為空,我們將回調函數作為主機函數的最后一個參數傳遞。 您應該始終在函數中執行此操作,因為用戶可能會假設這樣做。 使主機函數將回調作為其最后一個參數接收,并使回調將錯誤對象作為其第一個參數。

替代回調的現代JavaScript (The modern JavaScript alternative to Callbacks)

In modern JavaScript, we have promise objects. Promises can be an alternative to callbacks for asynchronous APIs. Instead of passing a callback as an argument and handling the error in the same place, a promise object allows us to handle success and error cases separately and it also allows us to chain multiple asynchronous calls instead of nesting them.

在現代JavaScript中,我們有promise對象。 承諾可以替代異步API的回調。 Promise對象無需將回調作為參數傳遞并在同一位置處理錯誤,而是使我們可以分別處理成功和錯誤情況,還可以鏈接多個異步調用而不是嵌套它們。

If the readFileAsArray function supports promises, we can use it as follows:

如果readFileAsArray函數支持promise,則可以按以下方式使用它:

readFileAsArray('./numbers.txt').then(lines => {const numbers = lines.map(Number);const oddNumbers = numbers.filter(n => n%2 === 1);console.log('Odd numbers count:', oddNumbers.length);}).catch(console.error);

Instead of passing in a callback function, we called a .then function on the return value of the host function. This .then function usually gives us access to the same lines array that we get in the callback version, and we can do our processing on it as before. To handle errors, we add a .catch call on the result and that gives us access to an error when it happens.

我們沒有傳遞回調函數,而是在宿主函數的返回值上調用了.then函數。 這個.then函數通常使我們能夠訪問與回調版本中相同的lines數組,并且我們可以像以前一樣對其進行處理。 為了處理錯誤,我們在結果上添加了.catch調用,使我們可以在錯誤發生時對其進行訪問。

Making the host function support a promise interface is easier in modern JavaScript thanks to the new Promise object. Here’s the readFileAsArray function modified to support a promise interface in addition to the callback interface it already supports:

由于有了新的Promise對象,在現代JavaScript中使宿主函數支持Promise接口更加容易。 這是已修改的readFileAsArray函數,除了已經支持的回調接口之外,還支持Promise接口:

const readFileAsArray = function(file, cb = () => {}) {return new Promise((resolve, reject) => {fs.readFile(file, function(err, data) {if (err) {reject(err);return cb(err);}const lines = data.toString().trim().split('\n');resolve(lines);cb(null, lines);});});
};

So we make the function return a Promise object, which wraps the fs.readFile async call. The promise object exposes two arguments, a resolve function and a reject function.

因此,我們使函數返回一個Promise對象,該對象包裝了fs.readFile異步調用。 Promise對象公開兩個參數,一個resolve函數和一個reject函數。

Whenever we want to invoke the callback with an error we use the promise reject function as well, and whenever we want to invoke the callback with data we use the promise resolve function as well.

每當我們想用錯誤調用回調函數時,我們也會使用promise reject函數,每當我們想對數據調用回調函數時,我們也將使用promise resolve函數。

The only other thing we needed to do in this case is to have a default value for this callback argument in case the code is being used with the promise interface. We can use a simple, default empty function in the argument for that case: () => {}.

在這種情況下,我們唯一需要做的另一件事就是為該回調參數設置一個默認值,以防代碼與promise接口一起使用。 在這種情況下,我們可以在參數中使用一個簡單的默認空函數: () => {}。

使用async / await消費諾言 (Consuming promises with async/await)

Adding a promise interface makes your code a lot easier to work with when there is a need to loop over an async function. With callbacks, things become messy.

當需要循環異步功能時,添加一個promise接口會使您的代碼更容易使用。 使用回調,事情變得混亂。

Promises improve that a little bit, and function generators improve on that a little bit more. This said, a more recent alternative to working with async code is to use the async function, which allows us to treat async code as if it was synchronous, making it a lot more readable overall.

承諾會有所改善,函數生成器會有所改善。 這就是說,使用異步代碼的另一種替代方法是使用async函數,該函數使我們可以將異步代碼視為同步代碼,從而使整體可讀性更高。

Here’s how we can consume the readFileAsArray function with async/await:

這是我們如何在async / await中使用readFileAsArray函數:

async function countOdd () {try {const lines = await readFileAsArray('./numbers');const numbers = lines.map(Number);const oddCount = numbers.filter(n => n%2 === 1).length;console.log('Odd numbers count:', oddCount);} catch(err) {console.error(err);}
}
countOdd();

We first create an async function, which is just a normal function with the word async before it. Inside the async function, we call the readFileAsArray function as if it returns the lines variable, and to make that work, we use the keyword await. After that, we continue the code as if the readFileAsArray call was synchronous.

我們首先創建一個異步函數,這只是一個普通函數,其前面帶有單詞async 。 在async函數內部,我們調用readFileAsArray函數,就好像它返回lines變量一樣,為了使其正常工作,我們使用關鍵字await 。 之后,我們繼續執行代碼,就像readFileAsArray調用是同步的一樣。

To get things to run, we execute the async function. This is very simple and more readable. To work with errors, we need to wrap the async call in a try/catch statement.

為了使事情運行,我們執行異步功能。 這非常簡單并且可讀性強。 要處理錯誤,我們需要將異步調用包裝在try / catch語句中。

With this async/await feature, we did not have to use any special API (like .then and .catch). We just labeled functions differently and used pure JavaScript for the code.

使用此異步/等待功能,我們不必使用任何特殊的API(例如.then和.catch)。 我們只是對函數進行了不同的標記,并對代碼使用了純JavaScript。

We can use the async/await feature with any function that supports a promise interface. However, we can’t use it with callback-style async functions (like setTimeout for example).

我們可以將async / await功能與任何支持promise接口的功能一起使用。 但是,我們不能將其與回調樣式的異步函數(例如setTimeout)一起使用。

EventEmitter模塊 (The EventEmitter Module)

The EventEmitter is a module that facilitates communication between objects in Node. EventEmitter is at the core of Node asynchronous event-driven architecture. Many of Node’s built-in modules inherit from EventEmitter.

EventEmitter是一個模塊,可促進Node中對象之間的通信。 EventEmitter是Node異步事件驅動的體系結構的核心。 Node的許多內置模塊都繼承自EventEmitter。

The concept is simple: emitter objects emit named events that cause previously registered listeners to be called. So, an emitter object basically has two main features:

這個概念很簡單:發射器對象發出命名事件,這些事件導致先前注冊的偵聽器被調用。 因此,發射器對象基本上具有兩個主要功能:

  • Emitting name events.

    發出名稱事件。
  • Registering and unregistering listener functions.

    注冊和注銷偵聽器功能。

To work with the EventEmitter, we just create a class that extends EventEmitter.

要使用EventEmitter,我們只需創建一個擴展EventEmitter的類。

class MyEmitter extends EventEmitter {}

Emitter objects are what we instantiate from the EventEmitter-based classes:

發射器對象是我們從基于EventEmitter的類中實例化的:

const myEmitter = new MyEmitter();

At any point in the lifecycle of those emitter objects, we can use the emit function to emit any named event we want.

在這些發射器對象的生命周期中的任何時候,我們都可以使用發出函數來發出我們想要的任何命名事件。

myEmitter.emit('something-happened');

Emitting an event is the signal that some condition has occurred. This condition is usually about a state change in the emitting object.

發出事件是已發生某種情況的信號。 該條件通常與發射物體的狀態變化有關。

We can add listener functions using the on method, and those listener functions will be executed every time the emitter object emits their associated name event.

我們可以使用on方法添加偵聽器函數,這些偵聽器函數將在每次發射器對象發出其關聯的名稱事件時執行。

事件!==異步 (Events !== Asynchrony)

Let’s take a look at an example:

讓我們看一個例子:

const EventEmitter = require('events');class WithLog extends EventEmitter {execute(taskFunc) {console.log('Before executing');this.emit('begin');taskFunc();this.emit('end');console.log('After executing');}
}const withLog = new WithLog();withLog.on('begin', () => console.log('About to execute'));
withLog.on('end', () => console.log('Done with execute'));withLog.execute(() => console.log('*** Executing task ***'));

Class WithLog is an event emitter. It defines one instance function execute. This execute function receives one argument, a task function, and wraps its execution with log statements. It fires events before and after the execution.

WithLog類是事件發射器。 它定義了一個實例函數execute 。 該execute函數接收一個參數,一個task函數,并用log語句包裝其執行。 它在執行前后觸發事件。

To see the sequence of what will happen here, we register listeners on both named events and finally execute a sample task to trigger things.

要查看此處發生的順序,我們在兩個命名事件上注冊偵聽器,最后執行一個示例任務來觸發事件。

Here’s the output of that:

這是輸出:

Before executing
About to execute
*** Executing task ***
Done with execute
After executing

What I want you to notice about the output above is that it all happens synchronously. There is nothing asynchronous about this code.

我希望您注意到上面的輸出,所有操作都是同步發生的。 此代碼沒有異步的。

  • We get the “Before executing” line first.

    我們首先得到“執行之前”這一行。
  • The begin named event then causes the “About to execute” line.

    然后, begin命名事件將導致“關于要執行”行。

  • The actual execution line then outputs the “*** Executing task ***” line.

    然后,實際執行行將輸出“ ***執行任務***”行。
  • The end named event then causes the “Done with execute” line

    然后,以命名end事件導致“完成并執行”行

  • We get the “After executing” line last.

    我們最后得到“執行后”行。

Just like plain-old callbacks, do not assume that events mean synchronous or asynchronous code.

就像普通的回調一樣,不要假定事件表示同步或異步代碼。

This is important, because if we pass an asynchronous taskFunc to execute, the events emitted will no longer be accurate.

這很重要,因為如果我們傳遞異步taskFuncexecute ,則發出的事件將不再準確。

We can simulate the case with a setImmediate call:

我們可以使用setImmediate調用來模擬這種情況:

// ...withLog.execute(() => {setImmediate(() => {console.log('*** Executing task ***')});
});

Now the output would be:

現在的輸出將是:

Before executing
About to execute
Done with execute
After executing
*** Executing task ***

This is wrong. The lines after the async call, which were caused the “Done with execute” and “After executing” calls, are not accurate any more.

錯了 異步調用之后的行(導致“執行完成”和“執行后”調用)不再準確。

To emit an event after an asynchronous function is done, we’ll need to combine callbacks (or promises) with this event-based communication. The example below demonstrates that.

為了在異步函數完成后發出事件,我們需要將回調(或promise)與基于事件的通信結合起來。 下面的示例演示了這一點。

One benefit of using events instead of regular callbacks is that we can react to the same signal multiple times by defining multiple listeners. To accomplish the same with callbacks, we have to write more logic inside the single available callback. Events are a great way for applications to allow multiple external plugins to build functionality on top of the application’s core. You can think of them as hook points to allow for customizing the story around a state change.

使用事件而不是常規回調的好處之一是,我們可以通過定義多個偵聽器來對同一信號進行多次響應。 為實現回調,我們必須在單個可用回調內編寫更多邏輯。 事件是應用程序允許多個外部插件在應用程序核心之上構建功能的好方法。 您可以將它們視為掛鉤點,以允許圍繞狀態更改自定義故事。

異步事件 (Asynchronous Events)

Let’s convert the synchronous sample example into something asynchronous and a little bit more useful.

讓我們將同步示例示例轉換為異步示例,然后再使用一些示例。

const fs = require('fs');
const EventEmitter = require('events');class WithTime extends EventEmitter {execute(asyncFunc, ...args) {this.emit('begin');console.time('execute');asyncFunc(...args, (err, data) => {if (err) {return this.emit('error', err);}this.emit('data', data);console.timeEnd('execute');this.emit('end');});}
}const withTime = new WithTime();withTime.on('begin', () => console.log('About to execute'));
withTime.on('end', () => console.log('Done with execute'));withTime.execute(fs.readFile, __filename);

The WithTime class executes an asyncFunc and reports the time that’s taken by that asyncFunc using console.time and console.timeEnd calls. It emits the right sequence of events before and after the execution. And also emits error/data events to work with the usual signals of asynchronous calls.

WithTime類執行的asyncFunc和報告,是采取由時間asyncFunc使用console.timeconsole.timeEnd電話。 它在執行前后發出正確的事件序列。 并且還會發出錯誤/數據事件以與異步調用的通常信號一起工作。

We test a withTime emitter by passing it an fs.readFile call, which is an asynchronous function. Instead of handling file data with a callback, we can now listen to the data event.

我們通過傳遞一個fs.readFile調用來測試withTime發射器,這是一個異步函數。 現在,我們可以偵聽數據事件,而不是使用回調處理文件數據。

When we execute this code , we get the right sequence of events, as expected, and we get a reported time for the execution, which is helpful:

當我們執行此代碼時,我們將按預期獲得正確的事件序列,并獲得執行的報告時間,這很有幫助:

About to execute
execute: 4.507ms
Done with execute

Note how we needed to combine a callback with an event emitter to accomplish that. If the asynFunc supported promises as well, we could use the async/await feature to do the same:

請注意,我們需要如何結合使用回調和事件發射器來實現這一點。 如果asynFunc支持promise,我們可以使用async / await功能執行相同的操作:

class WithTime extends EventEmitter {async execute(asyncFunc, ...args) {this.emit('begin');try {console.time('execute');const data = await asyncFunc(...args);this.emit('data', data);console.timeEnd('execute');this.emit('end');} catch(err) {this.emit('error', err);}}
}

I don’t know about you, but this is much more readable to me than the callback-based code or any .then/.catch lines. The async/await feature brings us as close as possible to the JavaScript language itself, which I think is a big win.

我不了解您,但是比起基于回調的代碼或任何.then / .catch行,這對我而言更具可讀性。 異步/等待功能使我們盡可能接近JavaScript語言本身,我認為這是一個巨大的勝利。

事件參數和錯誤 (Events Arguments and Errors)

In the previous example, there were two events that were emitted with extra arguments.

在前面的示例中,有兩個帶有額外參數的事件。

The error event is emitted with an error object.

錯誤事件與錯誤對象一起發出。

this.emit('error', err);

The data event is emitted with a data object.

數據事件與數據對象一起發出。

this.emit('data', data);

We can use as many arguments as we need after the named event, and all these arguments will be available inside the listener functions we register for these named events.

在命名事件之后,我們可以根據需要使用任意數量的參數,并且所有這些參數都將在我們為這些命名事件注冊的偵聽器函數中可用。

For example, to work with the data event, the listener function that we register will get access to the data argument that was passed to the emitted event and that data object is exactly what the asyncFunc exposes.

例如,要處理數據事件,我們注冊的偵聽器函數將可以訪問傳遞給發出的事件的數據參數,而該數據對象正是asyncFunc公開的。

withTime.on('data', (data) => {// do something with data
});

The error event is usually a special one. In our callback-based example, if we don’t handle the error event with a listener, the node process will actually exit.

error事件通常是一個特殊的事件。 在基于回調的示例中,如果不使用偵聽器處理錯誤事件,則節點進程實際上將退出。

To demonstrate that, make another call to the execute method with a bad argument:

為了證明這一點,請使用錯誤的參數再次調用execute方法:

class WithTime extends EventEmitter {execute(asyncFunc, ...args) {console.time('execute');asyncFunc(...args, (err, data) => {if (err) {return this.emit('error', err); // Not Handled}console.timeEnd('execute');});}
}const withTime = new WithTime();withTime.execute(fs.readFile, ''); // BAD CALL
withTime.execute(fs.readFile, __filename);

The first execute call above will trigger an error. The node process is going to crash and exit:

上面的第一個execute調用將觸發錯誤。 節點進程將崩潰并退出:

events.js:163throw er; // Unhandled 'error' event^
Error: ENOENT: no such file or directory, open ''

The second execute call will be affected by this crash and will potentially not get executed at all.

第二次執行調用將受到此崩潰的影響,并且可能根本無法執行。

If we register a listener for the special error event, the behavior of the node process will change. For example:

如果我們為特殊error事件注冊一個偵聽器,則節點進程的行為將改變。 例如:

withTime.on('error', (err) => {// do something with err, for example log it somewhereconsole.log(err)
});

If we do the above, the error from the first execute call will be reported but the node process will not crash and exit. The other execute call will finish normally:

如果執行上述操作,將報告來自第一個執行調用的錯誤,但節點進程不會崩潰并退出。 另一個執行調用將正常完成:

{ Error: ENOENT: no such file or directory, open '' errno: -2, code: 'ENOENT', syscall: 'open', path: '' }
execute: 4.276ms

Note that Node currently behaves differently with promise-based functions and just outputs a warning, but that will eventually change:

請注意,Node當前與基于promise的功能的行為有所不同,只是輸出警告,但最終會改變:

UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: ENOENT: no such file or directory, open ''
DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

The other way to handle exceptions from emitted errors is to register a listener for the global uncaughtException process event. However, catching errors globally with that event is a bad idea.

處理來自發出的錯誤的異常的另一種方法是為全局uncaughtException流程事件注冊一個偵聽器。 但是,在該事件中全局捕獲錯誤不是一個好主意。

The standard advice about uncaughtException is to avoid using it, but if you must do (say to report what happened or do cleanups), you should just let the process exit anyway:

關于uncaughtException的標準建議是避免使用它,但是,如果必須這樣做(例如報告所發生的事情或進行清理),則無論如何都要讓該過程退出:

process.on('uncaughtException', (err) => {// something went unhandled.// Do any cleanup and exit anyway!console.error(err); // don't do just that.// FORCE exit the process too.process.exit(1);
});

However, imagine that multiple error events happen at the exact same time. This means the uncaughtException listener above will be triggered multiple times, which might be a problem for some cleanup code. An example of this is when multiple calls are made to a database shutdown action.

但是,想象一下,多個錯誤事件恰好同時發生。 這意味著上面的uncaughtException偵聽器將被多次觸發,這對于某些清理代碼可能是一個問題。 例如,當多次調用數據庫關閉操作時。

The EventEmitter module exposes a once method. This method signals to invoke the listener just once, not every time it happens. So, this is a practical use case to use with the uncaughtException because with the first uncaught exception we’ll start doing the cleanup and we know that we’re going to exit the process anyway.

EventEmitter模塊公開once方法。 此方法發出信號僅一次調用偵聽器,而不是每次都調用。 因此,這是一個與uncaughtException一起使用的實際用例,因為對于第一個未捕獲的異常,我們將開始進行清理,并且我們知道無論如何都將退出該過程。

聽眾順序 (Order of Listeners)

If we register multiple listeners for the same event, the invocation of those listeners will be in order. The first listener that we register is the first listener that gets invoked.

如果我們為同一事件注冊多個偵聽器,則這些偵聽器的調用將是有序的。 我們注冊的第一個偵聽器是被調用的第一個偵聽器。

// ?????
withTime.on('data', (data) => {console.log(`Length: ${data.length}`);
});// ?????
withTime.on('data', (data) => {console.log(`Characters: ${data.toString().length}`);
});withTime.execute(fs.readFile, __filename);

The above code will cause the “Length” line to be logged before the “Characters” line, because that’s the order in which we defined those listeners.

上面的代碼將導致“長度”行記錄在“字符”行之前,因為這是我們定義這些偵聽器的順序。

If you need to define a new listener, but have that listener invoked first, you can use the prependListener method:

如果您需要定義一個新的偵聽器,但首先要調用該偵聽器,則可以使用prependListener方法:

// ?????
withTime.on('data', (data) => {console.log(`Length: ${data.length}`);
});// ?????
withTime.prependListener('data', (data) => {console.log(`Characters: ${data.toString().length}`);
});withTime.execute(fs.readFile, __filename);

The above will cause the “Characters” line to be logged first.

以上將導致“字符”行被首先記錄。

And finally, if you need to remove a listener, you can use the removeListener method.

最后,如果需要刪除偵聽器,則可以使用removeListener方法。

That’s all I have for this topic. Thanks for reading! Until next time!

這就是我要做的所有事情。 謝謝閱讀! 直到下一次!

Learning React or Node? Checkout my books:

學習React還是Node? 結帳我的書:

  • Learn React.js by Building Games

    通過構建游戲學習React.js

  • Node.js Beyond the Basics

    超越基礎的Node.js

翻譯自: https://www.freecodecamp.org/news/understanding-node-js-event-driven-architecture-223292fcbc2d/

node.js事件驅動

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/395738.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/395738.shtml
英文地址,請注明出處:http://en.pswp.cn/news/395738.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何基于 Notadd 構建 API (Laravel 寫 API)

如何基于 Notadd 構建 API Notadd 底層實現了 passport 機制,有統一的授權管理,主要支持兩種方式進行 API 授權,一個是 client,領一個是 passport,這個在其他文檔中有做詳細的說明。 這里主要說的是,如何基…

mysql 基于集_一種基于記錄集查找特定行的方法_MySQL

問:我的一個表中包含了名為IdValue的單列主鍵。對于給定的IdValue值,我希望找到緊鄰目標值之前和之后的表行(假定結果按IdValue排序)。怎樣才能不使用游標而通過一個基于集合的方法得到需要的結果?答:Transact-SQL是一個基于集合的…

react 交互_如何在React中建立動畫微交互

react 交互Microinteractions guide a user through your application. They reinforce your user experience and provide delight.微交互引導用戶完成您的應用程序。 它們可以增強您的用戶體驗并帶來愉悅感。 You may have seen some of the slick examples of microinterac…

HTTPS與MITM

HTTPS:基于SSL/TSL的HTTP協議 MITM:Man-In-The-Middle中間人攻擊 Https下中間人攻擊的思路: 1 去https化 2 向CA申請相似域名的證書 防范: 睜大雙眼轉載于:https://www.cnblogs.com/the-owl/p/5596254.html

PCB genesis自制孔點 Font字體實現方法

一.先看genesis原有Font字體 在PCB工程CAM加孔點字體要求時,通常我們直接用Geneis軟件給我們提供了2種孔點字體canned_57與canned_67,但此字體可能不能滿足各個工廠個性化需求,比如:孔密度,孔間距,孔形狀分布,如果有一…

Google 最新的 Fuchsia OS【科技訊息摘要】

轉自:http://www.cnblogs.com/pied/p/5771782.html 就是看到篇報道,有點好奇,就去FQ挖了點東西回來。 我似乎已開始就抓到了重點,沒錯,就是 LK 。 LK 是 Travis Geiselbrecht 寫的一個針對 ARM 的嵌入式操作系統&#…

java 03_Java基礎03—流程控制

流程控制參考資料:《Java從入門到精通》/明日科技編著. 4版. 北京:清華大學出版社,2016一、復合語句Java的復合語句由“{”開始,“}”結束,又稱為塊語句。復合語句都是由上至下被執行;復合語句中可以嵌套復…

這三種策略可以幫助女性在科技領域蓬勃發展

by Shubhi Asthana通過Shubhi Asthana 這三種策略可以幫助女性在科技領域蓬勃發展 (These 3 strategies can help women thrive in tech) As someone early on in her career, I’ve attended a few tech talks, conferences, and meetups. One thing I noticed is not many w…

手機衛士09_應用程序四種查看_ListView小標題_進程管理

手機衛士09_應用程序四種查看_ListView小標題_進程管理 1.懸浮窗體的功能實現: 1.1.應用程序的卸載: 包安裝器 packageInstall,包卸載packageruninstall intent.setData(Uri.pare(“package:” 應用程序包名)) 卸載完之后記得更新list集合,更新適配器. 但是不確定用戶是否點了…

pandas:根據行間差值進行數據合并

1. 問題描述 在處理用戶上網數據時,用戶的上網行為數據之間存在時間間隔,按照實際情況,若時間間隔小于閾值(next_access_time_app),則可把這幾條上網行為合并為一條行為數據;若時間間隔大于閾值…

Flask學習 一 基本結構

-from flask import Flaskfrom flask import Flask,render_template-from flask import request-from flask import make_response-from flask import abort-from flask import redirect-# __name__參數決定程序的根目錄app Flask (__name__)-# app.route (/)-# def hello_wor…

java8的路徑_什么是路徑?

# 什么是路徑?文件系統以某種形式的媒體(通常為一個或多個硬盤驅動器)存儲和組織文件,使得它們可以容易地被檢索。目前使用的大多數文件系統將文件存儲在樹形(或分層)結構中。在樹的頂部是一個(或多個)根節點。在根節點下,有文件和目錄(Micro…

為什么toString方法可以用來區分數組和對象?

首先大家都應該知道在javascript中只有是對象都存在toString方法,將調用該方法的值轉換為字符串返回,如下: var arr [1, 2, 3];console.log(arr.toString()); //1,2,3 但對象的toString方法和其他優點不同,其返回的是類似 [objec…

平安 開源 數據庫 實踐_刻意的實踐-成為開源

平安 開源 數據庫 實踐by Anthony Ng由Anthony Ng 刻意的實踐-成為開源 (Deliberate Practice — Becoming an Open Sourcerer) I recently finished reading Cal Newport’s book, So Good They Can’t Ignore You. It’s a quick read, and it introduced me to the concept…

更新Composer依賴報錯處理Fatal error: Declaration of Fxp\Composer\AssetPlugin\Repository\AbstractAssetsRe...

更新Composer依賴報錯處理 Fatal error: Declaration of Fxp\Composer\AssetPlugin\Repository\AbstractAssetsRepository::search() must be compatible with Composer\Repository\RepositoryInterface::search($query, $mode 0, $type NULL) in C:\Users\Arthur\AppData\Ro…

解析su,su -,sudo的區別

2019獨角獸企業重金招聘Python工程師標準>>> 本人以前一直習慣直接使用root,很少使用su,前幾天才發現su與su -命令是有著本質區別的! 大部分Linux發行版的默認賬戶是普通用戶,而更改系統文件或者執行某些命令&#xff…

java 前置通知_spring aop中的前置通知

fixassetServicemyInterceptormyInterceptor2在上面的配置文件中 我配置了兩個interceptor ,這兩個interceptor的invoke方法中的邏輯是一樣的。public Object invoke(MethodInvocation invo) throws Throwable {//自己的橫切邏輯log....invo.proceed()}我想請教的問…

Java并發編程藝術讀書筆記

1、多線程在CPU切換過程中,由于需要保存線程之前狀態和加載新線程狀態,成為上下文切換,上下文切換會造成消耗系統內存。所以,可合理控制線程數量。 如何控制: (1)使用ps -ef|grep appname&#…

您可能不需要翻譯您JavaScript

by Alex Ewerlf由AlexEwerlf 您可能不需要翻譯您JavaScript (You might not need to transpile your JavaScript) Popular guides like YouMightNotNeedJQuery.com and You Don’t Need Lodash/Underscore have challenged common industry practices.諸如YouMightNotNeedJQue…

java maven 操作 收集的一些命令

maven打包: mvn clean package -Dmaven.test.skiptrue 運行jar: java -jar target/spring-boot-scheduler-1.0.0.jar 這種方式關掉控制臺就不可以訪問,現在要后臺運行的方式啟動 nohup java -jar target/spring-boot-scheduler-1.0.0.jar & 清理并…