ECMAScript 6

Block Bindings

let and const

1.变量名不可重复,但可以在{}中重新声明
2.const的值为对像时,不可以修改为其它对像,但可以修改这个对像的属性

const person = {
    name: "Nicholas"
};
// works
person.name = "Greg";
// throws an error
person = {
    name: "Greg"
};

The Temporal Dead Zone

let and const声明的变量,在未声明时不能访问, 即使是 typeof操作符, Javascript引擎会搜索之后的代码,如果是var,则提升为function或全局作用域,如果是let,则在TDZ中声明

if (condition) {
    console.log(typeof value); // throws an error
    let value = "blue";
}

//以下的可以
console.log(typeof value); // "undefined"
if (true) {
    let value = "blue";
}

循环中使用let

for (var i = 0; i < 10; i++) {
    process(items[i]);
}
// i is still accessible here
console.log(i);
for (let i = 0; i < 10; i++) {
    process(items[i]);
}
// i is not accessible here - throws an error
console.log(i);
process.stdout.write("var:")
var funcs = [];
for (var i = 0; i < 10; i++) {
    funcs.push(function() {
        process.stdout.write(i+" ")
    });
}
funcs.forEach(function(func) {
    func(); // outputs the number "10" ten times
});

process.stdout.write("\n闭包:")
var funcs = [];
for (var i = 0; i < 10; i++) {
    funcs.push((function(value) {
        return function() {
           process.stdout.write(value+" ")    
        }
    }(i)));
}
funcs.forEach(function(func) {
    func(); // outputs 0, then 1, then 2, up to 9
});
process.stdout.write("\nlet:")
var funcs = [];
for (let i = 0; i < 10; i++) {
    funcs.push(function() {
         process.stdout.write(i+" ")    
    });
}
funcs.forEach(function(func) {
    func(); // outputs 0, then 1, then 2, up to 9
})

Global block Bindings

var 会重写全局对像,但let和const不会重写重局对像

// in a browser
var RegExp = "Hello!";
console.log(window.RegExp); // "Hello!"
var ncz = "Hi!";
console.log(window.ncz); // "Hi!"


// in a browser
let RegExp = "Hello!";
console.log(RegExp); // "Hello!"
console.log(window.RegExp === RegExp); // false
const ncz = "Hi!";
console.log(ncz); // "Hi!"
console.log("ncz" in window); // false

2 字符串和正则表达式

Unicode支持

之前javascript使用的是16位来表示单个字符串, code point. length和charAt都是基于16 bit. es6使用codePointAt()和charPointAt方法, codePointAt()返回整个code编码

var text = "?";
console.log(text.length); // 2
console.log(/^.$/.test(text)); // false
console.log(text.charAt(0)); // ""
console.log(text.charAt(1)); // ""
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271

let text = "?a";
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271
console.log(text.charCodeAt(2)); // 97
console.log(text.codePointAt(0)); // 134071
console.log(text.codePointAt(1)); // 57271
console.log(text.codePointAt(2)); // 97

function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}
console.log(is32Bit("?")); // true
console.log(is32Bit("a")); // false

console.log("中".codePointAt(0))
console.log(String.fromCodePoint(20013))
console.log("中".codePointAt(0))
console.log(String.fromCodePoint(20013))
20013
中

normalize() Method

如何比较, character “æ” and the two-character string “ae”, 它们来源于两个不同的code point, 但表示出来的字符串是相同的, es6提供normalize方法,来标准后
• Normalization Form Canonical Composition (“NFC”), the default
• Normalization Form Canonical Decomposition (“NFD”)
• Normalization Form Compatibility Composition (“NFKC”)
• Normalization Form Compatibility Decomposition (“NFKD”)

values.sort(function(first, second) {
    let firstNormalized = first.normalize("NFD"),
    secondNormalized = second.normalize("NFD");
    if (firstNormalized < secondNormalized) {
        return -1;
    } else if (firstNormalized === secondNormalized) {
        return 0;
    } else {
    return 1;
    }
});

正则表达式u

原于u语言的修改,如果是在不兼容es6的Javascript上使用,会有错误的发生, 所以需要先确定是否支持u flag.

function hasRegExpU() {
    try {
        var pattern = new RegExp(".", "u");
    return true;
    } catch (ex) {
        return false;
    }
}
let text = "?";
console.log(text.length); // 2
console.log(/^.$/.test(text)); // false
console.log(/^.$/u.test(text)); // true

function codePointLength(text) {
    let result = text.match(/[\s\S]/gu);
    return result ? result.length : 0;
}
console.log(codePointLength("abc")); // 3
console.log(codePointLength("?bc")); // 3

Other String Changes

es5添加了trim()方法,以下是es6添加的方法

  • includes() 如果参数字符串包含在字符串中返回true,否则false
  • startsWith()
  • endsWith()
  • repeat()
let msg = "Hello world!";
console.log(msg.startsWith("Hello")); // true
console.log(msg.endsWith("!")); // true
console.log(msg.includes("o")); // true
console.log(msg.startsWith("o")); // false
console.log(msg.endsWith("world!")); // true
console.log(msg.includes("x")); // false
console.log(msg.startsWith("o", 4)); // true
console.log(msg.length)
console.log(msg.endsWith("o", 8)); // true 匹配到第二个o, 左侧从1开始数8个, 右侧length - 8
console.log(msg.includes("o", 8)); // false
console.log("x".repeat(3)); // "xxx"
console.log("hello".repeat(2)); // "hellohello"
console.log("abc".repeat(4)); // "abcabcabcabc"

正则表达式

y flag

let text = "hello1 hello2 hello3",
pattern = /hello\d\s?/,
result = pattern.exec(text),
globalPattern = /hello\d\s?/g,
globalResult = globalPattern.exec(text),
stickyPattern = /hello\d\s?/y,
stickyResult = stickyPattern.exec(text);
console.log(result[0]); // "hello1 "
console.log(globalResult[0]); // "hello1 "  //lastIndex 7
console.log(stickyResult[0]); // "hello1 "  //lastIndex 7
pattern.lastIndex = 1;  //直接忽略,还是从0开始
globalPattern.lastIndex = 1; //global直接匹配第二个字符串后面的
stickyPattern.lastIndex = 1; //在指定位置没有找到
result = pattern.exec(text);
globalResult = globalPattern.exec(text);
stickyResult = stickyPattern.exec(text);
console.log(result[0]); // "hello1 "
console.log(globalResult[0]); // "hello2 "
console.log(stickyResult[0]); // throws an error!
let re1 = /ab/i,
// throws an error in ES5, okay in ES6
re2 = new RegExp(re1, "g");
console.log(re1.toString()); // "/ab/i"
console.log(re2.toString()); // "/ab/g"
console.log(re1.test("ab")); // true
console.log(re2.test("ab")); // true
console.log(re1.test("AB")); // true
console.log(re2.test("AB")); // false

获取正常表大式中的flag属性

// ECMAScript 5
function getFlags(re) {
var text = re.toString();
return text.substring(text.lastIndexOf("/") + 1, text.length);
}
// toString() is "/ab/g"
var re = /ab/g;
console.log(getFlags(re)); // "g"

//ECMAScript 6
let re = /ab/g;
console.log(re.source); // "ab"
console.log(re.flags); // "g"

字符量模板

基础语法,(`),

console.log(`hello world`)
//多行字符
let message = `Multiline
     string`;
console.log(message); // "Multiline
// string"
console.log(message.length); // 31
let name = "Nicholas";
console.log(`Hello, ${name}.`); // "Hello, Nicholas."

let count = 10,
price = 0.25;
console.log(`${count} items cost $${(count * price).toFixed(2)}.`); // "10 items cost $2.50."

let name1 = "Nicholas";
console.log(`Hello, ${
`my name is ${ name1 }`
}.`); // "Hello, my name is Nicholas."

Hello, Nicholas.
10 items cost $2.50.
Hello, my name is Nicholas.
//Tagged Templates
//传递的第一个是一个字符字面量数组
//数组的第一个值为第一个子{}的字符,这里为""
//数组的第二个值为第一个{}到第二个{}的字符串(" items cost $")
//第三个为第二个{}的字符串 (".")
function passthru(literals, ...substitutions) {
    console.log(literals)
    console.log(substitutions)
    let result = "";
    // run the loop only for the substitution count
    for (let i = 0; i < substitutions.length; i++) {
        result += literals[i];
        result += substitutions[i];
    }
    // add the last literal
    result += literals[literals.length - 1];
    return result;
}
let count = 10,
price = 0.25,
message = passthru`${count} items cost $${(count * price).toFixed(2)}.`;
console.log(message); // "10 items cost $2.50."

[ '', ' items cost $', '.' ]
[ 10, '2.50' ]
10 items cost $2.50.
//Raw string
let message1 = `Multiline\nstring`,
message2 = String.raw`Multiline\nstring`;
console.log(message1); // "Multiline
// string"
console.log(message2); // "Multiline\\nstring"
Multiline
string
Multiline\nstring

3 functions

参数默认值

ECMAScript5

function makeRequest(url, timeout, callback) {
timeout = timeout || 2000;
callback = callback || function() {};
// the rest of the function
}

//如果为0, 则会替换为2000,所以需要判断类型
function makeRequest(url, timeout, callback) {
    timeout = (typeof timeout !== "undefined") ? timeout : 2000;
    callback = (typeof callback !== "undefined") ? callback : function() {};
    // the rest of the function
}

ECMAScript6

function makeRequest(url, timeout = 2000, callback = function() {}) {
    // the rest of the function
}
function makeRequest(url, timeout = 2000, callback) {
    // the rest of the function
}
// uses default timeout
makeRequest("/foo", undefined, function(body) {
    doSomething(body);
});

默认值对arugments对像的影响,在ECMAScript 5 non-strict mode. arguments对像是命名参数反射,改变参数,会改变arguments中的值.


function mixArgs(first, second) {
    console.log(first === arguments[0]);
    console.log(second === arguments[1]);
    first = "c";
    second = "d";
    console.log(first === arguments[0]);
    console.log(second === arguments[1]);
}
mixArgs("a", "b");

true
true
true
true
//如果是strict mode, 则参数的变化,不会影响arguments
function mixArgs(first, second) {
    "use strict";
    console.log(first === arguments[0]);
    console.log(second === arguments[1]);
    first = "c";
    second = "d"
    console.log(first === arguments[0]);
    console.log(second === arguments[1]);
}
mixArgs("a", "b");
true
true
false
false
//ECMAScript6的形为跟strict model类似
function mixArgs(first, second = "b") {
    console.log(arguments.length);
    console.log(first === arguments[0]);
    console.log(second === arguments[1]);
    first = "c";
    second = "d"
    console.log(first === arguments[0]);
    console.log(second === arguments[1]);
}
mixArgs("a");
mixArgs("a", "b")
1
true
false
false
false
2
true
true
false
false
//默认参数表达式, 如果默认参数没有提供,则getValue()会被调用, 需要注意的是getValue()不是在定义的时候调用,而是在没有值入值的时候调用
function getValue() {
    return 5;
}
function add(first, second = getValue()) {
    return first + second;
}
console.log(add(1, 1)); // 2
console.log(add(1)); // 6
2
6
let value = 5;
function getValue() {
    return value++;
}
function add(first, second = getValue()) {
    return first + second;
}
console.log(add(1, 1)); // 2
console.log(add(1)); // 6
console.log(add(1)); // 7
2
6
7
//但需要注意,第一个对数不能访问后面的值 
function add(first = second, second) {
    return first + second;
}
console.log(add(1, 1)); // 2
console.log(add(undefined, 1)); // throws an error, TDZ

Rest Params

  • 命名参数不能在Rest参数之后
  • 不能用于setter函数
function pick(object, ...keys, last) {
    let result = Object.create(null);
    for (let i = 0, len = keys.length; i < len; i++) {
        result[keys[i]] = object[keys[i]];
    }
    return result;
}

let object = {
    // Syntax error: Can't use rest param in setter
    set name(...value) {
    // do something
}
};

function pick(object) {
    let result = Object.create(null);
    // start at the second parameter
    for (let i = 1, len = arguments.length; i < len; i++) {
    result[arguments[i]] = object[arguments[i]];
    }
    return result;
}
let book = {
    title: "Understanding ECMAScript 6",
    author: "Nicholas C. Zakas",
    year: 2016
};
let bookData = pick(book, "author", "year");
console.log(bookData.author); // "Nicholas C. Zakas"
console.log(bookData.year); // 2016
Nicholas C. Zakas
2016
//ECMAScript6使用Rest Parameters, 已
function pick(object, ...keys) {
    let result = Object.create(null);
    for (let i = 0, len = keys.length; i < len; i++) {
        result[keys[i]] = object[keys[i]];
    }
    return result;
}
//Arugments只引用传入给函数的参与,它且rest parameter的使用无关
function checkArgs(...args) {
    console.log(args.length);
    console.log(arguments.length);
    console.log(args[0], arguments[0]);
    console.log(args[1], arguments[1]);
    args[1] = "c";
    console.log(args[1], arguments[1]);
}
checkArgs("a", "b");
2
2
a a
b b
c b

Function构造函数

var add = new Function("first", "second", "return first + second");
console.log(add(1, 1)); // 2
//增加了默认值
var add = new Function("first", "second = first",
"return first + second");
console.log(add(1, 1)); // 2
console.log(add(1)); // 2
var pickFirst = new Function("...args", "return args[0]");
console.log(pickFirst(1, 2)); // 1

Spread操作符

let value1 = 25,
value2 = 50;
console.log(Math.max(value1, value2)); // 50

//ECMAString 5
let values = [25, 50, 75, 100]
console.log(Math.max.apply(Math, values)); // 100
//ECMAScript 6
let values = [25, 50, 75, 100]
// equivalent to
// console.log(Math.max(25, 50, 75, 100));
console.log(Math.max(...values)); // 100

增加了Name属性

之前的匿名函数在调试的时候非常困难,所以在ECMAScript6中为所有的函数增加了name属性

function doSomething() {
    // empty
}
var doAnotherThing = function() {
    // empty
};
console.log(doSomething.name); // "doSomething"
console.log(doAnotherThing.name); // "doAnotherThing" 匿名函数,但是它分配给了doAnotherThing变量


var doSomething = function doSomethingElse() {
    // empty
};
var person = {
    get firstName() {
        return "Nicholas"
    },
    sayName: function() {
        console.log(this.name);
    }
}
console.log(doSomething.name); // "doSomethingElse"  优先级高于doSomething
console.log(person.sayName.name); // "sayName"
console.log(person.firstName.name); // "get firstName"


var doSomething = function() {
    // empty
};
console.log(doSomething.bind().name); // "bound doSomething"  bind会添加一个Bound
console.log((new Function()).name); // "anonymous"

明确函数的用途

在ECMAScript5,函数都有两个目的,调用或者创建对像。

function Person(name) {
    this.name = name;
}
var person = new Person("Nicholas");
var notAPerson = Person("Nicholas");
console.log(person); // "[Object object]"
console.log(notAPerson); // "undefined"

当创建notAPerson时,因为没有new,所以结果为undefined(在全局对像中,创建了一个name属性), Javascript的函数有两个不同的内部方法[[Call]][[Construct]], 当没有new关键词时,调用[[call]]方法. 否则调用[[Construct]]. 但需要注意,并不是所有的Function都有[[Construct]]方法。比如Arrow Function就没有

ECMAScript5确认函数是调用还是创建对像

function Person(name) {
    if (this instanceof Person) {
        this.name = name; // using new
    } else {
        throw new Error("You must use new with Person.")
    }
}
var person = new Person("Nicholas");
var notAPerson = Person("Nicholas"); // throws an error

但这种方法有一个缺点是,并不是调用new来创建的对像

function Person(name) {
    if (this instanceof Person) {
        this.name = name;
    } else {
        throw new Error("You must use new with Person.")
    }
}
var person = new Person("Nicholas");
var notAPerson = Person.call(person, "Michael"); // works!

new.target 元属性

metaproperty是给一个非对像提供属性。当[[Construct]]调用时, new.target会设置为当前构造函数,

function Person(name) {
    if (typeof new.target !== "undefined") {
        this.name = name;
        console.log(new.target)
    } else {
        throw new Error("You must use new with Person.")
    }
}
var person = new Person("Nicholas");

[Function: Person]

块级函数

在ECMAScript5 strict mode是不允许的

"use strict";
if (true) {
// throws a syntax error in ES5, not so in ES6
    function doSomething() {
        // empty
    }
}

ECMAScript 6

"use strict";
if (true) {
    console.log(typeof doSomething); // "function"
    function doSomething() {
        // empty
    }
    doSomething();
}
console.log(typeof doSomething); // "undefined"

"use strict";
if (true) {
    console.log(typeof doSomething); // throws an error
    let doSomething = function () {
        // empty
    }
    doSomething();
}
console.log(typeof doSomething);

Non-strict mode会将block function提升为global环境

// ECMAScript 6 behavior
if (true) {
    console.log(typeof doSomething); // "function"
    function doSomething() {
    // empty
    }
    doSomething();
}
console.log(typeof doSomething); // "function"

Arrow Functions

  • No this, super, arguments, and new.target bindings, 这些值为包含Arrow的非Arrow函数(闭包中的作用域)
  • Cannot be called with new
  • No prototype
  • Can’t change this this的值不能改变,在整体函数生命周期中都保持一样
  • No arguments object 因为arrow函数没有绑定arguments对像,只能依整已命名参数和rest参数
  • No duplicate named parameters arrow不管在strict和non-strict都不能有重名的参数,但在non-arrow函数在non-strict是可以的

array函数语法

let reflect = value => value;
// effectively equivalent to:
let reflect = function(value) {
    return value;
};

let sum = (num1, num2) => num1 + num2;
// effectively equivalent to:
let sum = function(num1, num2) {
    return num1 + num2;
};

let getName = () => "Nicholas";
// effectively equivalent to:
let getName = function() {
    return "Nicholas";
};

let sum = (num1, num2) => {
    return num1 + num2;
};
// effectively equivalent to:
let sum = function(num1, num2) {
    return num1 + num2;
};

let doNothing = () => {};
// effectively equivalent to:
let doNothing = function() {};

Creating Immediately Invoked Function Expressions(IIFES)

let person = function(name) {
    return {
        getName: function() {
            return name;
        }
    };
}("Nicholas");
console.log(person.getName()); // "Nicholas"

也可以是以下的方式

let person = ((name) => {
return {
    getName: function() {
        return name;
    }
    };
})("Nicholas");
console.log(person.getName()); // "Nicholas"

No this Binding
Javascript常见的一种错误是函数this的绑定. 因为this在单个函数内的改变依赖于调用它的上下文.考虑以下的代码

let PageHandler = {
    id: "123456",
    init: function() {
        document.addEventListener("click", function(event) {
            this.doSomething(event.type); // error  this绑定到为document
        }, false);
    },
    doSomething: function(type) {
        console.log("Handling " + type + " for " + this.id);
    }
};

let PageHandler = {
    id: "123456",
    init: function() {
    document.addEventListener("click",
        event => this.doSomething(event.type), false);
    },
    doSomething: function(type) {
        console.log("Handling " + type + " for " + this.id);
    }
};

**Arrow Functions and Arrows

var result = values.sort(function(a, b) {
    return a - b;
});
var result = values.sort((a, b) => a - b);

No arguments Binding
虽然arrow不拥有自己的arguments对像,但它可以访问包含它的函数的arguments

function createArrowFunctionReturningFirstArg() {
    return () => arguments[0];
}
var arrowFunction = createArrowFunctionReturningFirstArg(5);
console.log(arrowFunction()); // 5

Tail Call Optimization

ECMAScript6对引擎tail call system的优化,当一个函数的最后一个语句是调用别一个函数,称为tail call.

function doSomething() {
    return doSomethingElse(); // tail call
}

Tail call在ECMAScript5的调用跟其它函数的调用处理是一样的,一个新的stack被创建,并且添加到调用堆中. 这意味着之前的函数也要保留在内存里。

ECMAScript6 Tail call的不同
在strict mode下,es6减小了调用栈。创建一个新的stack, 然后创建删除当前stock, 但需要符合以下条件

  • tail call 不在引用当前stock中的变量(即函数不是闭包的
  • 当前函数不依赖调用函数返回的结果
  • tail call 作为当前函数的返回值
"use strict";
function doSomething() {
    // optimized
    return doSomethingElse();
}

"use strict";
function doSomething() {
    // not optimized - no return
    doSomethingElse();
}

"use strict";
function doSomething() {
    // not optimized - must add after returning
    return 1 + doSomethingElse();
}

"use strict";
function doSomething() {
    var num = 1,
    func = () => num;
    // not optimized - function is a closure
    return func();
}

治理Tail call Optimization
通常, tail call optimization是不需要,主要用于递归调用.

function factorial(n) {
    if (n <= 1) {
        return 1;
    } else {
        // not optimized - must multiply after returning
        return n * factorial(n - 1);
    }
}


function factorial(n, p = 1) {
    if (n <= 1) {
        return 1 * p;
    } else {
        let result = n * p;
        // optimized
        return factorial(n - 1, result);
    }
}

4 Expanded Object Functionality

function createPerson(name, age) {
    return {
        name,
        age
    };
}

var person = {
    name: "Nicholas",
    sayName: function() {
        console.log(this.name);
    }
};

var person = {
    name: "Nicholas",
    sayName() {
        console.log(this.name);
    }
};

计算属性名

let lastName = "last name";
let person = {
    "first name": "Nicholas",
    [lastName]: "Zakas"
};
console.log(person["first name"]); // "Nicholas"
console.log(person[lastName]); // "Zakas"

var suffix = " name";
var person = {
    ["first" + suffix]: "Nicholas",
    ["last" + suffix]: "Zakas"
};
console.log(person["first name"]); // "Nicholas"
console.log(person["last name"]); // "Zakas"

New Method

Object.is() Method

console.log(+0 == -0); // true
console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
console.log(5 == 5); // true
console.log(5 == "5"); // true
console.log(5 === 5); // true
console.log(5 === "5"); // false
console.log(Object.is(5, 5)); // true
console.log(Object.is(5, "5")); // false

Object.assign() Method
ECMAScript 5

function mixin(receiver, supplier) {
    Object.keys(supplier).forEach(function(key) {
        receiver[key] = supplier[key];
    });
    return receiver;
}

通过上面的mixin方法,使得对像可以不通过继承,而获得新的属性;

function EventTarget() { /*...*/ }
EventTarget.prototype = {
    constructor: EventTarget,
    emit: function() { /*...*/ },
    on: function() { /*...*/ }
};
var myObject = {};
mixin(myObject, EventTarget.prototype);
myObject.emit("somethingChanged");

ECMAScript 6

function EventTarget1() { /*...*/ }
EventTarget1.prototype = {
    constructor: EventTarget,
    name:"event target name",
    get fullname(){
      return this.name + " get "
    },
    set fullname(a){
      this.name=  a + " event target set"
    },
    emit: function(msg) { console.log(msg + this.name)/*...*/ },
    on: function() { /*...*/ }
}
var myObject1 = {
    
   
}
Object.assign(myObject1, EventTarget1.prototype);
myObject.emit("somethingChanged");

Assign可以接收多个参数,后面的对像覆盖前面的对像

var receiver = {};
Object.assign(receiver,
    {
        type: "js",
        name: "file.js"
    },
    {
        type: "css"
    }
);
console.log(receiver.type); // "css"
console.log(receiver.name); // "file.js"

需要注意的是,Object.assign()方法,不会复制属性的访问符get or set. 将变成数据属性

var receiver = {},
    supplier = {
        get name() {
            return "file.js"
        }
    };
Object.assign(receiver, supplier);
var descriptor = Object.getOwnPropertyDescriptor(receiver, "name");
console.log(descriptor.value); // "file.js"
console.log(descriptor.get);

多个相同属性
ECMAScript5 strict mode不允许有相同的属性, 而ECMAScript6则允许

"use strict";
var person = {
    name: "Nicholas",
    name: "Greg" // no error in ES6 strict mode, syntax error in ES5 strict mode
};
console.log(person.name); // "Greg"

自有属性枚举的顺序
ECMAScript5没有定义对像属性的枚举顺序, 它用各Javascript Engine(Chrome, Firefox)自已定义. 而在ECMAScript6中严格定义了。 这影响到Object.getOwnProperyNames() and Reflect.ownKeys, object.assign(). 但for-in 和Object.keys, JSON.stringify()并不一定遵循此规定

  1. 数字按升顺排列
  2. 所有的字符键,以加入到Object时的顺序
  3. 所有的symbol键,以加入到object的顺序
var obj = {
    a: 1,
    0: 1,
    c: 1,
    2: 1,
    b: 1,
    1: 1
};
obj.d = 1;
console.log(Object.getOwnPropertyNames(obj).join("")); // "012acbd"
console.log(JSON.stringify(obj))
var result = ""
for (var k in obj){
    result += k + " "
}
console.log(result)
012acbd
{"0":1,"1":1,"2":1,"a":1,"c":1,"b":1,"d":1}
0 1 2 a c b d 

增加Prototype

修改Object的Prototype
ECMAScript 5只有一个Object.getPrototype()方法,但没有修改prototype的方法, ECMAScript6增加了Object.setPrototype()方法,允许我们修改作何对像的原型, 它有两个参数:第一个是要改变prototype的对像,第二个是将引用原型的对像

let person = {
    getGreeting() {
        return "Hello";
    }
};
let dog = {
    getGreeting() {
        return "Woof";
    }
};
// prototype is person
let friend = Object.create(person);
console.log(friend.getGreeting()); // "Hello"
console.log(Object.getPrototypeOf(friend) === person); // true
// set prototype to dog
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting()); // "Woof"
console.log(Object.getPrototypeOf(friend) === dog); // true

Super
ECMAScript5

let person = {
    getGreeting() {
        return "Hello";
    }
};
let dog = {
    getGreeting() {
        return "Woof";
    }
};
let friend = {
    getGreeting() {
        return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
    }
};
// set prototype to person
Object.setPrototypeOf(friend, person);
console.log(friend.getGreeting()); // "Hello, hi!"
console.log(Object.getPrototypeOf(friend) === person); // true
// set prototype to dog
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting()); // "Woof, hi!"
console.log(Object.getPrototypeOf(friend) === dog); // true

ECMAScript6,则定义了super,它是当前对像prototype的指针, 类假于Object.getPrototypeOf(this).

let friend = {
    getGreeting() {
        // in the previous example, this is the same as:
        // Object.getPrototypeOf(this).getGreeting.call(this)
        return super.getGreeting() + ", hi!";
    }
};


super只能在concise方法(简明方法,ECMAScript6,定义对像方法的一种)中调用, 否则出错

var person = {
    getGreeting() {
        return "Hello";
    }
};
var friend = {
  
    getGreeting() {
        // syntax error
       // console.log(super)
        return super.getGreeting() + ", Hi!"
    },
    getGreeting1: function(){
        //调用super将发生错误
        //return super.getGreeting() + ", Hi!"
        return this.getGreeting() + ", Hi!"
    }
  
};
Object.setPrototypeOf(friend, person)
var f = Object.create(friend)
console.log(f.getGreeting())
console.log(f.getGreeting1())
Hello, Hi!
Hello, Hi!, Hi!

super方法在多层继承中非常有用,因为Object.getPrototypeOf()不能应付以下的情况

let person = {
    getGreeting() {
        return "Hello";
    }
};
// prototype is person
let friend = {
    getGreeting() {
        return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
    }
};
Object.setPrototypeOf(friend, person);
// prototype is friend
let relative = Object.create(friend);
console.log(person.getGreeting()); // "Hello"
console.log(friend.getGreeting()); // "Hello, hi!"
console.log(relative.getGreeting()); // error! 因为relative的原型是friend方法,getGreeting()调用了friend中的getGreeting(), 而这个方法中的this, 又是relative. 导致循环调用,发生错误

方法的正式定义

在ECMAScript6之前,对像的方法跟属性是一样的,而在ECMAScript6正式有方法的定义, 定义的方法内部有一个[[HomeObject]]属性,它的值为包含此方法的对像

let person = {
    // method, [[HomeObject]] is person, 这也是为什么简名方法中为什么可以使用super,因为它引用了HomeObject的原型
    getGreeting() {
        return "Hello";
    }
};
// not a method
function shareGreeting() {
    return "Hi!";
}

5 更简单的数据访问 - 析构

ECMAScript 5访问对像数据

let options = {
    repeat: true,
    save: false
};
// extract data from the object
let repeat = options.repeat,
save = options.save;

EMCAScript 6析构

let node = {
    type: "Identifier",
    name: "foo"
};
let { type, name } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"

构构时必须指定initialzer, 非则出错

// syntax error!
var { type, name };
// syntax error!
let { type, name };
// syntax error!
const { type, name };

析构用于赋值

let node = {
    type: "Identifier",
    name: "foo"
},
type = "Literal",
name = 5;
// assign different values using destructuring
({ type, name } = node);  //{}表示的是一个块语句,必须以圆括号包围
console.log(type); // "Identifier"
console.log(name); // "foo"
let node = {
    type: "Identifier",
    name: "foo"
},
type = "Literal",
name = 5;
function outputInfo(value) {
    console.log(value === node); // true
}
outputInfo({ type, name } = node);  //node传递给outputInfo 
console.log(type); // "Identifier"
console.log(name); // "foo"

默认值

let node = {
    type: "Identifier",
    name: "foo"
};
let { type, name, value } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // undefined

let node = {
    type: "Identifier",
    name: "foo"
};
let { type, name, value = true } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // true

别名

let node = {
    type: "Identifier",
    name: "foo"
};
let { type: localType, name: localName } = node;  //声明了localType and localName variables
console.log(localType); // "Identifier"
console.log(localName); // "foo

let node = {
    type: "Identifier"
};
let { type: localType, name: localName = "bar" } = node;
console.log(localType); // "Identifier"
console.log(localName); // "bar"

嵌套对像析构

let node = {
    type: "Identifier",
    name: "foo",
    loc: {
        start: {
            line: 1,
            column: 1
        },
        end: {
            line: 1,
            column: 4
        }
    }
};
let { loc: { start }} = node;
console.log(start.line); // 1
console.log(start.column); // 1


let node = {
    type: "Identifier",
    name: "foo",
    loc: {
        start: {
            line: 1,
            column: 1
        },
        end: {
            line: 1,
            column: 4
        }
    }
};
// extract node.loc.start
let { loc: { start: localStart }} = node;
console.log(localStart.line); // 1
console.log(localStart.column); // 1

Array析构

let colors = [ "red", "green", "blue" ];
let [ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

let colors = [ "red", "green", "blue" ];
let [ , , thirdColor ] = colors;
console.log(thirdColor); // "blue"

赋值

let colors = [ "red", "green", "blue" ],
firstColor = "black",
secondColor = "purple";
[ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

// swapping variables in ECMAScript 6
let a = 1,
b = 2;
[ a, b ] = [ b, a ];
console.log(a); // 2
console.log(b); // 1

默认值

let colors = [ "red" ];
let [ firstColor, secondColor = "green" ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

嵌套

let colors = [ "red", [ "green", "lightgreen" ], "blue" ];
// later
let [ firstColor, [ secondColor ] ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

Rest Item

let colors = [ "red", "green", "blue" ];
let [ firstColor, ...restColors ] = colors;
console.log(firstColor); // "red"
console.log(restColors.length); // 2
console.log(restColors[0]); // "green"
console.log(restColors[1]); // "blue"

ECMAScript5 clone

// cloning an array in ECMAScript 5
var colors = [ "red", "green", "blue" ];
var clonedColors = colors.concat();
console.log(clonedColors); // "[red,green,blue]"

ECMAScript6 clone

// cloning an array in ECMAScript 6
let colors = [ "red", "green", "blue" ];
let [ ...clonedColors ] = colors;
console.log(clonedColors); // "[red,green,blue]"

混合析构

let node = {
    type: "Identifier",
    name: "foo",
    loc: {
        start: {
            line: 1,
            column: 1
        },
        end: {
            line: 1,
            column: 4
        }
    },
    range: [0, 3]
};
let {
    loc: { start },
    range: [ startIndex ]
} = node;
console.log(start.line); // 1
console.log(start.column); // 1
console.log(startIndex); // 0

参数析构

// properties on options represent additional parameters
function setCookie(name, value, options) {
    options = options || {};
    let secure = options.secure,
    path = options.path,
    domain = options.domain,
    expires = options.expires;
    Destructuring for Easier Data Access 95
    // code to set the cookie
}
// third argument maps to options
setCookie("type", "js", {
    secure: true,
    expires: 60000
});

//ECMAScript 6
function setCookie(name, value, { secure, path, domain, expires }) {
    // code to set the cookie
   //如果path, domain没有指定,则为undefined
}
setCookie("type", "js", {
    secure: true,
    expires: 60000
});
// error!
setCookie("type", "js");
//fix
function setCookie(name, value, { secure, path, domain, expires } = {}) {
    // empty
}

析构参数提供默认值

function setCookie(name, value,
{
    secure = false,
    path = "/",
    domain = "example.com",
    expires = new Date(Date.now() + 360000000)
} = {}
) {
    // empty
}

6 Symbols and Symbol Properties

ECMAScript6引入了symbols作为元类型, 原有的类型为strings, numbers, Booleans, null, and undefined 5种类型, Symbols可以作为私有对像的成员(javascript一直想要的私有特性).

创建Symbol

note: symbols为元类型,所以调用new Symbol()会发生错误

var firstName = Symbol();  //为了使用必须分配给一个变量
var person = {}
person[firstName] = "Nicholas";
console.log(person[firstName]); // "Nicholas"
Nicholas

Symbol函数也可以接受参数,用于symbol的描述

let firstName = Symbol("first name");
let person = {};
person[firstName] = "Nicholas";
console.log("first name" in person); // false
console.log(person[firstName]); // "Nicholas"
console.log(firstName); // "Symbol(first name)"
var firstName = Symbol("first name");
var person = {};
person[firstName] = "Nicholas";
console.log("first name" in person); // false
console.log(person[firstName]); // "Nicholas"
console.log(firstName); // "Symbol(first name)" 保存在Symbol内部的[[Description]]属性中
false
Nicholas
Symbol(first name)

使用Symbol

let firstName = Symbol("first name");
// use a computed object literal property
let person = {
    [firstName]: "Nicholas"
};
// make the property read only
Object.defineProperty(person, firstName, { writable: false });
let lastName = Symbol("last name");
Object.defineProperties(person, {
    [lastName]: {
        value: "Zakas",
        writable: false
    }
});
console.log(person[firstName]); // "Nicholas"
console.log(person[lastName]); // "Zakas"

共享Symbols

多个javascript文件共享同一个symbol

let uid = Symbol.for("uid");  //create symbol
let object = {
    [uid]: "12345"
};
console.log(object[uid]); // "12345"
console.log(uid); // "Symbol(uid)"
let uid2 = Symbol.for("uid");  //retrieve symbol from global symbol registry
console.log(uid === uid2); // true
console.log(object[uid2]); // "12345"
console.log(uid2); // "Symbol(uid)"

Symbol.keyFor

let uid = Symbol.for("uid");
console.log(Symbol.keyFor(uid)); // "uid"
let uid2 = Symbol.for("uid");
console.log(Symbol.keyFor(uid2)); // "uid"
let uid3 = Symbol("uid");
console.log(Symbol.keyFor(uid3)); // undefined

Symbol强制转换
Symbol不能与其它类型进行转换,但可以调用String()获取它的字符串

let uid = Symbol.for("uid"), 
desc = String(uid); //uid.toString()
console.log(desc); // "Symbol(uid)"

var uid = Symbol.for("uid"),
desc = uid + ""; // error!  不允许连接字符串 

var uid = Symbol.for("uid"),
sum = uid / 1; // error!

获取symbol属性
Object.keys() 和Object.getOwnPropertyNames() 可以获取对像的所有属性和方法。 但symbol属性不能获取。需要通过Object.getOwnPropertySymbols()获取

let uid = Symbol.for("uid");
let object = {
    [uid]: "12345"
};
let symbols = Object.getOwnPropertySymbols(object);
console.log(symbols.length); // 1
console.log(symbols[0]); // "Symbol(uid)"
console.log(object[symbols[0]]); // "12345"

ECMAScript 6预定义Symbol常量

ECMAScript6预定义了一些Symbol,用于处理语言的内部逻辑.

Symbol.hasInstance 每一个函数Function内部都有一个Symbol.hasInstance的方法,用于确定给定的对像是否为一个函数的实例,Symbol.hasInstance是定义在Function.prototype, 所以所有的函数继承了默认的行为. Symbol.hasInstance定义为不可改,不可配置,不可枚举,以确保不会发生重定的错误, Symbol.hasInstance方法接受单个参数,用于确定的类型

obj instanceof Array;  
//Equivalent for the follwoing, 
Array[Symbol.hasInstance](obj);

ECMAScript6重新定义了instanceof操作符,它调用了Symbol.hasInstance方法. 所以你可以改变instanceof

function MyObject() {
// empty
}
Object.defineProperty(MyObject, Symbol.hasInstance, {
    value: function(v) {  //Symbol.hasInstance属性的值value是一个方法,这个方法接收一个参数
        return false;
    }
});
let obj = new MyObject();
console.log(obj instanceof MyObject); // false

必须使用Object.defineProperty()来重写nonwritable属性。所以这里的Symbol.hasInstance方法是一个新的方法。

function MyObject() {
    
}
Object.defineProperty(MyObject, "hello", {
    value: "hello !",
    writable: false
})
console.log(MyObject.hello)
MyObject.hello = "test"
console.log(MyObject.hello)
hello !!!
hello !!!
function SpecialNumber() {
// empty
}
Object.defineProperty(SpecialNumber, Symbol.hasInstance, {
    value: function(v) {
        return (v instanceof Number) && (v >=1 && v <= 100);
    }
});
var two = new Number(2),
zero = new Number(0);
console.log(two instanceof SpecialNumber); // true
console.log(zero instanceof SpecialNumber); // false
true
false

Symbol.isConcatSpreadable

数组的concat方法用来连接两个数组, 它的参数可以是数组,也可以是单独的元素

let colors1 = [ "red", "green" ],
colors2 = colors1.concat([ "blue", "black" ]);  //返回新的元素 
console.log(colors2.length); // 4
console.log(colors2); // ["red","green","blue","black"]
let colors1 = [ "red", "green" ],
colors2 = colors1.concat([ "blue", "black" ], "brown");  //传递brown
console.log(colors2.length); // 5
console.log(colors2); // ["red","green","blue","black","brown"]

数组可以划分成独立的元素,而其它的类型则不可以。在ECMAScript6之前,其它的类型不能用于concat方法中. Symbol.isConcatSpreadable是一个布尔值. 对像必须包含length属性和数字key. 数字key就是可以添加到concat方法中的元素, 不同于其它预定义的Symbol, 这个symbol属性不会出现在标准对像中, 这个属性只是啬concat()方法


//length and tow numberic keys
let collection = {
    0: "Hello",
    1: "world",
    length: 2,
    [Symbol.isConcatSpreadable]: true
};
let messages = [ "Hi" ].concat(collection);
console.log(messages.length); // 3
console.log(messages); // ["hi","Hello","world"]

Symbol.match, Symbol.replace, Symbol.search, and Symbol.split Properties

Javascript中字符串和正常表达式关系非常接近,许多string方法接受字符串也接受regexp表达式作为参数

  • match(regex)
  • replace(regex, replacement)
  • search(regex)
  • split(regex)
    在ECMAScript6之前,开发者是不清楚string如何与正则表达式交互的,没有办法让自定义对像实现相同的功能。ECMAScript6定义了四个symbols用于上面的方法,实现跟RegExp的行为
let hasLengthOf10 = {
    [Symbol.match]: function(value) {
        return value.length === 10 ? [value.substring(0, 10)] : null;
    },
    [Symbol.replace]: function(value, replacement) {
        return value.length === 10 ? replacement + value.substring(10) : value;
    },
    [Symbol.search]: function(value) {
        return value.length === 10 ? 0 : -1;
    },
    [Symbol.split]: function(value) {
        return value.length === 10 ? ["", ""] : [value];
    }
};
let message1 = "Hello world", // 11 characters
message2 = "Hello John"; // 10 characters
let match1 = message1.match(hasLengthOf10),
match2 = message2.match(hasLengthOf10);
console.log(match1); // null
console.log(match2); // ["Hello John"]
let replace1 = message1.replace(hasLengthOf10),
replace2 = message2.replace(hasLengthOf10);
console.log(replace1); // "Hello world"
console.log(replace2); // "Hello John"
let search1 = message1.search(hasLengthOf10),
search2 = message2.search(hasLengthOf10);
console.log(search1); // -1
console.log(search2); // 0
let split1 = message1.split(hasLengthOf10),
split2 = message2.split(hasLengthOf10);
console.log(split1); // ["Hello world"]
console.log(split2); // ["", ""]

Symbol.toPrimitive Method

Javascript经常需要将一个对像转换为原始值, 比如使用 (==) 比较一个字符串和object。

function Temperature(degrees) {
    this.degrees = degrees;
}
Temperature.prototype[Symbol.toPrimitive] = function(hint) {
    switch (hint) {
        case "string":
            return this.degrees + "\u00b0"; // degrees symbol
        case "number":
            return this.degrees;
        case "default":
            return this.degrees + " degrees";
    }
};
var freezing = new Temperature(32);
console.log(freezing + "!"); // "32 degrees!"
console.log(freezing / 2); // 16
console.log(String(freezing)); // "32°"

Symbol.toStringTag Property

在Javascript中一个非常有趣的问题是:有多个全局执行环境中(web page包含iframe), 很多时候这不是什么问题,因为数据可以相互间传递,但如果你需要确定一个对像的类型时,就会发生问题

ECMAScript5使用以下面的方法在两个不同javascript中确定对像类型

function isArray(value) {
    return Object.prototype.toString.call(value) === "[object Array]";
}
console.log(isArray([])); // true

在ECMAScript5之前,开发者都是使用json2.js库用于JSON操作,它创建了一个全局的JSON对像,随着浏览器的发着JSON对像, 用以下的方法进行区分

function supportsNativeJSON() {
    return typeof JSON !== "undefined" && Object.prototype.toString.call(JSON) === "[object JSON]";
    //library json返回[object Object]
}

ECMAScript6 可以通过Symbol.toStringTag来定义[object Object]中的名字

function Person(name) {
    this.name = name;
}
Person.prototype[Symbol.toStringTag] = "Person";
var me = new Person("Nicholas");
console.log(me.toString()); // "[object Person]"
console.log(Object.prototype.toString.call(me)); // "[object Person]"

由于Person.prototype继承于Object.prototype.toString()方法,所以都会调用Symbol.toStringTag中的传,但是你可以自己定义toString方法

function Person(name) {
    this.name = name;
}
Person.prototype[Symbol.toStringTag] = "Person";
Person.prototype.toString = function() {
    return this.name;
};
var me = new Person("Nicholas");
console.log(me.toString()); // "Nicholas"
console.log(Object.prototype.toString.call(me)); // "[object Person]"

Symbol.unscopables Property

用于with操作符

var values = [1, 2, 3],
colors = ["red", "green", "blue"],
color = "black";
with(colors) {
    push(color);  //colors.push()
    push(...values);
}
console.log(colors); // ["red", "green", "blue", "black", 1, 2, 3]

但ECMAScript6为Array添加了一个values的方法。在ECMAScript6环境中, with语句,values不在引用本地变量,而是colors.values方法,这就会导至代码退出,这就是为什么要引用Symbolunscopables的原因

Symbol.unscopables用于表示哪些属性不需要出现在with语句中

// built into ECMAScript 6 by default
Array.prototype[Symbol.unscopables] = Object.assign(Object.create(null), {
    copyWithin: true,
    entries: true,
    fill: true,
    find: true,
    findIndex: true,
    keys: true,
    values: true
});

7 Sets and Maps

ECMAScript6之前只有array和object用来表示collection数据。现在增加了set和map. set是一个列表,它不包含重复的值,通常不需要像array那样单独访问其中的元素,而是确定一个值是否存在。map是邮key和value组成。

Sets and Map in ECMAScript5

var set = Object.create(null); //必须为null prototype, 保证不从object中继承作何属性
set.foo = true;
// checking for existence
if (set.foo) {
    // code to execute
}

添加到set中的属性都为true,这样就能确定一个值是否存在

var map = Object.create(null);
map.foo = "bar";
// retrieving a value
var value = map.foo;
console.log(value); // "bar"

存在的问题
以上的情况适用于简单的情况,但对于复杂的问题,属性就不能解决,比如

var map = Object.create(null);
map[5] = "foo";
console.log(map["5"]); // "foo"

var map = Object.create(null),
key1 = {},
key2 = {};
map[key1] = "foo";
console.log(map[key2]); // "foo"

key1和key2引用了相同的值,key1和key2都转换为了相同的字符串[object Object]

var map = Object.create(null);
map.count = 1;
// checking for the existence of "count" or for a nonzero value?
if (map.count) { //count=0没有办法确定是否包含count
// code to execute
}

ECMAScript 6

let set = new Set();
set.add(5);
set.add("5");
console.log(set.size); // 2

Set不会强制转换值的类型,所以5和"5"是不同的值, 它内部是调用Object.is()方法来确定两个值是否相等

let set = new Set(),
key1 = {},
key2 = {};
set.add(key1);
set.add(key2);
console.log(set.size); // 2
let set = new Set();
set.add(5);
set.add("5");
set.add(5); // duplicate - this is ignored
console.log(set.size); // 2

也可能通过一个数组来初始化set(iterable object), 这对于json来说非常有用,可以用来去除重复的数据

let set = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
console.log(set.size); // 5

** Get **

let set = new Set();
set.add(5);
set.add("5");
console.log(set.has(5)); // true
console.log(set.has(6)); // false

** 删除元素 **
可以通过delete()方法删除单个元素,或者调用clear()方法清空

let set = new Set();
set.add(5);
set.add("5");
console.log(set.has(5)); // true
set.delete(5);
console.log(set.has(5)); // false
console.log(set.size); // 1
set.clear();
console.log(set.has("5")); // false
console.log(set.size); // 0

** forEach() for Sets**

ECMAScript5为array添加了forEach()方法. 对于set的forEach方法,接收以下三个参数

  • Set中的下一个值
  • 跟第一个参数相同的值
  • set本身

Set与Array和Map中的第二个参数是不同的,Array和Map的第二个参数是Key. 而set是没有key.

var s = new Set([1, 2]);
s.forEach(function(value, key, ownerSet) {
    console.log(key + " " + value);
    console.log(ownerSet === s);
});
1 1
true
2 2
true

跟Array一样,你可以将上下文传递给forEach的第二个参数

let set = new Set([1, 2]);
let processor = {
    output(value) {
        console.log(value);
    },
    process(dataSet) {
        dataSet.forEach(function(value) {
            this.output(value);
        }, this);
    }
};
processor.process(set);

或者

let set = new Set([1, 2]);
let processor = {
    output(value) {
    console.log(value);
    },
    process(dataSet) {
        dataSet.forEach(value => this.output(value));
    }
};
processor.process(set);

虽然可以能过forEach来访问set中的值,但是你不能直接访问它单个的值,如果有需要,可以将Set转换为Array

Converting a Set to an Array

let set = new Set([1, 2, 3, 3, 3, 4, 5]),
array = [...set];  //通过...将一个set转换为array
console.log(array); // [1,2,3,4,5]

去掉数组中的重复元素

function eliminateDuplicates(items) {
    return [...new Set(items)];
}
let numbers = [1, 2, 3, 3, 3, 4, 5],
noDuplicates = eliminateDuplicates(numbers);
console.log(noDuplicates); // [1,2,3,4,5]

**Weak Sets **

由于Set可以保存整个object, 相当于object赋值给一个变量,所以一个set引用了一个对像时,它是不会被自动垃级回收

let set = new Set(),
key = {};
set.add(key);
console.log(set.size); // 1
// eliminate original reference
key = null;
console.log(set.size); // 1
// get the original reference back
key = [...set][0];

在这个例子中,key设置为空, 清空了变量key, 但是对像依然保存在set中。依然可以通过将set转换为数组后,找回这个key. 这对于大多数语言来说是很好的,但有的时候,希望set不应该在保留这个值. 比如一个DOM element保存在set中,将其它javascript将这个dom删除后,set中也应该删除这个对像. 为些ECMAScript6引入了 weak sets, 它仅保存弱对像的引用,而不能保存原始值, 一个weak reference引用的对像,不会破坏垃圾回收。

var s1 = new Set(),
key = {};
s1.add(key);
console.log(s1.size); // 1
// eliminate original reference
key = null;
console.log(s1.size); // 1
// get the original reference back
key = [...s1][0];
console.log(key)
1
1
{}
//Create Weak Sets
var s1 = new WeakSet(),
key = {};
// add the object to the set
s1.add(key);
console.log(s1.has(key)); // true
s1.delete(key);
console.log(s1.has(key)); // false

var key1 = {},
key2 = {},
s2 = new WeakSet([key1, key2]);
console.log(s2.has(key1)); // true
console.log(s2.has(key2)); // true
true
false
true
true

Weak set与正常set的不同

let set = new WeakSet(),
key = {};
// add the object to the set
set.add(key);
console.log(set.has(key)); // true
// remove the last strong reference to key (also removes from weak set)
key = null;
var s1 = new WeakSet(),
key = {};
// add the object to the set
s1.add(key);
console.log(s1.has(key)); // true
// remove the last strong reference to key (also removes from weak set)
key = null;
true





null

Weakset的特点:

  • add(), has(), delete()的参数必须为对像,否则报错
  • Weakset不能iterables, 所以不能使用for of循环
  • Weakset set没有向外暴露作何的iterator, 比如keys() and values()方法, 所以不能确定weak set中保存了哪些值
  • Weakset 不能使用forEach方法
  • Weakset没有size属性
    如果你仅仅是想跟踪对像的引用,可以使用weak set代替正常的set.

Maps in ECMAScript 6

ECMAScript6的map类型是一个key-value有序集合.

let map = new Map();
map.set("title", "Understanding ECMAScript 6");
map.set("year", 2016);
console.log(map.get("title")); // "Understanding ECMAScript 6"
console.log(map.get("year")); // 2016

也可以使用对像作为key

let map = new Map(),
key1 = {},
key2 = {};
map.set(key1, 5);
map.set(key2, 42);
console.log(map.get(key1)); // 5
console.log(map.get(key2)); // 42

Map methods

  • has(key) 确定一个key是否存在于map中
  • delete(key) 从map中删除相关的key-value
  • clear() 清空map中的值
  • size 属性
let map = new Map();
map.set("name", "Nicholas");
map.set("age", 25);
console.log(map.size); // 2
console.log(map.has("name")); // true
console.log(map.get("name")); // "Nicholas"
console.log(map.has("age")); // true
console.log(map.get("age")); // 25
map.delete("name");
console.log(map.has("name")); // false
console.log(map.get("name")); // undefined
console.log(map.size); // 1
map.clear();
console.log(map.has("name")); // false
console.log(map.get("name")); // undefined
console.log(map.has("age")); // false
console.log(map.get("age")); // undefined
console.log(map.size); // 0

Map初始化

let map = new Map([["name", "Nicholas"], ["age", 25]]);
console.log(map.has("name")); // true
console.log(map.get("name")); // "Nicholas"
console.log(map.has("age")); // true
console.log(map.get("age")); // 25
console.log(map.size); // 2

forEach

var map = new Map([["name", "Nicholas"], ["age", 25]]);
map.forEach(function(value, key, ownerMap) {
    console.log(key + " " + value);
    console.log(ownerMap === map);
});
name Nicholas
true
age 25
true

Weak Maps

Weak maps类似于weak set, 保存weak对像的引用. 在Weak Map中,每一个key必须是一个对像, 如果是非object,对发生错误。当key引用的对像删除后,key-value也将删除和.如果weak map中的值为weak object, 则依然会防止垃圾回收. Weak map常用于引用web page中的dom元素

let map = new WeakMap(),
element = document.querySelector(".element");
map.set(element, "Original");
// remove the element
element.parentNode.removeChild(element);
element = null;
// the weak map is empty at this point

weak map initialization

let key1 = {},
key2 = {},
map = new WeakMap([[key1, "Hello"], [key2, 42]]);
console.log(map.has(key1)); // true
console.log(map.get(key1)); // "Hello"
console.log(map.has(key2)); // true
console.log(map.get(key2)); // 42

weak map methods
weak map有has, get, delete方法,但没有clear方法,因为clear需要枚举keys()

let map = new WeakMap(),
element = document.querySelector(".element");
map.set(element, "Original");
console.log(map.has(element)); // true
console.log(map.get(element)); // "Original"
map.delete(element);
console.log(map.has(element)); // false
console.log(map.get(element)); // undefined

对像的私有数据
虽然大部分开发者使用weak map来操作dom 元素,但还有许多其它的用法。其中一种就是用来保存对像的私有数据。在ECMAScript6中,所有对像的属性都是public. 所以你需要使用至一些技巧来创建私有数据.

function Person(name) {
    this._name = name;
}
Person.prototype.getName = function() {
    return this._name;
};

以上的方法使用 _来约定私有属性,但它可以被重写, ECMAScript5使用闭包来保护私有数据

var Person = (function() {
    var privateData = {},
    privateId = 0;
    function Person(name) {
        Object.defineProperty(this, "_id", { value: privateId++ });
        privateData[this._id] = {
            name: name
        };
    }
    Person.prototype.getName = function() {
        return privateData[this._id].name;
    };
    return Person;
}());
var p = new Person("hello")
p.name="aaa"
console.log(p.getName())
hello

以上的方法可以保证name属性不能修改,所有的数据保存在内部的privateData对像中,但问题是,当一个对像被删除后,privateData依然保存相关的数据,所以可以通过Weak map替换

let Person = (function() {
    let privateData = new WeakMap();
    function Person(name) {
        privateData.set(this, { name: name });
    }
    Person.prototype.getName = function() {
        return privateData.get(this).name;
    };
    return Person;
}());

8 Iterators And Generators

Interator是数据处理的基础,比如set, maps, for-of, spread operator(…)都使用了interator.

现在循环的问题

var colors = ["red", "green", "blue"];
for (var i = 0, len = colors.length; i < len; i++) {
    console.log(colors[i]);
}

虽然这个循环非常简单明了, 当需要有嵌套循环时,则需要使用到多个变量(i)来追踪当前的遍历. 额外的复杂性,增加错误的可能。这时可以使用iterator来消除这种错误。

什么是Iterators?

Iterators是一个对像符合了iteration的接口设计,所有的iterator 对像有一个next()方法,它返回一个结果对像。这个结果对像有两个属性, value: 表现下一个值, done属性,如果done为true,则表示没有更多的值.

根据以上信息, ECMAScript5 创建一个interator

function createIterator(items) {
    var i = 0;
    return {
        next: function() {
            var done = (i >= items.length);
            var value = !done ? items[i++] : undefined;
                return {
                    done: done,
                    value: value
                };
        }
   };
}
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// for all further calls
console.log(iterator.next()); // "{ value: undefined, done: true }"

什么是Generators

一个generator是一个函数,它返回一个iterator. generator 函数是函数名为*为引导,并且使用新的关键词yeild.

// generator
function *createIterator() {
    yield 1;
    yield 2;
    yield 3;
}
// generators are called like regular functions but return an iterator
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

yield用来表示iterator, 在调用next()方法时,应该返回的值. 在yeild 1之后,程序停止执行,直到下一次调用iterator.next才会执行yeild2.

function *createIterator(items) {
    for (let i = 0; i < items.length; i++) {
        yield items[i];
    }
}
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// for all further calls
console.log(iterator.next()); // "{ value: undefined, done: true }"
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }
{ value: undefined, done: true }

Note: yeild只能跟generator一起使用,以下的代码是错误的, yield不能跨函数边界.

function *createIterator(items) {
    items.forEach(function(item) {
        // syntax error
        yield item + 1;
    });
}

** Generator 函数表达式**

let createIterator = function *(items) {
    for (let i = 0; i < items.length; i++) {
        yield items[i];
    }
};
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// for all further calls
console.log(iterator.next()); // "{ value: undefined, done: true }"

** Generator方法 **

let o = {
    createIterator: function *(items) {
        for (let i = 0; i < items.length; i++) {
            yield items[i];
        }
    }
};
let iterator = o.createIterator([1, 2, 3]);
let o = {
    *createIterator(items) {
        for (let i = 0; i < items.length; i++) {
            yield items[i];
        }
    }
};
let iterator = o.createIterator([1, 2, 3]);

Iterables and for-of loops

一个对像,通过Symbol.iterator属性,表示它是可遍历的。Symbol.iterator的值是一个函数,用于返回空上对像的iterator. 所有的集合对像Array, set, maps以及string都是可遍历的。for-of循环就是用于遍历iterable对像

所有通过generators创建的iterator都是可iterable的,因为generator会默认分配Symbol.iterator属性.

在本章的开始,我们在for循环中使用index来跟踪。Iterator是解决这个问题的第一个部分,而for-of是第二个部分。它不需要index来跟踪,只需要关注数据集的数据.

for-of每次循环都调用iterable对像的next()方法.直到对像的done属性为空

let values = [1, 2, 3];
for (let num of values) {
    console.log(num);
}

for-of首先调对像的Symbol.iterator方法,获取iterator,然后调用iterator.next(), 然后iterator的返回的result对像的value赋值给num.当result对像的done为true,终值循环,所以num不会为undefined.

for-of如果用于一个non-iterable对像,null, undefined时,会抛出错误

访问默认的iterator
你可以通过Symbol.iterator来访问对像的默认iterator.

let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();

console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

确定一个对像是否可以iterable

function isIterable(object) {
    return typeof object[Symbol.iterator] === "function";
}
console.log(isIterable([1, 2, 3])); // true
console.log(isIterable("Hello")); // true
console.log(isIterable(new Map())); // true
console.log(isIterable(new Set())); // true
console.log(isIterable(new WeakMap())); // false
console.log(isIterable(new WeakSet())); // false

Create Iterable

开发者自己定义的对是默认是不可iterable的,但是你可以通过Symbol.iterator属性,它的值为generator函数,然后创建一个iterable对像

let collection = {
    items: [],
    *[Symbol.iterator]() {
        for (let item of this.items) {
            yield item;
        }
    }
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
console.log(x);
}

Build-in Iterators

在ECMAScript6中Iterator是非常重要的一个部分,所以,你不需要为许多内置类型创建自己的iterator, 你仅需要在内置的iterators不能满足你条件时,才创建。主要用于自定义的对像或者Class.

Collection Iterators

  • entries() 返回一个itertor, 它的值为key-values键值对
  • values() 返回一个iterator, 它的值为collection的值
  • keys() 返回一个iterator, 它的值为collection的key

entries
对于array,它的第一个元素是index, 对于set,两个都是value, 对于map,第一个为key

var colors = [ "red", "green", "blue" ];
var tracking = new Set([1234, 5678, 9012]);
var data = new Map();
data.set("title", "Understanding ECMAScript 6");
data.set("format", "ebook");
for (let entry of colors.entries()) {
    console.log(entry);
}
for (let entry of tracking.entries()) {
    console.log(entry);
}
for (let entry of data.entries()) {
    console.log(entry);
}
[ 0, 'red' ]
[ 1, 'green' ]
[ 2, 'blue' ]
[ 1234, 1234 ]
[ 5678, 5678 ]
[ 9012, 9012 ]
[ 'title', 'Understanding ECMAScript 6' ]
[ 'format', 'ebook' ]
var colors = [ "red", "green", "blue" ];
var tracking = new Set([1234, 5678, 9012]);
var data = new Map();
data.set("title", "Understanding ECMAScript 6");
data.set("format", "ebook");
/*
nodejs array don't have values
for (let value of colors.values()) {
    console.log(value);
}
*/
for (let value of tracking.values()) {
    console.log(value);
}
for (let value of data.values()) {
    console.log(value);
}
1234
5678
9012
Understanding ECMAScript 6
ebook

keys() Interator

var colors = [ "red", "green", "blue" ];
var tracking = new Set([1234, 5678, 9012]);
var data = new Map();
data.set("title", "Understanding ECMAScript 6");
data.set("format", "ebook");
for (let key of colors.keys()) {
    console.log(key);
}
for (let key of tracking.keys()) {
    console.log(key);
}
for (let key of data.keys()) {
    console.log(key);
}
0
1
2
1234
5678
9012
title
format

Collection 类型默认的iterator

如果没有明确指定,array and set默认的iterator为values(), 而map默的为entries().这样在使用for-of时可以更简单

var colors = [ "red", "green", "blue" ];
var tracking = new Set([1234, 5678, 9012]);
var data = new Map();
data.set("title", "Understanding ECMAScript 6");
data.set("format", "print");
// same as using colors.values()
for (let value of colors) {
    console.log(value);
}
// same as using tracking.values()
for (let num of tracking) {
    console.log(num);
}
// same as using data.entries()
for (let entry of data) {
    console.log(entry);
}
red
green
blue
1234
5678
9012
[ 'title', 'Understanding ECMAScript 6' ]
[ 'format', 'print' ]

** Destructuring and for-of Loops **

let data = new Map();
data.set("title", "Understanding ECMAScript 6");
data.set("format", "ebook");
// same as using data.entries()
for (let [key, value] of data) {
    console.log(key + "=" + value);
}

string Iterators

ECMAScript5使用[0]中括号来遍历字符串,但这种方式获取的是code unit. 而不是字符。它不能访问双字节的字符

var message = "A ? B";
for (let i=0; i < message.length; i++) {
    console.log(message[i]);
}
A
 
�
�
 
B
//ECMAScript是支持Uincode的
var message = "A ? B";
for (let c of message) {
    console.log(c);
}
A
 
?
 
B

Nodelist iterator

var divs = document.getElementsByTagName("div");
for (let div of divs) {
    console.log(div.id);
}

Spread 操作符和非数组的可遍历性

在第7章,我们使用(…)将一个set转换为一个数组

let set = new Set([1, 2, 3, 3, 3, 4, 5]),
array = [...set];
console.log(array); // [1,2,3,4,5]

spread操作符可以用于所有可遍历的对像,使用iterator获取它们的值。

var map = new Map([["name", "Nicholas"], ["age", 25]]),
array = [...map]; //Default map iterator is key-value pairs
console.log(array); // [["name", "Nicholas"], ["age", 25]]
[ [ 'name', 'Nicholas' ], [ 'age', 25 ] ]
var smallNumbers = [1, 2, 3],
bigNumbers = [100, 101, 102],
allNumbers = [0, ...smallNumbers, ...bigNumbers];
console.log(allNumbers.length); // 7
console.log(allNumbers); // [0, 1, 2, 3, 100, 101, 102]
7
[ 0, 1, 2, 3, 100, 101, 102 ]

高级的iterator函数

iterator除了用于基础的collection的遍历,在ECMAScript6可以开发出更多的功能

向iterators传递参数

function *createIterator() {
    let first = yield 1;
    console.log(first)
    let second = yield first + 2; // 4 + 2
    yield second + 3; // 5 + 3
}
var iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
//console.log(iterator.next(4)); // "{ value: 6, done: false }"
//console.log(iterator.next(5)); // "{ value: 8, done: false }"
//console.log(iterator.next()); // "{ value: undefined, done: true }"
{ value: 1, done: false }

第一次调用next()是一个特例,任何传递给next()的参数都会被忽略。因为传递给next()的参数会成为yeild返回的值,第一句next()传递的参数,不能替换掉第一条yield. 所以第一次调用next()传递的参数是没有意义的.

第二次调用next(), 值4作为参数,并分配给first变量. 一条yeild语句包含了assignment, 右边部分作为第一次调用next()的返回值. 左边部分,则作为第二次调用next()时的值, 而第二次调用传递了参数4,所以它分配给first的值为4.然后继续执行.

alt text

Throwing Errors in Iterators

不仅可以向iterator传递数据,iterator还有一个throw()方法。它使得iterator在继续执行前,抛出一个错误。这对于异步编程来说是非常重要的。

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2; // yield 4 + 2, then throw
    yield second + 3; // never is executed
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.throw(new Error("Boom"))); // error thrown from generator

alt text

当throw()调用时,任何代码执行之前,会先抛出一个错误。你可以使用try-catch来捕获

function *createIterator() {
    let first = yield 1;
    let second;
    try {
        second = yield first + 2; // yield 4 + 2, then throw
    } catch (ex) {
        second = 6; // on error, assign a different value
    }
    yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.throw(new Error("Boom"))); // "{ value: 9, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

Generator中的return 语句

function *createIterator() {
    yield 1;
    return;
    yield 2;
    yield 3;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
function *createIterator() {
    yield 1;
    return 42;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 42, done: true }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

Spread 和for-of会忽略掉return返回的值,应为done is true.

Delegating Generator

有的时候组合两个iterators中的值非常有用, generator可以委派其它generator.

function *createNumberIterator() {
    yield 1;
    yield 2;
}
function *createColorIterator() {
    yield "red";
    yield "green";
}
function *createCombinedIterator() {
    yield *createNumberIterator();
    yield *createColorIterator();
    yield true;
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "red", done: false }"
console.log(iterator.next()); // "{ value: "green", done: false }"
console.log(iterator.next()); // "{ value: true, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
function *createNumberIterator() {
    yield 1;
    yield 2;
    return 3;
}
function *createRepeatingIterator(count) {
    for (let i=0; i < count; i++) {
        yield "repeat";
    }
}

function *createCombinedIterator() {
    let result = yield *createNumberIterator();
    yield *createRepeatingIterator(result);
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

注意value 3,不会被输出, 但可以修改为如下的语句

function *createNumberIterator() {
    yield 1;
    yield 2;
    return 3;
}
function *createRepeatingIterator(count) {
    for (let i=0; i < count; i++) {
        yield "repeat";
    }
}
function *createCombinedIterator() {
    let result = yield *createNumberIterator();
    yield result;
    yield *createRepeatingIterator(result);
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

Asynchronous Task Running

let fs = require("fs");
fs.readFile("config.json", function(err, contents) {
    if (err) {
        throw err;
    }
    doSomethingWith(contents);
    console.log("Done");
});

这段代码对于简单的任务来说,没有什么问题,但对于嵌套的作务来说,或者序列化异步操作,则generator和yeild非常有用

A Simple Task Runner

function run(taskDef) {
    // create the iterator, make available elsewhere
    let task = taskDef();
    // start the task
    let result = task.next();
    // recursive function to keep calling next()
    function step() {
        // if there's more to do
        if (!result.done) {
            result = task.next();
            step();
        }
    }
    // start the process
    step();
}
run(function*() {
    console.log(1);
    yield;
    console.log(2);
    yield;
    console.log(3);
});

Task Running with Data

function run(taskDef) {
    // create the iterator, make available elsewhere
    let task = taskDef();
    // start the task
    let result = task.next();
        // recursive function to keep calling next()
        function step() {
            // if there's more to do
            if (!result.done) {
            result = task.next(result.value);
            step();
        }
    }
    // start the process
    step();
}
run(function*() {
    let value = yield 1;
    console.log(value); // 1
    value = yield value + 3;
    console.log(value); // 4
});

An Asynchronous Task Runner

function fetchData() {
    return function(callback) {
        callback(null, "Hi!");
    };
}

function fetchData() {
    return function(callback) {
        setTimeout(function() {
            callback(null, "Hi!");
        }, 50);
    };
}

function run(taskDef) {
    // create the iterator, make available elsewhere
    let task = taskDef();
    // start the task
    let result = task.next();

    // recursive function to keep calling next()
    function step() {
        // if there's more to do
        if (!result.done) {
            if (typeof result.value === "function") {
                result.value(function (err, data) {
                    if (err) {
                        result = task.throw(err);
                        return;
                    }
                    result = task.next(data);
                    step();
                });
            } else {
                result = task.next(result.value);
                step();
            }
        }
    }

    // start the process
    step();
}

let fs = require("fs");
function readFile(filename) {
    return function(callback) {
        fs.readFile(filename, callback);
    };
}
run(function*() {
    let contents = yield readFile("config.json");
    doSomethingWith(contents);
    console.log("Done");
});

在这里只是让你理解,对于异步任务,在第11章中的promise更适合.

9 Classes

ECMAScript5模似Class构造函数

function PersonType(name) {
    this.name = name;
}
PersonType.prototype.sayName = function() {
    console.log(this.name);
};
var person = new PersonType("Nicholas");
person.sayName(); // outputs "Nicholas"
console.log(person instanceof PersonType); // true
console.log(person instanceof Object); // true

Class声明

ECMAScript6 可以直接通过class来声明一个类,但ECMAScript的类,也还是不像其它语言的类.

class PersonClass {
    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
}

let person = new PersonClass("Nicholas");
person.sayName(); // outputs "Nicholas"
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true
console.log(typeof PersonClass); // "function"
console.log(typeof PersonClass.prototype.sayName); // "function"

可以在controctor中声明自有属性. 其实ECMAScript6的类,跟PersonType是一样的, 这也是typeof PersonClass是一个函数, sayName也是添加到prototype.

Class prototype是只读的,不能向prototype中赋值新的值

Why Use the Class Syntax?

class和自定义类型是相似的,为什么还要使用class, 以下是非常重要的不同点:

  • Class声明,不像函数声明,它不会提升到全局,它类似于let声明。
  • 所有Class类部的代码是自动是strict mode.
  • 所有的方法是不可枚举的,相对于自定义类型,这是具有非常重要意义的改变, 否则你需要通过Object.defineProperty()使方法变为不可枚举
  • 所有的方法都没有内部的[[Construct]]方法,当调用Class的方法时使用了new,则会抛出error.
  • 如果调用Class时,没有使用new, 会抛出error
  • 如果在class的方法里,重写class name,会抛出error.

以面的类,等于以下的代码

// direct equivalent of PersonClass
let PersonType2 = (function() {
    "use strict";
    const PersonType2 = function(name) {
// make sure the function was called with new
        if (typeof new.target === "undefined") {
            throw new Error("Constructor must be called with new.");
        }
        this.name = name;
    }
    Object.defineProperty(PersonType2.prototype, "sayName", {
        value: function() {
// make sure the method wasn't called with new
            if (typeof new.target !== "undefined") {
                throw new Error("Method cannot be called with new.");
            }
            console.log(this.name);
        },
        enumerable: false,
        writable: true,
        configurable: true
    });
    return PersonType2;
}());

Class name可以在类声明之外修改

class Foo {
    constructor() {
        Foo = "bar"; // throws an error when executed...
    }
}
// but this is okay after the class declaration
Foo = "baz";

Class Expressions

let PersonClass = class {
// equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }
    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
};
let person = new PersonClass("Nicholas");
person.sayName(); // outputs "Nicholas"
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true
console.log(typeof PersonClass); // "function"
console.log(typeof PersonClass.prototype.sayName); // "function"

Named Class Expressions

let PersonClass = class PersonClass2 {
// equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }
// equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
};
console.log(typeof PersonClass); // "function"
console.log(typeof PersonClass2); // "undefined"

在这个例子中,类表达式的名称为PersonClass2, PersonClass2只存在于class定义中,它可以用于class的方法,比如sayName(). 在class外, typeof PersonClass2为undefined, 这是因为PersonClass2在这没有绑定。它类似于下面的代码.

// direct equivalent of PersonClass named class expression
let PersonClass = (function() {
    "use strict";
    const PersonClass2 = function(name) {
    // make sure the function was called with new
        if (typeof new.target === "undefined") {
            throw new Error("Constructor must be called with new.");
        }
        this.name = name;
    }
    Object.defineProperty(PersonClass2.prototype, "sayName", {
        value: function() {
            // make sure the method wasn't called with new
            if (typeof new.target !== "undefined") {
                throw new Error("Method cannot be called with new.");
            }
            console.log(this.name);
        },
        enumerable: false,
        writable: true,
        configurable: true
    });
    return PersonClass2;
}());

在程序中,可以传递给函数作为参数,或者从函数中返回,或者分配给变量的值,就是一等公民。Javascript中,函数就是这样的值. ECMAScript 6继续了这种传递, Class也是一等公民. 可以有多种方式使用class.

function createObject(classDef) {
    return new classDef();
}
let obj = createObject(class {
    sayHi() {
        console.log("Hi!");
    }
});
obj.sayHi(); // "Hi!"

另一种使用class expressions是创建singletons实例

let person = new class {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log(this.name);
    }
}("Nicholas");
person.sayName(); // "Nicholas"

Accessor Properties

class CustomHTMLElement {
    constructor(element) {
        this.element = element;
    }
    get html() {
        return this.element.innerHTML;
    }
    set html(value) {
        this.element.innerHTML = value;
    }
}
var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, "html");
console.log("get" in descriptor); // true
console.log("set" in descriptor); // true
console.log(descriptor.enumerable); // false

CustomHTMLElement类,包装了一个DOM Elment, getter and setter 用于设置element的innserHTML. accessor方法,也是不可枚举的。 它等于以下的代码

// direct equivalent to previous example
let CustomHTMLElement = (function() {
    "use strict";
    const CustomHTMLElement = function(element) {
// make sure the function was called with new
        if (typeof new.target === "undefined") {
            throw new Error("Constructor must be called with new.");
        }
        this.element = element;
    }
    Object.defineProperty(CustomHTMLElement.prototype, "html", {
        enumerable: false,
        configurable: true,
        get: function() {
            return this.element.innerHTML;
        },
        set: function(value) {
            this.element.innerHTML = value;
        }
    });
    return CustomHTMLElement;
}());

Computed Member Names

// direct equivalent to previous example
let CustomHTMLElement = (function() {
    "use strict";
    const CustomHTMLElement = function(element) {
// make sure the function was called with new
        if (typeof new.target === "undefined") {
            throw new Error("Constructor must be called with new.");
        }
        this.element = element;
    }
    Object.defineProperty(CustomHTMLElement.prototype, "html", {
        enumerable: false,
        configurable: true,
        get: function() {
            return this.element.innerHTML;
        },
        set: function(value) {
            this.element.innerHTML = value;
        }
    });
    return CustomHTMLElement;
}());
let propertyName = "html";
class CustomHTMLElement {
    constructor(element) {
        this.element = element;
    }
    get [propertyName]() {
        return this.element.innerHTML;
    }
    set [propertyName](value) {
        this.element.innerHTML = value;
    }
}

Generator Methods

class MyClass {
    *createIterator() {
        yield 1;
        yield 2;
        yield 3;
    }
}
let instance = new MyClass();
let iterator = instance.createIterator();

当你需要定义一个collection类时,需要为class使用默认的iterator时,还需要使用Symbol.iterator

class Collection {
    constructor() {
        this.items = [];
    }
    *[Symbol.iterator]() {
        yield *this.items.values();
    }
}
var collection = new Collection();
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
    console.log(x);
}
// Output:
// 1
// 2
// 3

Static Members

ECMAScript5可以直接向构造函数中添加静态成员.

function PersonType(name) {
    this.name = name;
}
// static method
PersonType.create = function(name) {
    return new PersonType(name);
};
// instance method
PersonType.prototype.sayName = function() {
    console.log(this.name);
};
var person = PersonType.create("Nicholas");

ECMAScript6

class PersonClass {
// equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }
// equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
// equivalent of PersonType.create
    static create(name) {
        return new PersonClass(name);
    }
}
let person = PersonClass.create("Nicholas");

继承

在ECMAScript6之前的版本, 需要有多个步聚

function Square(length) {
    Rectangle.call(this, length, length);
}
Square.prototype = Object.create(Rectangle.prototype, {
    constructor: {
        value: Square,
        enumerable: true,
        writable: true,
        configurable: true
    }
});
var square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true

Square要实现对Reactange, 必须重写Square.prototype, 同时调用Reactange.call()方法。ECMAScript6通过extends关键词,实现继承.

class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }
    getArea() {
        return this.length * this.width;
    }
}
class Square extends Rectangle {
    constructor(length) {
// equivalent of Rectangle.call(this, length, length)
        super(length, length);
    }
}
var square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true

Square的构造函数必须调用super,否则发生错误,当没有constructor时,它自动调用super(), 如下所示

class Square extends Rectangle {
// no constructor
}
// is equivalent to
class Square extends Rectangle {
    constructor(...args) {
        super(...args);
    }
}

Shadowing Class Methods

子类中有同名的方法,会覆盖父类中相同的方法

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }
// override and shadow Rectangle.prototype.getArea()
    getArea() {
        return this.length * this.length;
    }
}

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }
// override, shadow, and call Rectangle.prototype.getArea()
    getArea() {
        return super.getArea();
    }
}

继承静态成员

如果父类中有静态成员,这些成员也可以被继承

class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }
    getArea() {
        return this.length * this.width;
    }
    static create(length, width) {
        return new Rectangle(length, width);
    }
}
class Square extends Rectangle {
    constructor(length) {
// equivalent of Rectangle.call(this, length, length)
        super(length, length);
    }
}
var rect = Square.create(3, 4);
console.log(rect instanceof Rectangle); // true
console.log(rect.getArea()); // 12
console.log(rect instanceof Square); // false

Square.create()跟Rectange.create()方法一样

从表达式中继承类

function Rectangle(length, width) {
    this.length = length;
}

Rectangle.prototype.getArea = function () {
    return this.length * this.width;
};

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }
}

var x = new Square(3);
console.log(x.getArea()); // 9
console.log(x instanceof Rectangle); // true

只要function中有[[Construct]],也可以作为父类。这对于动态继承非常有用

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}
Rectangle.prototype.getArea = function() {
    return this.length * this.width;
};
function getBase() {
    return Rectangle;
}
class Square extends getBase() {
    constructor(length) {
        super(length, length);
    }
}
var x = new Square(3);
console.log(x.getArea()); // 9
console.log(x instanceof Rectangle); // true

甚至实现多重继承

let SerializableMixin = {
    serialize() {
        return JSON.stringify(this);
    }
};
let AreaMixin = {
    getArea() {
        return this.length * this.width;
    }
};
function mixin(...mixins) {
    var base = function() {};
    Object.assign(base.prototype, ...mixins);
    return base;
}
class Square extends mixin(AreaMixin, SerializableMixin) {
    constructor(length) {
        super();
        this.length = length;
        this.width = length;
    }
}
var x = new Square(3);
console.log(x.getArea()); // 9
console.log(x.serialize()); // "{"length":3,"width":3}"

在混合继承中,如果有多个相同的元素,则最后继承的类的元素才会保留。

Inheriting from Build-Ins

在ECMAScript5,不可能实现从array中继承。

// built-in array behavior
var colors = [];
colors[0] = "red";
console.log(colors.length); // 1
colors.length = 0;
console.log(colors[0]); // undefined
// trying to inherit from array in ES5
function MyArray() {
    Array.apply(this, arguments);
}
MyArray.prototype = Object.create(Array.prototype, {
    constructor: {
        value: MyArray,
        writable: true,
        configurable: true,
        enumerable: true
    }
});
var colors = new MyArray();
colors[0] = "red";
console.log(colors.length); // 0
colors.length = 0;
console.log(colors[0]); // "red"

ECMAScript6 的一个目标就是允许从内置类型中继承,它的继承模型跟ECMAScript5有点不同:在ECMAScript5的类型继承中, this的值首先是通过衍生类创建(MyArray), 然后才是基础类Array.apply()方法的调用. 这表示开始的时候是MyArray实例,然后使用Array修饰额外的属性。 ECMAScript6中,首先this是创建于Array, 然后通过子类的constructor修改.

class MyArray extends Array {
// empty
}
var colors = new MyArray();
colors[0] = "red";
console.log(colors.length); // 1
colors.length = 0;
console.log(colors[0]); // undefined

The Symbol.species Property

继承于内置类的子类,调用父类的方法,会自动替换返回的类为子类。 比如MyArray.slice()返回的是MyArray

class MyArray extends Array {
// empty
}
let items = new MyArray(1, 2, 3, 4),
    subitems = items.slice(1, 3);
console.log(items instanceof MyArray); // true
console.log(subitems instanceof MyArray); // true

正常的,slice()是从Array中继承下来的,正常应该返回Array. 而返回MyArray是依赖于Symbol.species属性.以下是包含Symbol.species的内置类型

• Array
• ArrayBuffer (discussed in Chapter 10)
• Map
• Promise
• RegExp
• Set
• Typed arrays (discussed in Chapter 10)

上面每一种类型都有默认的Symbol.species属性,它返回this,即这个属性将返回一个构造函数. 如果你需要在自已的类中实现,参考以下的代码

// several built-in types use species similar to this
class MyClass {
    static get [Symbol.species]() {
        return this;
    }
    constructor(value) {
        this.value = value;
    }
    clone() {
        return new this.constructor[Symbol.species](this.value);
    }
}
class MyClass {
    static get [Symbol.species]() {
        return this;
    }
    constructor(value) {
        this.value = value;
    }
    clone() {
        return new this.constructor[Symbol.species](this.value);
    }
}
class MyDerivedClass1 extends MyClass {
// empty
}
class MyDerivedClass2 extends MyClass {
    static get [Symbol.species]() {
        return MyClass;
    }
}
let instance1 = new MyDerivedClass1("foo"),
    clone1 = instance1.clone(),
    instance2 = new MyDerivedClass2("bar"),
    clone2 = instance2.clone();
console.log(clone1 instanceof MyClass); // true
console.log(clone1 instanceof MyDerivedClass1); // true
console.log(clone2 instanceof MyClass); // true
console.log(clone2 instanceof MyDerivedClass2); // false
class MyArray extends Array {
    static get [Symbol.species]() {
        return Array;
    }
}
let items = new MyArray(1, 2, 3, 4),
    subitems = items.slice(1, 3);
console.log(items instanceof MyArray); // true
console.log(subitems instanceof Array); // true
console.log(subitems instanceof MyArray); // false

Using new.target in Class Constructors

class Rectangle {
    constructor(length, width) {
        console.log(new.target === Rectangle);
        this.length = length;
        this.width = width;
    }
}
// new.target is Rectangle
var obj = new Rectangle(3, 4); // outputs true
class Rectangle {
    constructor(length, width) {
        console.log(new.target === Rectangle);
        this.length = length;
        this.width = width;
    }
}
class Square extends Rectangle {
    constructor(length) {
        super(length, length)
    }
}
// new.target is Square
var obj = new Square(3); // outputs false

因此我们可以通过上面的方法,创建抽像类

// abstract base class
class Shape {
    constructor() {
        if (new.target === Shape) {
            throw new Error("This class cannot be instantiated directly.")
        }
    }
}
class Rectangle extends Shape {
    constructor(length, width) {
        super();
        this.length = length;
        this.width = width;
    }
}
var x = new Shape(); // throws an error
var y = new Rectangle(3, 4); // no error
console.log(y instanceof Shape); // true

10 改进数组

创建数组

Array.of()方法

在ECMAScript6中为了帮助开发人员避免使用Array construct来创建数组。添加了Array.of()方法

let items = new Array(2);
console.log(items.length); // 2
console.log(items[0]); // undefined
console.log(items[1]); // undefined
items = new Array("2");
console.log(items.length); // 1
console.log(items[0]); // "2"
items = new Array(1, 2);
console.log(items.length); // 2
console.log(items[0]); // 1
console.log(items[1]); // 2
items = new Array(3, "2");
console.log(items.length); // 2
console.log(items[0]); // 3
console.log(items[1]); // "2"
let items = Array.of(1, 2);
console.log(items.length); // 2
console.log(items[0]); // 1
console.log(items[1]); // 2
items = Array.of(2);
console.log(items.length); // 1
console.log(items[0]); // 2
items = Array.of("2");
console.log(items.length); // 1
console.log(items[0]); // "2"

The Array.from() Method

ECMAScript5

function makeArray(arrayLike) {
    var result = [];
    for (var i = 0, len = arrayLike.length; i < len; i++) {
        result.push(arrayLike[i]);
    }
    return result;
}
function doSomething() {
    var args = makeArray(arguments);
// use args
}

或者

function makeArray(arrayLike) {
    return Array.prototype.slice.call(arrayLike);
}
function doSomething() {
    var args = makeArray(arguments);
    // use args
}

ECMAScript6

function doSomething() {
    var args = Array.from(arguments);
    // use args
}

Mapping Conversion

function translate() {
    return Array.from(arguments, (value) => value + 1);
}
let numbers = translate(1, 2, 3);
console.log(numbers); // 2,3,4

如果map函数是一个对像,可以作为第三个参数,传递给Array.from

let helper = {
    diff: 1,
    add(value) {
        return value + this.diff;
    }
};
function translate() {
    return Array.from(arguments, helper.add, helper);
}
let numbers = translate(1, 2, 3);
console.log(numbers); // 2,3,4

用于可遍历对像上
Array.from()可以用于类数组对像和iterable对像上。

let numbers = {
    *[Symbol.iterator]() {
        yield 1;
        yield 2;
        yield 3;
    }
};
let numbers2 = Array.from(numbers, (value) => value + 1);
console.log(numbers2); // 2,3,4

New Methods on All Arrays

find()和findIndex()

在ECMAScript5之前,没有任何内置的方法用来搜索元素。 ECMAScript5添加了indexOf()和lastIndexOf()方法. 可以让开发者数组中搜索到特定的值。但这依然有限,你每次只能搜索一个元素。因此ECMAScript6添加了 find()和findIndex().
find()和findIndex()方法接收两个参数,一个是回调函数,和一个在回调函数中使用的this的值. 回调函数被传递的参数是一个数组元素,元素在array中的index, 以及实际的array. 类似于map()和forEach()方法。如果匹配到要找的值,回调函数应该返回true. 当返回true时,find()和findIndex()停止搜索.

find返回找到的值,而findIndex返回index.

var numbers = [25, 30, 35, 40, 45];
console.log(numbers.find(n => n > 33)); // 35
console.log(numbers.findIndex(n => n > 33)); // 2

35
2

Fill()

let numbers = [1, 2, 3, 4];
numbers.fill(1);
console.log(numbers.toString()); // 1,1,1,1

也可以指定开始的位置

let numbers = [1, 2, 3, 4];
numbers.fill(1, 2);
console.log(numbers.toString()); // 1,2,1,1
numbers.fill(0, 1, 3);
console.log(numbers.toString()); // 1,0,0,1

copyWithin()
跟fill()一样,可以修改一个数据中的元素,copyWithin不同的地方,在于,它可以让你复制这个数组中的元素.因此,你需要传递两个参数给 copyWithin()方法。第一个参数index表示,哪个地方需要填充新的数据,第二参数是从哪开始复制.

let numbers = [1, 2, 3, 4];
// paste values into array starting at index 2
// copy values from array starting at index 0
numbers.copyWithin(2, 0);
console.log(numbers.toString()); // 1,2,1,2

第三个参数用于指定要复制停此的位置,但不包含这个位置

let numbers = [1, 2, 3, 4];
// paste values into array starting at index 2
// copy values from array starting at index 0
// stop copying values when you hit index 1
numbers.copyWithin(2, 0, 1);
console.log(numbers.toString()); // 1,2,1,4

当前fill和copyWithin并没什么实际的用处,但在接下来的操作位数组时,就会有用.

Typed Arrays

Typed Array用于特定目的的数组,比如WebGL, 为Javascript提供快速的位操作. 用Javascript来操作WebGL时,由于需要将64-bit的浮点格式转化为32整数,所以会很慢.Typed arrays规避了这种限制,并提供了更好的算法性能。

Numeric Data Types

Javascript数字是保存IEEE754的格式,它采用了64位来保存的浮点数。Javascript的整型和浮点数都是使用这种格式. 而特定类型的数组,可以包含以下8种不同的数字类型

• Signed 8-bit integer (int8)
• Unsigned 8-bit integer (uint8)
• Signed 16-bit integer (int16)
• Unsigned 16-bit integer (uint16)
• Signed 32-bit integer (int32)
• Unsigned 32-bit integer (uint32)
• 32-bit float (float32)
• 64-bit float (float64)

如果你用Javascript数字表示一个int8大小的值,那么你浪费了56位。所以可以使用Typed Array来解决这个问题

Array Buffers

所有类型数组的原理都是使用array buffer, 它是一个内存位置,可以包含指定数量的字节. 在C语言中,使用malloc()来申请内存,而不需要指定它要包含什么内容。

let buffer = new ArrayBuffer(10); // allocate 10 bytes
console.log(buffer.byteLength); // 10

你也可以使用slice()方法来创建一个新的array buffer. 新的array buffer包含了己存在array buffer的部分. slice()方法类似于array. 你传递 开始的位置和结束的位置。

let buffer = new ArrayBuffer(10); // allocate 10 bytes
let buffer2 = buffer.slice(4, 6);
console.log(buffer2.byteLength); // 2

创建保存数据的位置对写入数据到指定的位置并没有特别大的帮助,你还需要创建view.

Manipulating Array Buffers with Views

Array Buffer表示的是内存地址,而views表示你可以操作这个内存的接口。DataView类型是Array buffer一个通用的视图类型,允许你操作上面8种数字类型.

为了使用DataView, 你首先需要创建ArrayBuffer,然后使用它创建一个DataView

let buffer = new ArrayBuffer(10),
    view = new DataView(buffer);

以上的buffer可以访问10个字节。你也可以使用部分字节。提供一个字节偏移量, 第二个参数是起始位置的字节,第三个参数是多少个位置。

let buffer = new ArrayBuffer(10),
view = new DataView(buffer, 5, 2); // cover bytes 5 and 6

以上view操作的字段是位置5和6.

检索View信息

可以通获取view的以下信息,

  • buffer, view绑定的array buffer
  • byteOffset DataView的第二个参数,默认为0
  • byteLength DataView的第三个参数, 默认为array buffer的长度
let buffer = new ArrayBuffer(10),
    view1 = new DataView(buffer), // cover all bytes
    view2 = new DataView(buffer, 5, 2); // cover bytes 5 and 6
console.log(view1.buffer === buffer); // true
console.log(view2.buffer === buffer); // true
console.log(view1.byteOffset); // 0
console.log(view2.byteOffset); // 5
console.log(view1.byteLength); // 10
console.log(view2.byteLength); // 2

但读取这些信息并没有什么帮助, 主要还是要读取和写入内存数据

读取和写入内存数据

对于8种数字类型,DataView原型都提供了不同的方法用于读取和写入数据. 都是以set和get开头,加上数字类型.

  • getInt8(byteOffset, littleEndian)
  • setInt8(byteOffset, value, littleEndian)
  • getUint8(byteOffset, littleEndian)
  • setUint8(byteOffset, value, littleEndian)
  • getFloat32(byteOffset, littleEndian)
  • setFloat32(byteOffset, value, littleEndian)
  • getFloat64(byteOffset, littleEndian)
  • setFloat64(byteOffset, value, littleEndian)
    get方法接收两个参数:要读取字段的位置偏移,第二个是一个布尔值,读取值是否应该以little-endian读取(Little-endian的意思是数字中的最右边部分,保存在内存的低位(左边)).通常默认的是big endian.
var buffer = new ArrayBuffer(2),
view = new DataView(buffer);
view.setInt8(0, 5);
view.setInt8(1, -1);
console.log(view.getInt8(0)); // 5
console.log(view.getInt8(1)); // -1
5
-1

这段代码使用一个两字节的array buffer来保存两个int8数字. 第一个设置offset为0,第二个为1, 每一个字刚好是8bit(一个字节).
Views不管之前保存的是什么数据,它只根据位置和类型来读取, 比如,之前两个int8,可以以int16来读取

var buffer = new ArrayBuffer(2),
view = new DataView(buffer);
view.setInt8(0, 5);
view.setInt8(1, -1);
console.log(view.getInt16(0)); // 1535
console.log(view.getInt8(0)); // 5
console.log(view.getInt8(1)); // -1
1535
5
-1

buffer contents

DataView可以混合使用不同的数据库型,但如果,你仅需要使用一种类型,最好是指定类型
buffer contents

Uint8ClampedArray跟Uint8Array一样,除了0和大于255的部分.Uint8ClampedArray将小于0的值转换为0(-1 将为0), 将大于255的转换为255(300 将为 255)

let buffer = new ArrayBuffer(10),
    view1 = new Int8Array(buffer),
    view2 = new Int8Array(buffer, 5, 2);
console.log(view1.buffer === buffer); // true
console.log(view2.buffer === buffer); // true
console.log(view1.byteOffset); // 0
console.log(view2.byteOffset); // 5
console.log(view1.byteLength); // 10
console.log(view2.byteLength); // 2

第二种创建typed array的方法是,向构造函灵敏传递单个的数值。它表示元素的个数(而不是字节)

let ints = new Int16Array(2),
    floats = new Float32Array(5);
console.log(ints.byteLength); // 4
console.log(ints.length); // 2
console.log(floats.byteLength); // 20
console.log(floats.length); // 5

如果有传递参数,则默认为0,则没有申请内存空间。

第三种创建 typed array的方法是传递一个对像, 这个对像可以是以下的几种

  • 一个数组, 数组中的每一个元素将复制到typed array, 如果数组中的元素,不是这个类型的,则会抛出错误
  • 一个类数组
  • 可iterable,
  • 其它typed array, 比如将一个int8 array传递给Int16Array, int8中的值将复制到int16. 新的int16 array将创建一个新的Array buffer.
let ints1 = new Int16Array([25, 50]),
ints2 = new Int32Array(ints1);
console.log(ints1.buffer === ints2.buffer); // false
console.log(ints1.byteLength); // 4
console.log(ints1.length); // 2
console.log(ints1[0]); // 25
console.log(ints1[1]); // 50
console.log(ints2.byteLength); // 8
console.log(ints2.length); // 2
console.log(ints2[0]); // 25
console.log(ints2[1]); // 50

Element size

console.log(UInt8Array.BYTES_PER_ELEMENT); // 1
console.log(UInt16Array.BYTES_PER_ELEMENT); // 2
let ints = new Int8Array(5);
console.log(ints.BYTES_PER_ELEMENT); // 1

Typed Array跟正常元素相似性

很多情况下,typed array跟原来的array使用是一样的,比如检测数组的长度length

let ints = new Int16Array([25, 50]);
console.log(ints.length); // 2
console.log(ints[0]); // 25
console.log(ints[1]); // 50
ints[0] = 1;
ints[1] = 2;
console.log(ints[0]); // 1
console.log(ints[1]); // 2

但跟array不同,typed array的length是不可更改的。在non-strict model则忽略,如果是strict model则抛出异常.

共同性

copyWithin()findIndex()lastIndexOf()slice()
entries()forEach()map()some()
fill()indexOf()reduce()sort()
filter()join()reduceRight()values()
find()keys()reverse()

虽然这些方法实现跟Array.prototype是一样的,但也并不是完全一样, typed array的方法有额外的类型安全检查,另一个不同时,当一个数组返回时,它返回的是type array而不是正常的array(Symbol.species)

let ints = new Int16Array([25, 50]),
mapped = ints.map(v => v * 2);
console.log(mapped.length); // 2
console.log(mapped[0]); // 50
console.log(mapped[1]); // 100
console.log(mapped instanceof Int16Array); // true

相同的interators
entries(), keys(), values()方法, 这意味着可以使用...和for-of

let ints = new Int16Array([25, 50]),
intsArray = [...ints];
console.log(intsArray instanceof Array); // true
console.log(intsArray[0]); // 25
console.log(intsArray[1]); // 50

of()和from()方法
静态方法of和from跟Array.of()和Array.from()方法类似。不同的是,typed array返回的是typed array,而不是array.

let ints = Int16Array.of(25, 50),
floats = Float32Array.from([1.5, 2.5]);
console.log(ints instanceof Int16Array); // true
console.log(floats instanceof Float32Array); // true
console.log(ints.length); // 2
console.log(ints[0]); // 25
console.log(ints[1]); // 50
console.log(floats.length); // 2
console.log(floats[0]); // 1.5
console.log(floats[1]); // 2.5

不同性

typed array跟正常array最重要的不同是typed arrays不是array. typed array不从Array中继承。 Array.isArray()返回false.

let ints = new Int16Array([25, 50]);
console.log(ints instanceof Array); // false
console.log(Array.isArray(ints)); // false

行为的不同
正常的array可以增加和减小大小,但是typed array保持初始时的大小

let ints = new Int16Array([25, 50]);
console.log(ints.length); // 2
console.log(ints[0]); // 25
console.log(ints[1]); // 50
ints[2] = 5;  //index 2不存在
console.log(ints.length); // 2  
console.log(ints[2]); // undefined

typed array还会检查数据的类型,如果是非法的值,则用0代替

var ints = new Int16Array(["hi"]);//"h".charCodeAt(0), 这段代码相将"hi"字符串传递,所以它不是有效的值.
console.log(ints.length); // 1
console.log(ints[0]); // 0
1
0

所有的typed array方法,涉及到修改值,都会有这样的限制

let ints = new Int16Array([25, 50]),
mapped = ints.map(v => "hi");
console.log(mapped.length); // 2
console.log(mapped[0]); // 0
console.log(mapped[1]); // 0
console.log(mapped instanceof Int16Array); // true
console.log(mapped instanceof Array); // false

由于 "hi"不是有效的16整型,所以替换为0.

缺失的方法

concat()shift()
pop()splice
push()unshift()

除了concat方法之外,其它的方法都可以修改array的大小,而typed array 是不可以改变大小的。这也是为什么这些方法不能在typed array方法中使用. concat不能使用的原因是连接的两个typed array(特别是两个类型不同的数组),所以类型是不确定的。

新增的方法

typed array有两个自已的方法 set()和subarray()方法。这两个方法是对立的,set()方法用于从其它数组中复制元素到typed array中,而subarray()是从一个typed array中提取出部分到新的数组中.

set()方法接收一个数组(正常或者typed arrray)和一个可选的offset(插入的位置)。如果没有指定,则复制到0的位置。要复制的元素类型必须是有效的。

let ints = new Int16Array(4);
ints.set([25, 50]);
ints.set([75, 100], 2);
console.log(ints.toString()); // 25,50,75,100

subarray()接收两个参数,开始的位置和结束的位置。类似于slice()方法。 并且返回一个新的typed array. 你可以不传递任何参数,直接克隆整个数组.

let ints = new Int16Array([25, 50, 75, 100]),
subints1 = ints.subarray(),
subints2 = ints.subarray(2),
subints3 = ints.subarray(1, 3);
console.log(subints1.toString()); // 25,50,75,100
console.log(subints2.toString()); // 75,100
console.log(subints3.toString()); // 50,75

11 Promises and 异步编程

Promise基础

一个Promise是一个异步操作结果的占位符。用于替换一个事件的订阅或者传递一个回递函数,这个函数返回一个promise.

// readFile promises to complete at some point in the future
let promise = readFile("example.txt");

在这段代码中, readFile()不会立即读取文件, 这个函数返回一个promise对像,表示异步读取操作,所以你可以在之后中使用到这个promise对像. 确切的说,如何读取文件依赖于promise的生命周期。

Promise Life Cycle

每一个Promise都有一个简短的pending状态,它表示异步操作还没有完成. 一个在等待的promise可以认为是unsettled. 在上一个readFile()的例子中,promise就是pending状态。当一个异步操作完成,promise可以认为是settled, 它可以有两种状态

  • Fulfilled 整个异步操作成功完成
  • Rejected 异步操作没有成功完成,发了错误

Promise内部的[[PromiseState]]属性可以设置为"pending", “fulfilled”, “rejected”. 这个属性不会向外暴露,所以你编程中不能获取当前的状态。 但promise状态发生改变时,可以通过then()方法,执行特定的行为.

then()接收两个参数,第一个对数是当promise是filfulled状态时调用,任何跟异步操作的相关的数据都会传递给这个参数。第二个参数是一个函数,当一个promise是rejected时调用。 跟fulfilled类似,这个函数也会传递额外的数据.

let promise = readFile("example.txt");
promise.then(function(contents) {
// fulfillment
    console.log(contents);
}, function(err) {
// rejection
    console.error(err.message);
});
promise.then(function(contents) {
// fulfillment
    console.log(contents);
});
promise.then(null, function(err) {
// rejection
    console.error(err.message);
});

Promise也有catch()方法,它类似于then(),但只传递一个rejection处理函数。

promise.catch(function(err) {
    // rejection
    console.error(err.message);
});
// is the same as:
promise.then(null, function(err) {
    // rejection
    console.error(err.message);
});

处理程序即使在promise已经被设置为settled时,应该可以添加。 这就允许你可以在任何时候添加新的处理器。如下所示

let promise = readFile("example.txt");
// original fulfillment handler
promise.then(function(contents) {
    console.log(contents);
// now add another
    promise.then(function(contents) {
        console.log(contents);
    });
});

创建一个Unsettled Promise

创建一个新的Promise,是通过Promise构造函数。这个构造函数接收单个参数:这个函数称为执行器,它包含了初始化promise的代码。resolve()和reject()作为参数传递给执行器。

// Node.js example
let fs = require("fs");
function readFile(filename) {
    return new Promise(function(resolve, reject) {// trigger the asynchronous operation
        fs.readFile(filename, { encoding: "utf8" }, function(err, contents) {
// check for errors
            if (err) {
                reject(err);
                return;
            }
// the read succeeded
            resolve(contents);
        });
    });
}
let promise = readFile("example.txt");
// listen for both fulfillment and rejection
promise.then(function(contents) {
// fulfillment
    console.log(contents);
}, function(err) {
// rejection
    console.error(err.message);
});

需要注意的是,readFile()调用时,执行器在这里会被立即执行。 当在执行器内部,resolve()或者reject()被调用时,一个作业被添加到作业队列中。实现promise. 这称为 job scheduling. 如果你之前使用过setTimeout()或者setInterval(),那么你就能明白其中的原理。在一个job scheduling, 你添加一个新的job到job queue中,并告诉他,现在不要执行,在之后执行它".

// add this function to the job queue after 500 ms have passed
setTimeout(function() {
    console.log("Timeout");
}, 500)
console.log("Hi!");
//Hi!
//Timeout

Promise也是类似,它的执行器先被执行.

let promise = new Promise(function(resolve, reject) {
    console.log("Promise");
    resolve();
});
console.log("Hi!");
// Promise
// Hi

当调用resolve()时触发一个异步操作。传递给then()和catch()的异步操作, 由于添加到了工作队列中,所以将会被执行。

let promise = new Promise(function(resolve, reject) {
    console.log("Promise");
    resolve();
});
promise.then(function() {
    console.log("Resolved.");
});
console.log("Hi!");

//Promise
//Hi!
//Resolved

创建Settled Promise

Promise构造函数是创建unsettled promise最好的方式。如果你想一个promise仅表示单个已知的值,你可以传递一个settled promise.

使用Promise.resolve()
Promise.resolve()方法接收当个参数,并返回一个fulfilled promise. 这意味着job scheduling已经发生,你需要的只是添加一个或者多个fulfillment处理器,来接收这个值。

let promise = Promise.resolve(42);
promise.then(function(value) {
    console.log(value); // 42
});

使用Promise.reject()

let promise = Promise.reject(42);
promise.catch(function(value) {
    console.log(value); // 42
});

Non-Promise Thenables

Promise.resolve和Promise.reject()方法也可以接收一个 non-promise thenable作为参数。 当你传递一个non-promise thenable时,在调用它的then方法之后,它们将创建一个promise.

一个non-promise thenable是一个对像,它包含了then方法,它接收一个resolve和reject参数.

let thenable = {
    then: function(resolve, reject) {
        resolve(42);
    }
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
    console.log(value); // 42
});
let thenable = {
    then: function(resolve, reject) {
        reject(42);
    }
};
let p1 = Promise.resolve(thenable);
p1.catch(function(value) {
    console.log(value); // 42
});

Executor Errors

let promise = new Promise(function(resolve, reject) {
    throw new Error("Explosion!");
});
promise.catch(function(error) {
    console.log(error.message); // "Explosion!"
});

在这段代码中,执行器内部抛出一个错误,每一个执行器都有一个隐形的try-catch。所以上面的代码,类似于

let promise = new Promise(function(resolve, reject) {
    try {
        throw new Error("Explosion!");
    } catch (ex) {
        reject(ex);
    }
});
promise.catch(function(error) {
    console.log(error.message); // "Explosion!"
});

全局的Promise Rejecttion处理

Node.js Rejection Handling
Nodejs会在process对像发出两个错误,用于处理promise rejection.

  • unhandleRejection 当一个promise为rejected,而又没有调用rejection handler. 然后打开事件循环
  • rejectionHandled, 当一个promise为rejected, 而一个rejection handler被调用, 然后关闭事件循环
let rejected;
process.on("unhandledRejection", function(reason, promise) {
    console.log(reason.message); // "Explosion!"
    console.log(rejected === promise); // true
});
rejected = Promise.reject(new Error("Explosion!"));
let rejected;
process.on("rejectionHandled", function(promise) {
    console.log(rejected === promise); // true
});
rejected = Promise.reject(new Error("Explosion!"));
    // wait to add the rejection handler
    setTimeout(function() {
    rejected.catch(function(value) {
        console.log(value.message); // "Explosion!"
    });
}, 1000);
let possiblyUnhandledRejections = new Map();
// when a rejection is unhandled, add it to the map
process.on("unhandledRejection", function(reason, promise) {
    possiblyUnhandledRejections.set(promise, reason);
});
process.on("rejectionHandled", function(promise) {
    possiblyUnhandledRejections.delete(promise);
});
setInterval(function() {
    possiblyUnhandledRejections.forEach(function(reason, promise) {
        console.log(reason.message ? reason.message : reason);
// do something to handle these rejections
        handleRejection(promise, reason);
    });
    possiblyUnhandledRejections.clear();
}, 60000);

Browser Rejection Handling

  • unhandledrejection
  • rejectionhandled

window的事件处理函数,包含以下三个参数

  • type event的名称(“unhandledrejection” or “rejectionhandled”)
  • promise 被rejected promise.
  • reason 从promise中获取到的rejected值
let rejected;
window.onunhandledrejection = function(event) {
    console.log(event.type); // "unhandledrejection"
    console.log(event.reason.message); // "Explosion!"
    console.log(rejected === event.promise); // true
});
window.onrejectionhandled = function(event) {
    console.log(event.type); // "rejectionhandled"
    console.log(event.reason.message); // "Explosion!"
    console.log(rejected === event.promise); // true
});
rejected = Promise.reject(new Error("Explosion!"));
let possiblyUnhandledRejections = new Map();
// when a rejection is unhandled, add it to the map
window.onunhandledrejection = function(event) {
    possiblyUnhandledRejections.set(event.promise, event.reason);
};
window.onrejectionhandled = function(event) {
    possiblyUnhandledRejections.delete(event.promise);
};
setInterval(function() {
    possiblyUnhandledRejections.forEach(function(reason, promise) {
        console.log(reason.message ? reason.message : reason);
// do something to handle these rejections
        handleRejection(promise, reason);
    });
    possiblyUnhandledRejections.clear();
}, 60000);

Promise调用链

var p1 = new Promise(function(resolve, reject) {
    resolve(42);
});
p1.then(function(value) {
    console.log(value);
}).then(function() {
    console.log("Finished");
});
42
Finished

类似于下面的代码

let p1 = new Promise(function(resolve, reject) {
    resolve(42);
});
let p2 = p1.then(function(value) {
    console.log(value);
})
p2.then(function() {
    console.log("Finished");
});

Catching Errors

let p1 = new Promise(function(resolve, reject) {
    resolve(42);
});
p1.then(function(value) {
    throw new Error("Boom!");
}).catch(function(error) {
    console.log(error.message); // "Boom!"
});

//or 
let p1 = new Promise(function(resolve, reject) {
    throw new Error("Explosion!");
});
p1.catch(function(error) {
    console.log(error.message); // "Explosion!"
    throw new Error("Boom!");
}).catch(function(error) {
    console.log(error.message); // "Boom!"
});

在调用链中返回值

let p1 = new Promise(function(resolve, reject) {
    resolve(42);
});
p1.then(function(value) {
    console.log(value); // "42"
    return value + 1;
}).then(function(value) {
    console.log(value); // "43"
});

返回promise

let p1 = new Promise(function(resolve, reject) {
    resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
    resolve(43);
});
p1.then(function(value) {
// first fulfillment handler
    console.log(value); // 42
    return p2;
}).then(function(value) {
// second fulfillment handler
    console.log(value); // 43
});
let p1 = new Promise(function(resolve, reject) {
    resolve(42);
});
p1.then(function(value) {
    console.log(value); // 42
// create a new promise
    let p2 = new Promise(function(resolve, reject) {
        resolve(43);
    });
    return p2
}).then(function(value) {
    console.log(value); // 43
});

响应多个Promise

ECMAScript6提供了两个方法来监控多个promise:promise.all(), promise.race().

let p1 = new Promise(function(resolve, reject) {
    resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
    resolve(43);
});
let p3 = new Promise(function(resolve, reject) {
    resolve(44);
});
let p4 = Promise.all([p1, p2, p3]);
p4.then(function(value) {
    console.log(Array.isArray(value)); // true
    console.log(value[0]); // 42
    console.log(value[1]); // 43
    console.log(value[2]); // 44
});

输出的值是按子promise,resolved的顺序, 如果其中任何一个promise被rejected, 则整个父promise将立即rejected, 不用等待其它promise.

let p1 = new Promise(function(resolve, reject) {
    resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
    reject(43);
});
let p3 = new Promise(function(resolve, reject) {
    resolve(44);
});
let p4 = Promise.all([p1, p2, p3]);
p4.catch(function(value) {
    console.log(Array.isArray(value)) // false
    console.log(value); // 43
});

The Promise.race() Method

当其中一个promise被resolved, 则Promise.race()被resolved.

let p1 = Promise.resolve(42);
let p2 = new Promise(function(resolve, reject) {
    resolve(43);
});
let p3 = new Promise(function(resolve, reject) {
    resolve(44);
});
let p4 = Promise.race([p1, p2, p3]);
p4.then(function(value) {
    console.log(value); // 42
});
let p1 = new Promise(function(resolve, reject) {
    resolve(42);
});
let p2 = Promise.reject(43);
let p3 = new Promise(function(resolve, reject) {
    resolve(44);
});
let p4 = Promise.race([p1, p2, p3]);
p4.catch(function(value) {
    console.log(value); // 43
});

Inheriting from Promises

使用success和failture替换then和catch

class MyPromise extends Promise {
// use default constructor
    success(resolve, reject) {
        return this.then(resolve, reject);
    }
    failure(reject) {
        return this.catch(reject);
    }
}
let promise = new MyPromise(function(resolve, reject) {
    resolve(42);
});
promise.success(function(value) {
    console.log(value); // 42
}).failure(function(value) {
    console.log(value);
});

同样可以继承静态方法,MyPromise.resolve()和MyPromise.reject(),以及MyPromise.all()和MyPromise.race()方法,后两个跟Promise是一样的,但前两个稍微有点不同.

MyPromise.resolve()和MyPromise.reject()将返回一个MyPromise实例,而不管传递给它的值。 这是因为这些方法使用的是Symbol.species属性(Page 185)。来确定返回的promise类型。 如果一个内置的promise传递给其中的一个方法,这个内置的promise将被resolved或者rejected.之后这个方法将返回一个新的MyPromise.

let p1 = new Promise(function(resolve, reject) {
    resolve(42);
});
let p2 = MyPromise.resolve(p1);
p2.success(function(value) {
    console.log(value); // 42
});
console.log(p2 instanceof MyPromise); // true

基于Promise的异步任务处理器

在第八章中,我们使用generator实现了一个异步任务处理器

let fs = require("fs");
function run(taskDef) {
    // create the iterator, make available elsewhere
    let task = taskDef();
    // start the task
    let result = task.next();
    // recursive function to keep calling next()
    function step() {
        // if there's more to do
        if (!result.done) {
            if (typeof result.value === "function") {
                result.value(function(err, data) {
                    if (err) {
                        result = task.throw(err);
                        return;
                    }
                    result = task.next(data);
                    step();
                });
            } else {
                result = task.next(result.value);
                step();
            }
        }
    }
    // start the process
    step();
}
// define a function to use with the task runner
function readFile(filename) {
    return function(callback) {
        fs.readFile(filename, callback);
    };
}
// run a task
run(function*() {
    let contents = yield readFile("config.json");
    doSomethingWith(contents);
    console.log("Done");
});

以下是Promise的版本

let fs = require("fs");
function run(taskDef) {
    // create the iterator
    let task = taskDef();
// start the task
    let result = task.next();
// recursive function to iterate through
    (function step() {
// if there's more to do
        if (!result.done) {
// resolve to a promise to make it easy
            let promise = Promise.resolve(result.value);
            promise.then(function(value) {
                result = task.next(value);
                step();
            }).catch(function(error) {
                result = task.throw(error);
                step();
            });
        }
    }());
}
// define a function to use with the task runner
function readFile(filename) {
    return new Promise(function(resolve, reject) {
        fs.readFile(filename, function(err, contents) {
            if (err) {
                reject(err);
            } else {
                resolve(contents);
            }
        });
    });
}
// run a task
run(function*() {
    let contents = yield readFile("config.json");
    doSomethingWith(contents);
    console.log("Done");
});

在ECMAScript8中,还可以使用await关键词

(async function() {
    let contents = await readFile("config.json");
    doSomethingWith(contents);
    console.log("Done");
});

12 Proxies and The Reflection API

在ECMAScript5之前,Javascript环境中包含一些可不枚举,可不写的对像属性。而在ECMAScript5,我们可以通过Object.defineProperty()方水土土又,修改这些属性。
ECMAScript6给开发者提供了更多访问Javascript引擎的能力, 为了允许开发者创建内置的对像,Javascript通过 proxies暴露了对像内部的工作机制,它可以拦截和改变底层的Javascript引擎操作。

The Array Problem

在ECMAScript5,我们无法实现Array的length行为,即通过修改length来改变array的值, 而在ECMAScript6中可以通过proxy来实现相似的形为

let colors = ["red", "green", "blue"];
console.log(colors.length); // 3
colors[3] = "black";
console.log(colors.length); // 4
console.log(colors[3]); // "black"
colors.length = 2;
console.log(colors.length); // 2
console.log(colors[3]); // undefined
console.log(colors[2]); // undefined
console.log(colors[1]); // "green"

介绍Proxy和Reflection

通过new Proxy()创建一个proxy来代替别一个对像(target). 这个代理是虚拟了target, 所以target和proxy的功能上是相同的.

Proxies允许你拦截在target上的低级对像操作, 即Javascript引擎的内部操作。这些低层次的操作是使用了trap, 它是一个函数,响应一个特定的操作。

Reflection API, 表示的是反射对像,是proxy可以重写的默认低层操作的方法集合. 每一个proxy trap方法,都有一个Reflect方法。 以下是汇总的 proxy trap行为

Table 12-1: Proxy Traps in JavaScript

Proxy trapOverrides the behavior ofDefault behavior
getReading a property valueReflect.get()
setWriting to a propertyReflect.set()
hasThe in operatorReflect.has()
deletePropertyThe delete operatorReflect.deleteProperty()
getPrototypeOfObject.getPrototypeOf()Reflect.getPrototypeOf()
setPrototypeOfObject.setPrototypeOf()Reflect.setPrototypeOf()
isExtensibleObject.isExtensible()Reflect.isExtensible()
preventExtensionsObject.preventExtensions()Reflect.preventExtensions()
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor()Reflect.getOwnPropertyDescriptor()
definePropertyObject.defineProperty()Reflect.defineProperty
ownKeysObject.keys(),Object.getOwnPropertyNames(), and Object.getOwnPropertySymbols()Reflect.ownKeys()
applyCalling a functionReflect.apply()
constructCalling a function with newReflect.construct()

note: ECMAScript7 删除了enumerate trap

创建一个简单的代理

创建一个代理需要传递两个参数,一个是target对像,一个是handler, 它定义traps. proxty使用默认的行为,除非定义了某个trap. 为了创建一个简单的proxy,你可以不指定任何trap.

let target = {};
let proxy = new Proxy(target, {});
proxy.name = "proxy";
console.log(proxy.name); // "proxy"
console.log(target.name); // "proxy"
target.name = "target";
console.log(proxy.name); // "target"
console.log(target.name); // "target"

对proxy的操作直接就是对target的操作,向proxy分配了name属性,则target也分配了name属性。proxy并不保存这个属性,而是简单的操作在target. 当然没有trap的proxy是没有意义的。

使用set Trap来校验属性

假设你想添加到对像上的属性都为数值,则需要添加 set trap, 这个trap接收四个参数

  • trapTarget 将接收属性的对像(proxy的target)
  • key 属性的名称(string or symbol)
  • value 属性的值
  • receiver 操作发生时的对像(通常为proxy)

Reflect.set()是set trap的相应方法, set trap 就是调用Reflect.set()来执行默认的设置值的行为. Reflect.set()也是接收四个相同的参数. set trap需要返true or false. 而Reflect.set()方法会基础设置操作是否完成,返回true or false).

let target = {
    name: "target"
};
let proxy = new Proxy(target, {
    set(trapTarget, key, value, receiver) {
        // ignore existing properties so as not to affect them
        if (!trapTarget.hasOwnProperty(key)) {
            if (isNaN(value)) {
                throw new TypeError("Property must be a number.");
            }
        }
        // add the property
        return Reflect.set(trapTarget, key, value, receiver);
    }
});
// adding a new property
proxy.count = 1;
console.log(proxy.count); // 1
console.log(target.count); // 1
// you can assign to name because it exists on target already
proxy.name = "proxy";
console.log(proxy.name); // "proxy"
console.log(target.name); // "proxy"
// throws an error
proxy.anotherName = "proxy";

Get trap

在Javascript中访问对像没有的属性是不会抛出错误,而是undefined, 现在我们可以通过proxy的get trap来抛出错误

let proxy = new Proxy({}, {
    get(trapTarget, key, receiver) {
        if (!(key in receiver)) {
            throw new TypeError("Property " + key + " doesn't exist.");
        }
        return Reflect.get(trapTarget, key, receiver);
    }
});
// adding a property still works
proxy.name = "proxy";
console.log(proxy.name); // "proxy"
// nonexistent properties throw an error
console.log(proxy.nme); // throws an error

使用Has trap隐藏属性

let target = {
    value: 42;
}
console.log("value" in target); // true
console.log("toString" in target); // true

has trap接收以下两个参数

  • trapTarget
  • key
    返回true,如果属性存在,否则, false.
let target = {
    name: "target",
    value: 42
};
let proxy = new Proxy(target, {
    has(trapTarget, key) {
        if (key === "value") {
            return false;
        } else {
            return Reflect.has(trapTarget, key);
        }
    }
});
console.log("value" in proxy); // false
console.log("name" in proxy); // true
console.log("toString" in proxy); // true

通过deleteProperty Trap防止属性删除

当一个属性设置为configurable: false时,是不能删除这个属性的

let target = {
    name: "target",
    value: 42
};
Object.defineProperty(target, "name", { configurable: false });
console.log("value" in target); // true
let result1 = delete target.value;
console.log(result1); // true
console.log("value" in target); // false
// note: the following line throws an error in strict mode
let result2 = delete target.name;
console.log(result2); // false
console.log("name" in target); // true

通过deleteProperty trap 防止属性删除

let target = {
    name: "target",
    value: 42
};
let proxy = new Proxy(target, {
    deleteProperty(trapTarget, key) {
        if (key === "value") {
            return false;
        } else {
            return Reflect.deleteProperty(trapTarget, key);
        }
    }
});
// attempt to delete proxy.value
console.log("value" in proxy); // true
let result1 = delete proxy.value;
console.log(result1); // false
console.log("value" in proxy); // true
// attempt to delete proxy.name
console.log("name" in proxy); // true
let result2 = delete proxy.name;
console.log(result2); // true
console.log("name" in proxy); // false

Prototype Proxy Traps

在第四章中介绍过,ECMAScript6引入了setPrototypeOf()方法,以对应ECMAScript5中添加getPrototypeOf()方法, 这两个方法也分别有相应的trap

  • trapTarget
  • proto 对像使用的prototype

Prototype proxy trap的使用有一些限制,首先, getPrototypeOf必须返回一个对像或者null。 第二个, setPrototypeOf 如果操作失败必须返回false, 当setPrototypeOf返回false, 则Object.setPrototypeOf()则抛出异常。 如果setPrototypeOf返回其它值,Object.setPrototype()则认为操作成功.

let target = {};
let proxy = new Proxy(target, {
    getPrototypeOf(trapTarget) {
        return null;
    },
    setPrototypeOf(trapTarget, proto) {
        return false;
    }
});

let targetProto = Object.getPrototypeOf(target);
let proxyProto = Object.getPrototypeOf(proxy);
console.log(targetProto === Object.prototype); // true
console.log(proxyProto === Object.prototype); // false
console.log(proxyProto); // null
// succeeds
Object.setPrototypeOf(target, {});
// throws an error
Object.setPrototypeOf(proxy, {});

如果你需要使用默认的getPrototypeOf和setPrototypeOf, 可以参考下面的代码

let target = {};
let proxy = new Proxy(target, {
    getPrototypeOf(trapTarget) {
        return Reflect.getPrototypeOf(trapTarget);
    },
    setPrototypeOf(trapTarget, proto) {
        return Reflect.setPrototypeOf(trapTarget, proto);
    }
});
let targetProto = Object.getPrototypeOf(target);
let proxyProto = Object.getPrototypeOf(proxy);
console.log(targetProto === Object.prototype); // true
console.log(proxyProto === Object.prototype); // true
// succeeds
Object.setPrototypeOf(target, {});
// also succeeds
Object.setPrototypeOf(proxy, {});

Reflect.getPrototypeOf()和Reflect.getPrototypeOf()

Reflect.getPrototypeOf与Object.getPrototypeOf的不同在于object是高级操作,而Reflect.getPrototypeOf是低级操作。 它们都是包装了内部的[[GetPrototypeOf]]操作, 但是Object.getPrototypeOf在执行[[GetPrototypeOf]]之前执行了一些额外的步聚

let result1 = Object.getPrototypeOf(1); //强制将 1转换为Number 对像,所以原型为Number.prototype
console.log(result1 === Number.prototype); // true  
// throws an error
Reflect.getPrototypeOf(1);

Reflect.setPrototypeOf()返回一个布尔值表示是否成功, 而Object.setPrototypeOf()则在失败时抛出错误.

let target1 = {};
let result1 = Object.setPrototypeOf(target1, {});
console.log(result1 === target1); // true
let target2 = {};
let result2 = Reflect.setPrototypeOf(target2, {});
console.log(result2 === target2); // false
console.log(result2); // true

Object Extensibility Traps

ECMAScript5添加了对像的可扩展性,通过Object.preventExtensions和Object.isExtensible方法可以修改对像的可护展性. ECMAScript6通过proxy的isExtensible trap和preventExtensions trap。 这两个trap都只有一个trapTarget参数.

let target = {};
let proxy = new Proxy(target, {
    isExtensible(trapTarget) {
        return Reflect.isExtensible(trapTarget);
    },
    preventExtensions(trapTarget) {
        return Reflect.preventExtensions(trapTarget);
    }
});
console.log(Object.isExtensible(target)); // true
console.log(Object.isExtensible(proxy)); // true
Object.preventExtensions(proxy);
console.log(Object.isExtensible(target)); // false
console.log(Object.isExtensible(proxy)); // false
let target = {};
let proxy = new Proxy(target, {
    isExtensible(trapTarget) {
        return Reflect.isExtensible(trapTarget);
    },
    preventExtensions(trapTarget) {
        return false
    }
});
console.log(Object.isExtensible(target)); // true
console.log(Object.isExtensible(proxy)); // true
Object.preventExtensions(proxy);
console.log(Object.isExtensible(target)); // true
console.log(Object.isExtensible(proxy)); // true

Object.IsExtensible()返回false, 而Reflect.isExtensible()抛出错误。

let result1 = Object.isExtensible(2);
console.log(result1); // false
// throws an error
let result2 = Reflect.isExtensible(2);


let result1 = Object.preventExtensions(2);
console.log(result1); // 2
let target = {};
let result2 = Reflect.preventExtensions(target);
console.log(result2); // true
// throws an error
let result3 = Reflect.preventExtensions(2);

Property Descriptor Traps

defineProperty trap接收以下三个参数

  • targetTarget
  • key
  • descriptor
let proxy = new Proxy({}, {
    defineProperty(trapTarget, key, descriptor) {
        return Reflect.defineProperty(trapTarget, key, descriptor);
    },
    getOwnPropertyDescriptor(trapTarget, key) {
        return Reflect.getOwnPropertyDescriptor(trapTarget, key);
    }
});
Object.defineProperty(proxy, "name", {
    value: "proxy"
});
console.log(proxy.name); // "proxy"
let descriptor = Object.getOwnPropertyDescriptor(proxy, "name");
console.log(descriptor.value); // "proxy"

阻止Object.defineProperty()
defineProperty 需要你返回一个boolean值来表示,是否操作成功。 当返回true时,Object.defineProperty()方法定义属性成功。如果是false返回,Object.defineProperty()则抛出错误。 你可能使用这个原理来限制属性的类型。比如你想要限制symbol属性。

let proxy = new Proxy({}, {
    defineProperty(trapTarget, key, descriptor) {
        if (typeof key === "symbol") {
            return false;
        }
        return Reflect.defineProperty(trapTarget, key, descriptor);
    }
});
Object.defineProperty(proxy, "name", {
    value: "proxy"
});
console.log(proxy.name); // "proxy"
let nameSymbol = Symbol("name");
// throws an error
Object.defineProperty(proxy, nameSymbol, {
    value: "proxy"
});

13 Module

Basic Exporting

// export data
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;
// export function
export function sum(num1, num2) {
    return num1 + num1;
}
// export class
export class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }
}
// this function is private to the module
function subtract(num1, num2) {
    return num1 - num2;
}
// define a function...
function multiply(num1, num2) {
    return num1 * num2;
}
// ...and then export it later
export multiply;

Basic Importing

import { identifier1, identifier2 } from "./example.js";

Importing a Single Binding

// import just one
import { sum } from "./example.js";
console.log(sum(1, 2)); // 3
sum = 1; // throws an error

Importing Multiple Bindings

// import multiple
import { sum, multiply, magicNumber } from "./example.js";
console.log(sum(1, magicNumber)); // 8
console.log(multiply(1, 2)); // 2

Import an Entire Module

// import everything
import * as example from "./example.js";
console.log(example.sum(1,
example.magicNumber)); // 8
console.log(example.multiply(1, 2)); // 2

ECMAScript6导入在导入时创建一个变量,函数,类的只读绑定,而不是简单的像正常的变量引用原始的绑定。即import不能改变绑定的值。

export var name = "Nicholas";
export function setName(newName) {
    name = newName;
}

当你导入这两个绑定,setName可以改变name的值

import { name, setName } from "./example.js";
console.log(name); // "Nicholas"
setName("Greg");
console.log(name); // "Greg"
name = "Nicholas"; // throws an error

setName(‘Greg’)通name的改变可以反射影响到导入的name。

Renaming Exports and Imports

改变导出时的变量名称

function sum(num1, num2) {
    return num1 + num2;
}
export { sum as add };
import { add } from "./example.js";

在导入时改变名称

import { add as sum } from "./example.js";
console.log(typeof add); // "undefined"
console.log(sum(1, 2)); // 3

Modules中的默认值

可以跟CommonJS那样指定一个Default value. 但只能是单个的值,如果是多个default则发生错误.

export default function(num1, num2) {
    return num1 + num2;
}

或者

function sum(num1, num2) {
    return num1 + num2;
}
export default sum;

或者

function sum(num1, num2) {
    return num1 + num2;
}
export { sum as default };

导入默认值

// import the default
import sum from "./example.js";
console.log(sum(1, 2)); // 3

注意这里没有使用{}, sum表示模块导出的default值。

export let color = "red";
export default function(num1, num2) {
    return num1 + num2;
}
import sum, { color } from "./example.js";
console.log(sum(1, 2)); // 3
console.log(color); // "red"

导入默认值是,必须在非默认值的前面

import { default as sum, color } from "./example.js";
console.log(sum(1, 2)); // 3
console.log(color); // "red"

Re-exporting a binding

import { sum } from "./example.js";
export { sum }
export { sum } from "./example.js";
export { sum as add } from "./example.js";  //export sum 重命名为add
export * from "./example.js";

Importing Without Bindings

Module可以修改全局变量的值

// module code without exports or imports
Array.prototype.pushAll = function(items) {
// items must be an array
    if (!Array.isArray(items)) {
        throw new TypeError("Argument must be an array.");
    }
// use built-in push() and spread operator
    return this.push(...items);
};
import "./example.js";
let colors = ["red", "green", "blue"];
let items = [];
items.pushAll(colors);

Loading Modules

浏览器中使用模块

  • <script>中通过src来加载模块
  • <script>内联代码
  • 通过Worker加载

可以指定type的值为module, 以模块加载,而不是角本来执行

<!-- load a module JavaScript file -->
<script type="module" src="module.js"></script>
<!-- include a module inline -->
<script type="module">
import { sum } from "./example.js";
let result = sum(1, 2);
</script>

按顺序加载模块

<!-- this will execute first -->
<script type="module" src="module1.js"></script>
<!-- this will execute second -->
<script type="module">
import { sum } from "./example.js";
let result = sum(1, 2);
</script>
<!-- this will execute third -->
<script type="module" src="module2.js"></script>
  1. 下载和解析module.js
  2. 递归下载module1.js中import的资源
  3. 解析inline模块
  4. 递归下载inline中的import的资源
  5. 下载和解析module2.js
  6. 递归下载module2中的import资源

当加载完成时,代码不会被执行,直到整个文档被解析. 当文档解析完成时,发生以下行为

  1. 递归执行module1.js中import的资源
  2. 执行module1.js
  3. 递归执行inline模块中加载的资源
  4. 执行inline模块
  5. 递归执行module2.js中import资源
  6. 执行module2.js

defer 属性会被忽略<script type="module">, 因为module默认是defer的行为

<!-- no guarantee which one of these will execute first -->
<script type="module" async src="module1.js"></script>
<script type="module" async src="module2.js"></script>

不能保证module1和module2的加载顺序

Loading Modules as Works
Work执行的上下文,脱离了web page的上下文

// load script.js as a script
let worker = new Worker("script.js");

为了支持模块的加载, 可以通过第二个参数传递以module方式加载,默认为script.

// load module.js as a module
let worker = new Worker("module.js", { type: "module" });

worker加载script与module的区别:如果是script, 它必须与网页是同源的,而module默认是受同源的限制,但可以通过Cross-Origin Resource Sharing(CORS)头来访问。script可以使用importScripts()方法加载额外的其它角本,但module中不能使用importScripts(),而应该使用import代替.

浏览器模块中指定资源

  • / 从根目录中加载资源
  • ./当前目录中加载资源
  • ../上级目录中加载资源
  • URL格式

比如模块https://www.example.com/modules/module.js

// imports from https://www.example.com/modules/example1.js
import { first } from "./example1.js";
// imports from https://www.example.com/example2.js
import { second } from "../example2.js";
// imports from https://www.example.com/example3.js
import { third } from "/example3.js";
// imports from https://www2.example.com/example4.js
import { fourth } from "https://www2.example.com/example4.js";

www2.example.com需要CORS头,允许跨域访问

以下是非法的

// invalid - doesn't begin with /, ./, or ../
import { first } from "example.js";
// invalid - doesn't begin with /, ./, or ../
import { second } from "example/index.js";

以上的方式是非法的,即使在<script> src中可以这样使用。import和<script>是不同的行为.

附录A

整数

识别整数

ECMAScript6添加了Number.isInteger()方法。虽然javascript使用IEEE754表示number, 但float和iterger存储方式是不一样的。Number.isInteger()就是利用了这种存储的不同. 当调用这个方法时,Javascript引擎查找底层值,以确定是否为整型. 一个数值看起来是float, 但实际上是以integer存储,所以Number.isInteger()返回true. 举例来说

console.log(Number.isInteger(25)); // true
console.log(Number.isInteger(25.0)); // true
console.log(Number.isInteger(25.1)); // false

整数安全

IEEE754整数实际可以表示的值为-$ 2^{53}$和 2 53 2^{53} 253, 超出这个范围的整数就不能表示

console.log(Math.pow(2, 53)); // 9007199254740992
console.log(Math.pow(2, 53) + 1); // 9007199254740992

ECMAScript6引入了Number.isSafeInteger()方法来识别一个整数是否是有效的。同时它通过Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER 来表示整数的上限和下限.Number.isSafeInteger()可以保证一个值是个整数,而且是一个安全的整数

var inside = Number.MAX_SAFE_INTEGER,
outside = inside + 1;
console.log(Number.isInteger(inside)); // true
console.log(Number.isSafeInteger(inside)); // true
console.log(Number.isInteger(outside)); // true 它是一个整数,但不是一个安全的整数
console.log(Number.isSafeInteger(outside)); // false
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值