Nodejs中的一些小trick

之前常常因為不注意,習(xí)慣用寫PHP或者Java的方式來寫nodejs,產(chǎn)生了了一些錯誤疲牵,這里總結(jié)一些小小的trick,以便于展示nodejs的不同榆鼠,和平時需要注意的地方纲爸。

變量提升

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

你可能以為這段代碼執(zhí)行結(jié)果為:

global
global
local

但實際上結(jié)果是

global
undefined
local

原因就是函數(shù)作用域?qū)е戮植孔兞吭谡麄€函數(shù)體內(nèi)部可見,所以執(zhí)行起來就成了:

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

函數(shù)內(nèi)部的console.log出于就近原則讀取的是內(nèi)部variable妆够,亦即局部variable覆蓋了全局variable识啦,然后局部variable是整個函數(shù)體內(nèi)可見,所以相當(dāng)于提升了變量聲明神妹,亦即變量聲明放在了函數(shù)開頭颓哮,但是變量初始化還是在原來的位置,所以就是上面展示的順序鸵荠。
寫Java的時候我們傾向于在最開始使用一個局部變量之前聲明它冕茅,這樣幫我們清晰它的作用域以及生命周期;但是JavaScript沒有塊級作用域蛹找,所以局部變量最好寫在函數(shù)開始姨伤,這樣才能更清晰的展示它的作用域(整個函數(shù)內(nèi)部)和生命周期,避免產(chǎn)生誤解庸疾。

有點需要注意的是:聲明寫var與不寫var是有區(qū)別的:

console.log(a);   
a = 1;

會報錯乍楚,而下面這個:

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

結(jié)果是 undefined ,也就是沒有var的聲明不會提升届慈。

函數(shù)提升

js中創(chuàng)建函數(shù)有兩種方式:函數(shù)聲明式和函數(shù)字面量式炊豪。只有函數(shù)聲明才存在函數(shù)提升:

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

結(jié)果:

[Function: f1]
undefined

就是函數(shù)提升導(dǎo)致順序變?yōu)椋?/p>

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

原型繼承中的坑

JavaScript 沒有 提供對象繼承的語言級別特性凌箕,而是通過原型復(fù)制來實現(xiàn)的。

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);

結(jié)果:

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

subC僅僅繼承了superC在prototype中定義的屬性d词渤,而構(gòu)造函數(shù)內(nèi)部創(chuàng)造的a屬性沒有被subC繼承牵舱。同時,在原型中定義的屬性不會被console.log作為對象的屬性輸出缺虐。在subC中修改屬性a并不會修改superC的屬性a芜壁,但是能獲取superC的屬性d,而且設(shè)置了一個屬性cc也不會影響superC高氮。所以對于set操作并不會修改原型鏈慧妄,只有g(shù)et操作才會體會到原型鏈(繼承)的存在。

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);
}

結(jié)果是

b
d

也就是說 in 關(guān)鍵字能檢測到自有屬性和繼承熟性剪芍,這個可以用 !==來代替

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

結(jié)果一致塞淹, 但是in可以區(qū)分屬性不存在 和 屬性存在且為undefined 兩種情況,而!==不能區(qū)分罪裹。
再看下面的:

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

結(jié)果是

b

也就是說hasOwnProperty能檢測到自身屬性饱普,不包含繼承屬性∽垂玻總結(jié)一下:

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)
}

你可能會認(rèn)為結(jié)果是

0 1 2 3 4

但是結(jié)果是

5 5 5 5 5

原因就是 settimeout的回調(diào)函數(shù)執(zhí)行時套耕,for循環(huán)已經(jīng)執(zhí)行完畢。i變成了5峡继,而回調(diào)函數(shù)最近的原型作用域上的i(此處也就是全局作用域)就是5冯袍,自然就是5了。要達(dá)到想要的效果正確的做法是:

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

即用 (function(i){})(i);來產(chǎn)生一個立即作用域碾牌,保證settimeout回調(diào)函數(shù)執(zhí)行的時候最近的原型作用域的i就是當(dāng)時循環(huán)的i康愤。

說到這個就得談?wù)勯]包:所謂“閉包”,指的是一個擁有許多變量和綁定了這些變量的環(huán)境的表達(dá)式(通常是一個函數(shù))舶吗,因而這些變量也是該表達(dá)式的一部分征冷。用大白話:閉包的作用就是在一個函數(shù)執(zhí)行完并返回后,并不回收該函數(shù)所占用的資源裤翩,因為該函數(shù)的內(nèi)部函數(shù)(或?qū)傩裕┑膱?zhí)行需要依賴該函數(shù)中的屬性。

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

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

可見outF執(zhí)行過后调榄,其屬性count并未回收踊赠。回到上面那個錯誤的循環(huán)每庆,for創(chuàng)建了若干個閉包筐带,每個閉包共享上下文環(huán)境 i。因為for(很大可能)會先跑完缤灵,所以運行回調(diào)函數(shù)的時候 i 已經(jīng)變成了5伦籍。而正確的循環(huán)中蓝晒,也通過匿名函數(shù)創(chuàng)建了閉包,這個匿名函數(shù)作為外部函數(shù)帖鸦,通過立即調(diào)用芝薇,使得settimeout不需要共享循環(huán)中的i,而是獨享每一次循環(huán)不同的i作儿。

作用域真的可以說是JavaScript的一個問題洛二,var 聲明是具有整個函數(shù)內(nèi)部的可見性,而js1.7之后的let的出現(xiàn)算是彌補了這個缺陷(至于是不是缺陷就見仁見智了)攻锰,let 聲明的變量只屬于就近的花括號內(nèi)部晾嘶,看下面的代碼

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

結(jié)果就是

 0 1 2 3 4

區(qū)別就在于使用了 var 和 let 來生明變量i,let 使得每次程序進(jìn)入花括號就產(chǎn)生了一個塊級作用域娶吞,也就是說settimeout的回調(diào)函數(shù)作用域鏈中最近的i不再是全局的i垒迂,而是塊級作用域的i,也就是每一次不同的0,1,2,3,4而不是全局i最后是5妒蛇。let產(chǎn)生了和上面立即作用域相同的效果机断。

對象類型

非常奇怪,在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會在必要的時候幫助我們添加分號蛾狗,它有自己的添加規(guī)則(我們自然都懶得去記)。

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

就沒有按預(yù)期執(zhí)行沉桌,因為它解析成了 return;true;返回的自然是undefined。
所以避免混亂最簡單的做法就是老老實實的給每一句都加上 ;

數(shù)組相關(guān)

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

a1 = [,,,]; // [undefined,undefined,undefined]
a2 = new Array(3); //數(shù)組根本沒有元素
0 in a1;// true  留凭,如上所說,in 可以區(qū)分元素不存在和元素值存在且為undefined的情況
0 in a2;// false

高級數(shù)組方法

filter():“過濾”功能偎巢,數(shù)組中的每一項運行給定函數(shù)蔼夜,返回滿足過濾條件組成的數(shù)組。

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():判斷數(shù)組中每一項都是否滿足條件压昼,只有所有項都滿足條件求冷,才會返回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():判斷數(shù)組中是否存在滿足條件的項,只要有一項滿足條件匠题,就會返回true拯坟。

reduce() 和 reduceRight(),這兩個方法都會實現(xiàn)迭代數(shù)組的所有項梧躺,然后構(gòu)建一個最終返回的值似谁。reduce()方法從數(shù)組的第一項開始,逐個遍歷到最后掠哥。而 reduceRight()則從數(shù)組的最后一項開始巩踏,向前遍歷到第一項。
這兩個方法都接收兩個參數(shù):一個在每一項上調(diào)用的函數(shù)和(可選的)作為歸并基礎(chǔ)的初始值续搀。
傳給 reduce()和 reduceRight()的函數(shù)接收 4 個參數(shù):前一個值塞琼、當(dāng)前值、項的索引和數(shù)組對象禁舷。這個函數(shù)返回的任何值都會作為第一個參數(shù)自動傳給下一項彪杉。第一次迭代發(fā)生在數(shù)組的第二項上,因此第一個參數(shù)是數(shù)組的第一項牵咙,第二個參數(shù)就是數(shù)組的第二項派近。
下面代碼用reduce()實現(xiàn)數(shù)組求。

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

調(diào)用函數(shù)之 this

調(diào)用函數(shù)有4種方式洁桌,不同之處就在于調(diào)用上下文渴丸,也就是關(guān)鍵字(不是變量,不是屬性名)this的值

  • 函數(shù)調(diào)用 另凌,亦即直接調(diào)用一個函數(shù)谱轨,在非嚴(yán)格模式下this指向全局對象,嚴(yán)格模式下指向undefined吠谢。需要注意的是嵌套函數(shù)的this并不指向外層函數(shù)的上下文土童,而是也遵照這個規(guī)則。
  • 方法調(diào)用 工坊,亦即作為一個類的方法調(diào)用一個函數(shù)献汗,this指向這個類的對象
  • 構(gòu)造器調(diào)用 ,亦即使用 new 關(guān)鍵字王污,this指向新建的對象本身
  • call,apply調(diào)用 均澳,this指向該函數(shù)綁定的對象

參考

JavaScript 權(quán)威指南 第六版辱士;
JavaScript 語言精粹叼旋;
深入淺出 nodejs阎抒;
http://blog.csdn.net/u014607184/article/details/51820564
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/let

** 歡迎訪問我的主頁 Mageek`s Wonderland http://mageek.cn/archives/32/ **

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末醒叁,一起剝皮案震驚了整個濱河市司浪,隨后出現(xiàn)的幾起案子泊业,更是在濱河造成了極大的恐慌,老刑警劉巖啊易,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吁伺,死亡現(xiàn)場離奇詭異,居然都是意外死亡租谈,警方通過查閱死者的電腦和手機(jī)篮奄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來割去,“玉大人窟却,你說我怎么就攤上這事∩肽妫” “怎么了夸赫?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咖城。 經(jīng)常有香客問我茬腿,道長,這世上最難降的妖魔是什么宜雀? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任切平,我火速辦了婚禮,結(jié)果婚禮上辐董,老公的妹妹穿的比我還像新娘悴品。我一直安慰自己,他們只是感情好郎哭,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布他匪。 她就那樣靜靜地躺著,像睡著了一般夸研。 火紅的嫁衣襯著肌膚如雪邦蜜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天亥至,我揣著相機(jī)與錄音悼沈,去河邊找鬼。 笑死姐扮,一個胖子當(dāng)著我的面吹牛絮供,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茶敏,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼壤靶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了惊搏?” 一聲冷哼從身側(cè)響起贮乳,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤忧换,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后向拆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亚茬,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年浓恳,在試婚紗的時候發(fā)現(xiàn)自己被綠了刹缝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡颈将,死狀恐怖梢夯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晴圾,我是刑警寧澤厨疙,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站疑务,受9級特大地震影響沾凄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜知允,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一撒蟀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧温鸽,春花似錦保屯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蝠猬,卻和暖如春切蟋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背榆芦。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工柄粹, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匆绣。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓驻右,卻偏偏與公主長得像,于是被迫代替她去往敵國和親崎淳。 傳聞我的和親對象是個殘疾皇子堪夭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 繼承 一、混入式繼承 二、原型繼承 利用原型中的成員可以被和其相關(guān)的對象共享這一特性森爽,可以實現(xiàn)繼承礼华,這種實現(xiàn)繼承的...
    magic_pill閱讀 1,064評論 0 3
  • 原文: https://github.com/ecomfe/spec/blob/master/javascript...
    zock閱讀 3,375評論 2 36
  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,238評論 0 4
  • 那里有用微笑迎接我們的人,留下很多牽掛的人祈惶,許久未見的人雕旨;那里月光如雪,壁爐成煙捧请。 有一天凡涩,二爺在電話那端冷不防地...
    張寕逺閱讀 755評論 5 2
  • 每一個熬夜的人下輩子都會變成一只丑丑的貓,小公舉就是如此疹蛉。他常年熬夜活箕,終于在下輩子嘗到了惡果——原本帥到爆炸的他變...
    深井冰4353閱讀 345評論 0 0