0%

node.js 官方網站說 Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient

到底是什麼架構讓 node.js 可以做到 event-driven 及 non-blocking 呢?今天就來談談這個話題。

首先我們要知道 node.js 內部有個 v8 engine,它負責把 javascript 程式碼轉成電腦能執行的資訊。

另一個重要的架構是 libuv,由於有這個 library,node.js 才能實現 event-driven 及 non-blocking 特性。

node.js 架構

假設你想用 node.js 讀一個文字檔,你可能會這麼寫

var fs = require('fs');

fs.readFile('myFile.txt', function(err, data) {
  console.log(data);
});
console.log('After calling readFile');

readFile 被調用時,libuv 會發出一個 request 給作業系統,請它回傳 myFile.txt 的內容。等到作業系統取得檔案內容後,libuv 會收到通知,當 event loop 處理這個 event 時,發現它有對應的 callback, function(err, data) { console.log(data); } 才會被調用。

這邊 event loop 做的事可以用一段 pseudo code 表示:

while there are still events to process:
    e = get the next event
    if there is a callback associated with e:
        call the callback

由於 libuv 運作時,v8 engine 也可以同時處理 javascript 的 code,所以才說 node.js 是 non-blocking,這代表 javascript code 不需要等作業系統取得檔案內容就可以繼續下一動作。

由於每當一個 event 結束時,v8 都可以收到通知,所以才說 node.js 是 event-driven。

參考資料:

  1. An Introduction to libuv
  2. Learn and Understand NodeJS

node.js 官方網站說 Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient

到底是什麼架構讓 node.js 可以做到 event-driven 及 non-blocking 呢?今天就來談談這個話題。

首先我們要知道 node.js 內部有個 v8 engine,它負責把 javascript 程式碼轉成電腦能執行的資訊。

另一個重要的架構是 libuv,由於有這個 library,node.js 才能實現 event-driven 及 non-blocking 特性。

node.js 架構

假設你想用 node.js 讀一個文字檔,你可能會這麼寫

var fs = require('fs');

fs.readFile('myFile.txt', function(err, data) {
  console.log(data);
});
console.log('After calling readFile');

readFile 被調用時,libuv 會發出一個 request 給作業系統,請它回傳 myFile.txt 的內容。等到作業系統取得檔案內容後,libuv 會收到通知,當 event loop 處理這個 event 時,發現它有對應的 callback, function(err, data) { console.log(data); } 才會被調用。

這邊 event loop 做的事可以用一段 pseudo code 表示:

while there are still events to process:
    e = get the next event
    if there is a callback associated with e:
        call the callback

由於 libuv 運作時,v8 engine 也可以同時處理 javascript 的 code,所以才說 node.js 是 non-blocking,這代表 javascript code 不需要等作業系統取得檔案內容就可以繼續下一動作。

由於每當一個 event 結束時,v8 都可以收到通知,所以才說 node.js 是 event-driven。

參考資料:

  1. An Introduction to libuv
  2. Learn and Understand NodeJS

Closure is an important topic in javascript if you want to have a deep understanding of this language.
In this article, I’ll explain the concept of closure based on what I’ve learned at udemy. If you find anything strange or weird with this article, feel free to let me know by leaving a comment below.

First, let’s say I have a function like this:

function foo(var1) {
    return bar(var2) {
        console.log(var1 + ' ' + var2);
    }
}

A function foo that returns another function bar. When I invoke foo('Hello'), I get another function that I can invoke again.

var sayHello = foo('Hello');
sayHello('Brian');    // Hello Brian

It seems very intuitive that sayHello('Brian') will display Hello Brian. But let’s stop and think about this for a moment. How does sayHello remember the value of var1, 'Hello', when sayHello('Brian') is invoked? Because the var1 variable is created when foo('Hello') is called. And when this foo function is over, the execution context is popped off the execution stack. And yet it still has the correct value, 'Hello', of var1. How is that possible? Well, this is where closure comes into play.

To understand what happened, we need to take a look at the execution stack.
When the code starts, the global execution context is created.

午安枕的墊子

When var sayHello = foo('Hello'); line is hit, we have the foo() execution context created. And the variable that’s passed to it, 'Hello', is sitting in its variable environment. The foo function returns a new function object.

午安枕的墊子

After the return, the foo execution context is popped off the execution stack.

午安枕的墊子

Under normal circumstances, the memory space outside of the execution context will be garbage collected. But because of closure, the javascript engine knows it should keep the var1 variable.

And this is why bar execution context can have access to the var1 variable:

When javascript engine sees the var1 variable at console.log(var1 + ' ' + var2) line, it goes up the scope chain to look for the value of var1. And becuase there’s an outer lexical environment reference.
The javascript engine is able to find var1 outside of bar execution context.

午安枕的墊子

The phenomon that the bar execution context is closed in its outer variables is called a closure.

午安枕的墊子

無意間發現console.log( 3 > 2 > 1 );回傳值是 false,讓我感到有些奇怪,因為 3 比 2 大也比 1 大,而且 2 比 1 大也是正確的,照理講應該回傳true才合理。但仔細想想後才意識到3 > 2 > 1應該要拆開來看能知道當中發生什麼事情:

首先,由於>運算子的執行順序(operator associativity)是從左到右,所以我們必須先觀察3 > 2的算運結果:

打開 node.js 來試試:

> 3 > 2
true

由於3 > 2是正確的,所以我們不意外地得到 true,接下來最需要注意的地方在 javascript 會比較 true1,那麼ture > 1會是什麼結果呢?

> true > 1
false

實測結果發現是 false。這是由於執行 >運算時,兩個運算子true1都會被強制轉型成 boolean,因為在 javascript 當中,只有 0 轉成 boolean 會是 false,其它數字強制轉型後都會是 true,所以原本的 true > 1可以視為 true > true。兩個true應該要相等,不可能其中一個 true 大於另一個 true,所以true > 1自然會回傳 fasle,這就是為什麼console.log( 3 > 2 > 1 );最後會得到 false

最後做個總整理:

3 > 2 > 1 相當於 true > 1 相當於 true > true 相當於 false

In javascript, you can call a function with any number of parameters you like.

If the number of parameters you provided is less than the number of parameters specified in function declaration, then the rest of the parameters will be undefined.

Here’s an example:

// sayHello.js

function sayHello(name) {
    console.log('Hello, ' + name);
}

sayHello()                // Hello, undefined
sayHello('Brian');        // Hello, Brian

What happened here is that name, the undefined primitive type, is coerced into a string which contains 'undefined' value.
Then Hello, and 'undefined' are concatenated by + operator.

What if you want to specify a value when the parameter is undefined ? Well, You can use || operator.

// sayHello_default_value.js

function sayHello(name) {
    name = name || '<Your name>';    // line 4
    console.log('Hello, ' + name);
}

sayHello();            // Hello, <Your name>
sayHello('Brian');     // Hello, Brian

You can simply think that line 4 means if the value of name is undefined, then assign it with <Your name> string value.
However, there’s something you should be aware of with the above statement.

Let’s dig deeper with name = name || '<Your name>';

> node
> 0 || 1
1       
> 1 || 2
1       
> undefined || 'Brian'
'Brian'

From the above execution results, we can see that if || is passed with two values that can be coerced into true and false, or two values that can both be coerced into true, it returns the first value that coerced into true.

So what happened at line 4 in sayHello_default_value.js is that
name and '<Your name>' are first coerced into boolean values. Because '<Your name>' is a string with length larger than 0, it is converted to true.
The result of the conversion of name depends on the input value of the parameter.
Then the return value of || is assiged to name on the left hand side of =. This is because the precedence of || is greater than that of =.

This means that if you pass a value that can be converted to false, like 0 or '', Hello, <Your name> will be printed by sayHello() function.

午睡一直是筆者長期以來的習慣,由於趴在辦公桌睡很容易導致背痛,之前便敗了一個號稱午睡救星的午安QQ枕

午安QQ枕

這枕頭的高度能夠自由調整,適合用在各種不同高度的辦公桌,唯一美中不足的就是枕頭中間的孔對於筆者來說實在是太小

午安枕的墊子

9公分寬的孔

孔的寬度差不多只有9公分,當我趴在上面的時候,眼睛或鼻子一定會被壓到,非常不舒服,筆者認為這塊墊子改成ㄇ字型設計會好很多

所幸後來找到其它產品來換掉這塊墊子

Evolution pillow

這塊 Evolution Pillow 原本是個頸枕,它中間的孔當初是設計掛在脖子上的,但把臉趴在中間其實也非常舒服,另一個神奇的地方在於它下方的繩子可以調整孔的大小,讓不論是臉大或臉小的人都可以順利趴上去而且不會壓到眼睛或鼻子

繩子

為了讓 Evolution Pillow 放到架子上不會滑下來,筆者又買了一個止滑墊,並切割成適當大小,架子掛上止滑墊後長這樣

止滑墊

最後組合起來的樣子

組裝後

由於這東西太少見,在辦公室用的時候我就像是動物園裡的生物被同事注目+偷拍

使用情況

最後,我想說的是這個午睡枕價格其實太高,不過相信不久的將來會有人開發出ㄇ字型午睡QQ枕,到時候就不必自己 DIY 組裝