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.然后继续执行.

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

当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

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

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 trap Overrides the behavior of Default behavior
get Reading a property value Reflect.get()
set Writing to a property Reflect.set()
has The in operator Reflect.has()
deleteProperty The delete operator Reflect.deleteProperty()
getPrototypeOf Object.getPrototypeOf() Reflect.getPrototypeOf()
setPrototypeOf Object.setPrototypeOf() Reflect.setPrototypeOf()
isExtensible Object.isExtensible() Reflect.isExtensible()
preventExtensions Object.preventExtensions() Reflect.preventExtensions()
getOwnPropertyDescriptor Object.getOwnPropertyDescriptor() Reflect.getOwnPropertyDescriptor()
defineProperty Object.defineProperty() Reflect.defineProperty
ownKeys Object.keys(),Object.getOwnPropertyNames(), and Object.getOwnPropertySymbols() Reflect.ownKeys()
apply Calling a function Reflect.apply()
construct Calling a function with new Reflect.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

浏览器中使用模块

你可能感兴趣的:(html相关,Javascript,React,native)