nodejs死亡筆記之溯本歸源--node4.0+與ECMAScript6新特性(one)

前言: Node v4這個版本是Node和iojs合併後發佈的首個穩定版本,並且爲開發者帶來了大量的ES6語言擴展。現在瀏覽Node的官網,進入主頁會看到兩個推薦版本,一個是最新的V6.4.0,另一個就是現在使用量最大的、最穩定的版本—-V4.5.0。本篇文章將講解v4版本後nodejs的新特性。


新特性總覽

我們先來大致看一下有哪些新特性:

  • block scoping - 使用 let 、const 作用域,塊轄域
  • classes - 各種‘類‘,再也無需用 CoffeeScript 的語法糖寫類了
  • generators - 未來的.js 代碼中將有無數生成器,不學一點就看不懂 JS 代碼了哦
  • collections - 集合、映射、弱集合、弱映射
  • arrow functions - 箭向函數
  • template strings - 模板字串
  • promises - 用標準化了的方法進行延遲和異步計算
  • symbols - 唯一的、不可修改的數據

因爲ECMAScript 6中的一些特性,必須在嚴格模式下纔可以使用而不報錯,所以,我們先簡單的提一下嚴格模式。


嚴格模式

嚴格模式在語義上與正常的JavaScript有一些不同。 首先,嚴格模式會將JavaScript陷阱直接變成明顯的錯誤。其次,嚴格模式修正了一些引擎難以優化的錯誤:同樣的代碼有些時候嚴格模式會比非嚴格模式下更快。 第三,嚴格模式禁用了一些有可能在未來版本中定義的語法。

嚴格模式可以應用到整個script標籤或某個別函數中。

爲整個script標籤開啓嚴格模式, 需要在所有語句之前放一個特定語句 “use strict”; (或 ‘use strict’;)

// 整個語句都開啓嚴格模式的語法
"use strict";
let v = "Hi!  I'm a strict mode script!";

同樣的,要給某個函數開啓嚴格模式,得把 “use strict”; (或 ‘use strict’; )聲明一字不漏地放在函數體所有語句之前。

function strict(){
  // 函數級別嚴格模式語法
  'use strict';
  return "Hi!  I'm a strict mode function!" ;
}
function notStrict() { 
  return "I'm not strict.";
}

關於嚴格模式,之後會單獨寫一篇文章來介紹,注意,嚴格模式並不是es6的新特性,它是es5時出現的特性。

仔細閱讀了上面代碼的人會發現一個很莫名其妙的關鍵字—let,let是個什麼鬼東西?


let

很多語言中都有塊級作用域,JavaScript使用var聲明變量,以function來劃分作用域,大括號“{}” 卻限定不了var的作用域。用var聲明的變量具有變量提升(declaration hoisting)的效果。

ES6裏增加了一個let,可以在{}, if, for裏聲明。用法同var,但作用域限定在塊級,let聲明的變量不存在變量提升。

let 允許把變量的作用域限制在塊級域中。與 var 不同處是:var 申明變量要麼是全局的,要麼是函數級的,而無法是塊級的。

let vs var

let的作用域是塊,而var的作用域是函數

'use strict';
var a = 5;
var b = 10;
if (a === 5) {
  let a = 4; // 作用域爲if語句塊
  var b = 1; // 作用域爲整個方法
  console.log(a);  // 4
  console.log(b);  // 1
} 
console.log(a); // 5
console.log(b); // 1

let在循環中

可以使用let關鍵字綁定變量在循環的範圍而不是使用一個全局變量(使用var)定義。

'use strict';
for (let i = 0; i < 10; i++) {
  console.log(i); // 0, 1, 2, 3, 4 ... 9
}
console.log(i); // i is not defined

上面報錯,因爲變量i不存在於for語句外的作用域中。

除了let以外,es6還增加了一個const的關鍵字。


const

const這個聲明創建一個常量,可以是全局的或者是局部的,常量遵循與變量相同的作用域規則。

一個常量不可以被重新賦值,並且不能被重複聲明.所以,雖然可以在聲明一個常量的時候不進行初始化,但這樣做是沒有意義的,因爲這個常量的值永遠會保持undefined。

一個常量不能和它所在作用域內的其他變量或函數擁有相同的名稱。

示例
下面的例子演示了常量的行爲。

const num = 10;
num =20;
console.log(num); // 10

如果我們在上面聲明常量num,在聲明var num,這時會報錯,num已經聲明。

const num = 10;
var num = 20;
console.log(num); // 'num' has already been declared

接下來,我們看一下JavaScript中的class。


clsss

類聲明和類表達式

 ES6 中的類實際上就是個函數,而且正如函數的定義方式有函數聲明和函數表達式兩種一樣,類的定義方式也有兩種,分別是:類聲明、類表達式。

類聲明
  類聲明是定義類的一種方式,就像下面這樣,使用 class 關鍵字後跟一個類名(這裏是 Square),就可以定義一個類。

'use strict';
class Square{
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

變量提升
  類聲明和函數聲明不同的一點是,函數聲明存在變量提升現象,而類聲明不會。也就是說,你必須先聲明類,然後才能使用它,否則代碼會拋出 ——ReferenceError 異常,像下面這樣:

var p = new Square(); // ReferenceError

class Square {}

類表達式

類表達式是定義類的另外一種方式,就像函數表達式一樣,在類表達式中,類名是可有可無的。如果定義了類名,則該類名只有在類體內部才能訪問到。

'use strict';
// 匿名類表達式
var Polygon = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

// 命名類表達式
var Polygon = class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

構造函數

類的成員需要定義在一對花括號 {} 裏,花括號裏的代碼和花括號本身組成了類體。類成員包括類構造器和類方法(包括靜態方法和實例方法)。

  class 根據 constructor 方法來創建和初始化對象。

  constructor方法是類的默認方法,通過new命令生成對象實例時,自動調用該方法。一個類只能有一個constructor方法,如果沒有顯式定義,一個空的constructor方法會被默認添加。
  JavaScript constructor() {}

constructor方法默認返回實例對象(即this),完全可以指定返回另外一個對象。

'use strict';
class Foo {
  constructor() {
    return Object.create(null);
  }
}
new Foo() instanceof Foo
// false

上面代碼中,constructor函數返回一個全新的對象,結果導致實例對象不是Foo類的實例。

  constructor 方法是一個特殊的類方法,它既不是靜態方法也不是實例方法,它僅在實例化一個類的時候被調用。一個類只能擁有一個名爲 constructor 的方法,否則會拋出 SyntaxError 異常。

嚴格模式: 類和模塊的內部,默認就是嚴格模式,所以不需要使用use
strict指定運行模式。只要你的代碼寫在類或模塊之中,就只有嚴格模式可用。

靜態方法

static關鍵字定義了一個類的靜態方法。靜態方法被稱爲無需實例化類也可當類被實例化。靜態方法通常用於爲應用程序創建實用函數。

示例

'use strict';
class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    static distance(a, b) {
        const dx = a.x - b.x;
        const dy = a.y - b.y;

        return Math.sqrt(dx*dx + dy*dy);
    }
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);

console.log(Point.distance(p1, p2));//7.0710678118654755

使用 extends 關鍵字創建子類

extends 關鍵字可以用來創建繼承於某個類的子類。

這個例子是根據名爲Animal類創建一個名爲Dog的類。

'use strict';
class Animal { 
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(this.name + ' makes a noise.');
  }
}

class Dog extends Animal {
  speak() {
    console.log(this.name + ' barks.');
  }
}
var dog = new Dog('dogone');
dog.speak();

class暫時就講到這裏,接下來,我們將一下JavaScript中的幾種集合。


map

map對象是一個簡單的鍵/值映射。任何值(包括對象和原始值)都可以用作一個鍵或一個值。

var m = new Map();
var o = {p: "Hello World"};
m.set(o, "content")
m.get(o) // "content"

上面代碼使用set方法,將對象o當作m的一個鍵。

Map也可以接受一個數組作爲參數。該數組的成員是一個個表示鍵值對的數組。

var map = new Map([["name", "張三"], ["title", "Author"]]);
map.size // 2
map.get("name") // "張三"
map.get("title") // "Author"

上面代碼在新建Map實例時,就指定了兩個鍵name和title。

注意Map的鍵實際上是跟內存地址綁定的,只要內存地址不一樣,就視爲兩個鍵。

  • 如果使用對象作爲鍵名,就不用擔心自己的屬性與原作者的屬性同名。
  • 如果Map的鍵是一個簡單類型的值(數字、字符串、布爾值),則只要兩個值嚴格相等,Map將其視爲一個鍵,包括0和-0。
  • 另外,雖然NaN不嚴格相等於自身,但Map將其視爲同一個鍵。

實例的屬性和操作方法

size屬性返回Map結構的成員總數。即返回映射對象中的鍵/值對的數目。

set(key, value)方法設置key所對應的鍵值,然後返回整個Map結構。如果key已經有值,則鍵值會被更新,否則就新生成該鍵。

var m = new Map();
m.set("edition", 6)        // 鍵是字符串
m.set(262, "standard")     // 鍵是數值
m.set(undefined, "nah")    // 鍵是undefined
  • set()方法返回的是Map本身,因此可以採用鏈式寫法。

  • get(key)方法讀取key對應的鍵值,如果找不到key,返回undefined。

  • has(key)方法返回一個布爾值,表示某個鍵是否在Map數據結構中。

  • delete(key)方法刪除某個鍵,返回true。如果刪除失敗,返回false。

  • clear()方法清除所有成員,沒有返回值。

map遍歷方法

Map原生提供三個遍歷器生成函數和一個遍歷方法。

  • keys():返回鍵名的遍歷器。
  • values():返回鍵值的遍歷器。
  • entries():返回所有成員的遍歷器。
  • forEach():遍歷Map的所有成員。
var myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");

for (var key of myMap.keys()) {
  console.log(key);
}
// 0
// 1

for (var value of myMap.values()) {
  console.log(value);
}
// "zero"
//"one"

for (var item of myMap.entries()) {
  console.log(item[0] + " = " + item[1]);
}
// "0 = zero" 
// "1 = one"

myMap.forEach(function(value, key) {
  console.log(key + " = " + value);
}, myMap)
// "0 = zero" 
// "1 = one"

WeakMap

  WeakMap結構與Map結構基本類似,唯一的區別是它只接受對象作爲鍵名(null除外),不接受其他類型的值作爲鍵名,而且鍵名所指向的對象,不計入垃圾回收機制。

var map = new WeakMap()
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key

上面代碼中,如果將1和Symbol作爲WeakMap的鍵名,都會報錯。

  WeakMap的設計目的在於,鍵名是對象的弱引用(垃圾回收機制不將該引用考慮在內),所以其所對應的對象可能會被自動回收。當對象被回收後,WeakMap自動移除對應的鍵值對。

  典型應用是,一個對應DOM元素的WeakMap結構,當某個DOM元素被清除,其所對應的WeakMap記錄就會自動被移除。基本上,WeakMap的專用場合就是,它的鍵所對應的對象,可能會在將來消失。WeakMap結構有助於防止內存泄漏。

  WeakMap與Map在API上的區別主要是兩個,一是沒有遍歷操作(即沒有key()、values()和entries()方法),也沒有size屬性;二是無法清空,即不支持clear方法。這與WeakMap的鍵不被計入引用、被垃圾回收機制忽略有關。

  WeakMap只有四個方法可用:get()、set()、has()、delete()。

var myMap = new WeakMap();
var key = {name:"John"};
// 添加鍵
myMap.set(key, "this is john");
// 讀取值
var info = myMap.get(key);    
console.log(info);  // info: this is john

Set

Set基本用法

ES6提供了新的數據結構Set。它類似於數組,但是成員的值都是唯一的,沒有重複的值。

Set本身是一個構造函數,用來生成Set數據結構。

var s = new Set();
[2,3,5,4,5,2,2].map(x => s.add(x))
for (i of s) {console.log(i)}
// 2 3 5 4

上面代碼通過add方法向Set結構加入成員,結果表明Set結構不會添加重複的值。使用箭頭函數形式。

  向Set加入值的時候,不會發生類型轉換,所以5和”5”是兩個不同的值。Set內部判斷兩個值是否不同,使用的算法類似於精確相等運算符(===),這意味着,兩個對象總是不相等的。唯一的例外是NaN等於自身(精確相等運算符認爲NaN不等於自身)。

let set = new Set();
set.add({})
set.size // 1
set.add({})
set.size // 2

上面代碼表示,由於兩個空對象不是精確相等,所以它們被視爲兩個值。

Set實例的屬性和方法

Set結構的實例有以下屬性。

  • Set.prototype.constructor:構造函數,默認就是Set函數。
  • Set.prototype.size:返回Set實例的成員總數。

Set實例的方法分爲兩大類:操作方法(用於操作數據)和遍歷方法(用於遍歷成員)。

下面先介紹四個操作方法

  • add(value):添加某個值,返回Set結構本身。
  • delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
  • has(value):返回一個布爾值,表示該值是否爲Set的成員。
  • clear():清除所有成員,沒有返回值。

上面這些屬性和方法的實例如下。

var s = new Set();
s.add(1).add(2).add(2);
// 注意2被加入了兩次
console.log(s.size); // 2
console.log(s.has(1)); // true
console.log(s.has(2)); // true
console.log(s.has(3)); // false
console.log(s.delete(2));
console.log(s.has(2)); // false

接下來是遍歷操作

Set結構的實例有四個遍歷方法,可以用於遍歷成員。

  • keys():返回一個鍵名的遍歷器
  • values():返回一個鍵值的遍歷器
  • entries():返回一個鍵值對的遍歷器
  • forEach():使用回調函數遍歷每個成員

key方法、value方法、entries方法返回的都是遍歷器對象。由於Set結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),所以key方法和value方法的行爲完全一致。

let set = new Set(['red', 'green', 'blue']);
for ( let item of set.keys() ){
  console.log(item);
};
// red  green  blue
for ( let item of set.values() ){
  console.log(item);
};
// red  green  blue
for ( let item of set.entries() ){
  console.log(item);
};
// ["red", "red"]  ["green", "green"]  ["blue", "blue"]

上面代碼中,entries方法返回的遍歷器,同時包括鍵名和鍵值,所以每次輸出一個數組,它的兩個成員完全相等。

Set結構的實例的forEach方法,用於對每個成員執行某種操作,沒有返回值。

let set = new Set([1, 2, 3]);
set.forEach((value, key) => console.log(value * 2) )
// 2  4  6

上面代碼說明,forEach方法的參數就是一個處理函數。該函數的參數依次爲鍵值、鍵名、集合本身(上例省略了該參數)。另外,forEach方法還可以有第二個參數,表示綁定的this對象。


WeakSet

WeakSet基本用法

WeakSet結構與Set類似,也是不重複的值的集合。但是,它與Set有兩個區別。

首先,WeakSet的成員只能是對象,而不能是其他類型的值。

其次,WeakSet中的對象都是弱引用,即垃圾回收機制不考慮WeakSet對該對象的引用,也就是說,如果其他對象都不再引用該對象,那麼垃圾回收機制會自動回收該對象所佔用的內存,不考慮該對象還存在於WeakSet之中。這個特點意味着,無法引用WeakSet的成員,因此WeakSet是不可遍歷的

WeakSet是一個構造函數,可以使用new命令,創建WeakSet數據結構。

作爲構造函數,WeakSet可以接受一個數組或類似數組的對象作爲參數。(實際上,任何具有iterable接口的對象,都可以作爲WeakSet的參數。)該數組的所有成員,都會自動成爲WeakSet實例對象的成員。

var a = [[1,2], [3,4]];
var ws = new WeakSet(a);

上面代碼中,a是一個數組,它有兩個成員,也都是數組。將a作爲WeakSet構造函數的參數,a的成員會自動成爲WeakSet的成員。

WeakSet方法

WeakSet結構有以下三個方法。

  • WeakSet.prototype.add(value):向WeakSet實例添加一個新成員。
  • WeakSet.prototype.delete(value):清除WeakSet實例的指定成員。
  • WeakSet.prototype.has(value):返回一個布爾值,表示某個值是否在WeakSet實例之中

下面是一個例子。

var ws = new WeakSet();
var obj = {};
var foo = {};
ws.add(obj);
ws.has(foo);    // false
WeakSet沒有size屬性,沒有辦法遍歷它的成員。
ws.size // undefined
ws.forEach // undefined
ws.forEach(function(item){ console.log('WeakSet has ' + item)});
// TypeError: undefined is not a function

上面代碼試圖獲取size和forEach屬性,結果都不能成功。

  WeakSet不能遍歷,是因爲成員都是弱引用,隨時可能消失,遍歷機制無法保存成員的存在,很可能剛剛遍歷結束,成員就取不到了。WeakSet的一個用處,是儲存DOM節點,而不用擔心這些節點從文檔移除時,會引發內存泄漏。


本篇文章就先到這裏,其餘的新特性將會在下一篇文章中進行講解。希望我的文章能幫到你。

發佈了46 篇原創文章 · 獲贊 675 · 訪問量 75萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章