Nodejs中的一些小trick

以前經常由於不注意,習慣用寫PHP或者Java的方式來寫nodejs,產生了了一些錯誤,這裏總結一些小小的trick,以便於展現nodejs的不一樣,和平時須要注意的地方。node

變量提高

var variable = 'global';
console.log(variable); 
function fn () {
    console.log(variable); 
    var variable = 'local';
    console.log(variable);
}
fn();

你可能覺得這段代碼執行結果爲:數組

global
global
local

但實際上結果是閉包

global
undefined
local

緣由就是函數做用域致使局部變量在整個函數體內部可見,因此執行起來就成了:app

function fn () {
     var variable
     console.log(variable); 
     variable = 'local';
     console.log(variable);
 }

函數內部的console.log出於就近原則讀取的是內部variable,亦即局部variable覆蓋了全局variable,而後局部variable是整個函數體內可見,因此至關於提高了變量聲明,亦即變量聲明放在了函數開頭,可是變量初始化仍是在原來的位置,因此就是上面展現的順序。
寫Java的時候咱們傾向於在最開始使用一個局部變量以前聲明它,這樣幫咱們清晰它的做用域以及生命週期;可是JavaScript沒有塊級做用域,因此局部變量最好寫在函數開始,這樣才能更清晰的展現它的做用域(整個函數內部)和生命週期,避免產生誤解。函數

有點須要注意的是:聲明寫var與不寫var是有區別的:ui

console.log(a);   
a = 1;

會報錯,而下面這個:this

console.log(a);   
var a = 1;

結果是 undefined ,也就是沒有var的聲明不會提高。.net

函數提高

js中建立函數有兩種方式:函數聲明式和函數字面量式。只有函數聲明才存在函數提高:prototype

console.log(f1);  
console.log(f2);   
function f1() {}
var f2 = function() {}

結果:code

[Function: f1]
undefined

就是函數提高致使順序變爲:

function f1() {}     
console.log(f1);   
console.log(f2);   
var f2 = function() {}

原型繼承中的坑

JavaScript 沒有 提供對象繼承的語言級別特性,而是經過原型複製來實現的。

var util = require('util')
function Superclass(){
    this.a = 'a';
}
Superclass.prototype.d = 'd';
function Subclass(){
    this.b = 'b';
}
util.inherits(Subclass, Superclass);
var superC = new Superclass();
var subC = new Subclass();

console.log(superC.a);
console.log(subC.a);
subC.a = 'suba';
console.log(superC.a);
console.log(subC.a);
subC.cc = 'cc';
console.log(superC.cc);
console.log(subC.cc);
console.log(superC.d);
console.log(subC.d);

結果:

a
undefined
a
suba
undefined
cc
d
d
Superclass { a: 'a' }

subC僅僅繼承了superC在prototype中定義的屬性d,而構造函數內部創造的a屬性沒有被subC繼承。同時,在原型中定義的屬性不會被console.log做爲對象的屬性輸出。在subC中修改屬性a並不會修改superC的屬性a,可是能獲取superC的屬性d,並且設置了一個屬性cc也不會影響superC。因此對於set操做並不會修改原型鏈,只有get操做纔會體會到原型鏈(繼承)的存在。

var util = require('util')
function Superclass(){
    this.a = 'a';
}
Superclass.prototype.d = 'd';
function Subclass(){
    this.b = 'b';
}
util.inherits(Subclass, Superclass);
var superC = new Superclass();
var subC = new Subclass();


for(x in subC){
    console.log(x);
}

結果是

b
d

也就是說 in 關鍵字能檢測到自有屬性和繼承熟性,這個能夠用 !==來代替

for(x in subC){
    if(subC[x] !== undefined)
        console.log(x);
}

結果一致, 可是in能夠區分屬性不存在 和 屬性存在且爲undefined 兩種狀況,而!==不能區分。
再看下面的:

for(x in subC){
    if(subC.hasOwnProperty(x))
        console.log(x);
}

結果是

b

也就是說hasOwnProperty能檢測到自身屬性,不包含繼承屬性。總結一下:

superC.hasOwnProperty();             //自有屬性爲真
superC.propertyIsEnumerable(superC); //可枚舉屬性爲真
Object.keys(superC);                 //全部可枚舉自有屬性
Object.getOwnPropertyNames(superC);  //全部自有屬性
for x in superC                      //自有及其其原型鏈上繼承到的可枚舉屬性

依然是做用域

看看這段代碼:

for(var i = 0; i < 5; i++){
    setTimeout(function(){
        console.log(i);
    },100*i)
}

你可能會認爲結果是

0 1 2 3 4

可是結果是

5 5 5 5 5

緣由就是 settimeout的回調函數執行時,for循環已經執行完畢。i變成了5,而回調函數最近的原型做用域上的i(此處也就是全局做用域)就是5,天然就是5了。要達到想要的效果正確的作法是:

for(var i = 0; i < 5; i++){
    (function(i){setTimeout(function(){
        console.log(i);
    },100*i)})(i)
}

即用 (function(i){})(i);來產生一個當即做用域,保證settimeout回調函數執行的時候最近的原型做用域的i就是當時循環的i。

說到這個就得談談閉包:所謂「閉包」,指的是一個擁有許多變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分。用大白話:閉包的做用就是在一個函數執行完並返回後,並不回收該函數所佔用的資源,由於該函數的內部函數(或屬性)的執行須要依賴該函數中的屬性。

function outF() {
   var count = 0;
   return function inF(){
      count++;
      console.log(count);
   }
}

var inF= outF();
inF();  // 1
inF();  // 2

可見outF執行事後,其屬性count並未回收。回到上面那個錯誤的循環,for建立了若干個閉包,每一個閉包共享上下文環境 i。由於for(很大可能)會先跑完,因此運行回調函數的時候 i 已經變成了5。而正確的循環中,也經過匿名函數建立了閉包,這個匿名函數做爲外部函數,經過當即調用,使得settimeout不須要共享循環中的i,而是獨享每一次循環不一樣的i。

做用域真的能夠說是JavaScript的一個問題,var 聲明是具備整個函數內部的可見性,而js1.7以後的let的出現算是彌補了這個缺陷(至因而不是缺陷就見仁見智了),let 聲明的變量只屬於就近的花括號內部,看下面的代碼

for(let i = 0; i < 5; i++){
    setTimeout(function(){
        console.log(i);
    },100*i)
}

結果就是

0 1 2 3 4

區別就在於使用了 var 和 let 來生明變量i,let 使得每次程序進入花括號就產生了一個塊級做用域,也就是說settimeout的回調函數做用域鏈中最近的i再也不是全局的i,而是塊級做用域的i,也就是每一次不一樣的0,1,2,3,4而不是全局i最後是5。let產生了和上面當即做用域相同的效果。

對象類型

很是奇怪,在Javascript中沒有很是簡單的獲取一個對象的類別的方法,instanceof 是要檢查原型鏈的,相似於isPropertypeOf,因此沒法一步到位得到最精確地的對象類型,通常用下面這個 classof能夠得到最精確的類型

var a = new Date();

function classof(o){
    if(o===null) return "Null";
    if(o===undefined) return "Undefined";
    return Object.prototype.toString.call(o).slice(8,-1);
}

console.log(classof(a));//Date

之因此不直接用Object.prototype.toString,是由於好多類型重寫了這個方法,不能保證它輸出是
[object class],因此使用Function.call方法。

萬惡的分號 ;

nodejs中分號; 是可選的,這個有必定程度的便利,但是在我看來它更多的是形成了混亂,js會在必要的時候幫助咱們添加分號,它有本身的添加規則(咱們天然都懶得去記)。

var a
a 
=
3
console.log(a)

這個會解析成

var a; a = 3;console.log(a);

沒啥毛病。
但是

var equa = function(a,b){
    if(a===b){
        return
        true;
    }
    return false;        
}
console.log(equa(5,5));//undefined

就沒有按預期執行,由於它解析成了 return;true;返回的天然是undefined。
因此避免混亂最簡單的作法就是老老實實的給每一句都加上 ;

數組相關

a = [];a[1000]=5; //a.length=1001,雖然a只有一個元素

a1 = [,,,]; // [undefined,undefined,undefined]
a2 = new Array(3); //數組根本沒有元素
0 in a1;// true  ,如上所說,in 能夠區分元素不存在和元素值存在且爲undefined的狀況
0 in a2;// false

高級數組方法

filter():「過濾」功能,數組中的每一項運行給定函數,返回知足過濾條件組成的數組。

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var arr2 = arr.filter(function(x, index) {
    return index % 3 === 0 || x >= 8;
}); 
console.log(arr2); //[1, 4, 7, 8, 9, 10]

every():判斷數組中每一項都是否知足條件,只有全部項都知足條件,纔會返回true。

var arr = [1, 2, 3, 4, 5];
var arr2 = arr.every(function(x) {
    return x < 10;
}); 
console.log(arr2); //true
var arr3 = arr.every(function(x) {
    return x < 3;
}); 
console.log(arr3); // false

some():判斷數組中是否存在知足條件的項,只要有一項知足條件,就會返回true。

reduce() 和 reduceRight(),這兩個方法都會實現迭代數組的全部項,而後構建一個最終返回的值。reduce()方法從數組的第一項開始,逐個遍歷到最後。而 reduceRight()則從數組的最後一項開始,向前遍歷到第一項。
這兩個方法都接收兩個參數:一個在每一項上調用的函數和(可選的)做爲歸併基礎的初始值。
傳給 reduce()和 reduceRight()的函數接收 4 個參數:前一個值、當前值、項的索引和數組對象。這個函數返回的任何值都會做爲第一個參數自動傳給下一項。第一次迭代發生在數組的第二項上,所以第一個參數是數組的第一項,第二個參數就是數組的第二項。
下面代碼用reduce()實現數組求。

var values = [1,2,3,4,5];
var sum = values.reduceRight(function(prev, cur, index, array){
    return prev + cur;
},0);
console.log(sum); //15

調用函數之 this

調用函數有4種方式,不一樣之處就在於調用上下文,也就是關鍵字(不是變量,不是屬性名)this的值

  • 函數調用 ,亦即直接調用一個函數,在非嚴格模式下this指向全局對象,嚴格模式下指向undefined。須要注意的是嵌套函數的this並不指向外層函數的上下文,而是也遵守這個規則。
  • 方法調用 ,亦即做爲一個類的方法調用一個函數,this指向這個類的對象
  • 構造器調用 ,亦即便用 new 關鍵字,this指向新建的對象自己
  • call,apply調用 ,this指向該函數綁定的對象

參考

JavaScript 權威指南 第六版;
JavaScript 語言精粹;
深刻淺出 nodejs;
http://blog.csdn.net/u0146071...
https://developer.mozilla.org...

歡迎訪問個人主頁 Mageek`s Wonderland http://mageek.cn/archives/32/

相關文章
相關標籤/搜索