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


本篇文章就先到这里,其余的新特性将会在下一篇文章中进行讲解。希望我的文章能帮到你。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值