Node.js 4.0 (一)

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。Node.js 的包管理器 npm 是全球最大的开源库生态系统。

  Node v4 这个版本是 Node 和 iojs 合并后发布的首个稳定版本,并且为开发者带来了大量的 ES6 语言扩展。了解 Node.js中包括的 ES6 语言扩展。

  Node.js 4.0.0 尖端的技术,保持了项目的先进性。其中对 v8 的升级几乎做到了与 Chromium / Google Chrome 同步,达到了 4.5.x,它提供了很多新的语言功能。ECMA-262 是 JavaScript 语言规范的最新版本,而且好多新特性数都是开箱即用的。这些新特性包括:

classes - 各种 ‘类’,再也无需用 CoffeeScript 的语法糖写类了
generators - 未来的.js 代码中将有无数生成器,不学一点就看不懂 JS 代码了哦
collections - 集合、映射、弱集合、弱映射
arrow functions - 箭向函数
block scoping - 使用 let 、const 作用域,块辖域
template strings - 模板字串
promises - 用标准化了的方法进行延迟和异步计算
symbols - 唯一的、不可修改的数据

块级作用域

严格模式

严格模式在语义上与正常的 JavaScript 有一些不同。 首先,严格模式会将 JavaScript 陷阱直接变成明显的错误。其次,严格模式修正了一些引擎难以优化的错误:同样的代码有些时候严格模式会比非严格模式下更快。 第三,严格模式禁用了一些有可能在未来版本中定义的语法。

因为我们 ECMAScript 6 中的一些特性,必须在严格模式下,才可以使用,而不报错。

严格模式可以应用到整个 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.";
}

let

let 允许把变量的作用域限制在块级域中。与 var 不同处是:var 申明变量要么是全局的,要么是函数级的,而无法是块级的。

let vs var
let 的作用域是块,而 var 的作用域是函数

'use strict';
var a = 5;
var b = 10;
if (a === 5) {
  let a = 4; // The scope is inside the if-block
  var b = 1; // The scope is inside the function
  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 创建块级作用域变量的,使用 var 创建一个全局变量。

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 使用 var 声明变量,以 function 来划分作用域,大括号“{}” 却限定不了 var 的作用域。用var声明的变量具有变量提升(declaration hoisting)的效果。

ES6 里增加了一个 let,可以在{}, if, for 里声明。用法同 var,但作用域限定在块级,let 声明的变量不存在变量提升。

'use strict';
function f1() {
  var a = 1;
  let n = 2;
  if (true) {
      var a = 20;
      let n = 10;
  }
  console.log(n); // 2
  console.log(a); // 20
}
f1();

类 (Classes)

类声明

ES6 中的类实际上就是个函数,而且正如函数的定义方式有函数声明和函数表达式两种一样,类的定义方式也有两种,分别是:类声明、类表达式。

类声明
类声明是定义类的一种方式,就像下面这样,使用 class 关键字后跟一个类名(这里是 Ploygon),就可以定义一个类。

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

变量提升
类声明和函数声明不同的一点是,函数声明存在变量提升现象,而类声明不会。也就是说,你必须先声明类,然后才能使用它,否则代码会抛出 ——ReferenceError 异常,像下面这样:

var p = new Polygon(); // ReferenceError

class Polygon {}

类表达式

类表达式是定义类的另外一种方式,就像函数表达式一样,在类表达式中,类名是可有可无的。如果定义了类名,则该类名只有在类体内部才能访问到。

'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 方法会被默认添加。

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

使用 extent 关键字创建子类

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('NiNi');
dog.speak();

集合

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 将其视为同一个键。

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的所有成员。

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 Map();
var keyString = "a string";
// 添加键
myMap.set(keyString, "和键'a string'关联的值");
// 读取值
myMap.get(keyString);    // "和键'a string'关联的值"

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 结构与 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 实例之中

下面是一个例子:

1
2
3
4
5
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 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。