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()并不一定遵循此规定
- 数字按升顺排列
- 所有的字符键,以加入到Object时的顺序
- 所有的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
浏览器中使用模块
<script>
中通过src来加载模块<script>
内联代码- 通过Worker加载
可以指定type的值为module, 以模块加载,而不是角本来执行
<!-- load a module JavaScript file -->
<script type="module" src="module.js"></script>
<!-- include a module inline -->
<script type="module">
import { sum } from "./example.js";
let result = sum(1, 2);
</script>
按顺序加载模块
<!-- this will execute first -->
<script type="module" src="module1.js"></script>
<!-- this will execute second -->
<script type="module">
import { sum } from "./example.js";
let result = sum(1, 2);
</script>
<!-- this will execute third -->
<script type="module" src="module2.js"></script>
- 下载和解析
module.js
- 递归下载module1.js中import的资源
- 解析inline模块
- 递归下载inline中的import的资源
- 下载和解析module2.js
- 递归下载module2中的import资源
当加载完成时,代码不会被执行,直到整个文档被解析. 当文档解析完成时,发生以下行为
- 递归执行module1.js中import的资源
- 执行module1.js
- 递归执行inline模块中加载的资源
- 执行inline模块
- 递归执行module2.js中import资源
- 执行module2.js
defer 属性会被忽略
<script type="module">
, 因为module默认是defer的行为
<!-- no guarantee which one of these will execute first -->
<script type="module" async src="module1.js"></script>
<script type="module" async src="module2.js"></script>
不能保证module1和module2的加载顺序
Loading Modules as Works
Work执行的上下文,脱离了web page的上下文
// load script.js as a script
let worker = new Worker("script.js");
为了支持模块的加载, 可以通过第二个参数传递以module方式加载,默认为script.
// load module.js as a module
let worker = new Worker("module.js", { type: "module" });
worker加载script与module的区别:如果是script, 它必须与网页是同源的,而module默认是受同源的限制,但可以通过Cross-Origin Resource Sharing(CORS)头来访问。script可以使用importScripts()方法加载额外的其它角本,但module中不能使用importScripts(),而应该使用import代替.
浏览器模块中指定资源
/
从根目录中加载资源./
当前目录中加载资源../
上级目录中加载资源- URL格式
比如模块https://www.example.com/modules/module.js
// imports from https://www.example.com/modules/example1.js
import { first } from "./example1.js";
// imports from https://www.example.com/example2.js
import { second } from "../example2.js";
// imports from https://www.example.com/example3.js
import { third } from "/example3.js";
// imports from https://www2.example.com/example4.js
import { fourth } from "https://www2.example.com/example4.js";
www2.example.com
需要CORS头,允许跨域访问
以下是非法的
// invalid - doesn't begin with /, ./, or ../
import { first } from "example.js";
// invalid - doesn't begin with /, ./, or ../
import { second } from "example/index.js";
以上的方式是非法的,即使在<script>
src中可以这样使用。import和<script>
是不同的行为.
附录A
整数
识别整数
ECMAScript6添加了Number.isInteger()方法。虽然javascript使用IEEE754表示number, 但float和iterger存储方式是不一样的。Number.isInteger()就是利用了这种存储的不同. 当调用这个方法时,Javascript引擎查找底层值,以确定是否为整型. 一个数值看起来是float, 但实际上是以integer存储,所以Number.isInteger()返回true. 举例来说
console.log(Number.isInteger(25)); // true
console.log(Number.isInteger(25.0)); // true
console.log(Number.isInteger(25.1)); // false
整数安全
IEEE754整数实际可以表示的值为-$ 2^{53}$和 2 53 2^{53} 253, 超出这个范围的整数就不能表示
console.log(Math.pow(2, 53)); // 9007199254740992
console.log(Math.pow(2, 53) + 1); // 9007199254740992
ECMAScript6引入了Number.isSafeInteger()方法来识别一个整数是否是有效的。同时它通过Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER 来表示整数的上限和下限.Number.isSafeInteger()可以保证一个值是个整数,而且是一个安全的整数
var inside = Number.MAX_SAFE_INTEGER,
outside = inside + 1;
console.log(Number.isInteger(inside)); // true
console.log(Number.isSafeInteger(inside)); // true
console.log(Number.isInteger(outside)); // true 它是一个整数,但不是一个安全的整数
console.log(Number.isSafeInteger(outside)); // false