一个数据结构只要部署了Symbol.iterator
属性,就被视为具有 iterator 接口,就可以用for...of
循环遍历它的成员。也就是说,for...of
循环内部调用的是数据结构的Symbol.iterator
方法。
// yield语句遍历完全二叉树
// 下面是二叉树的构造函数,
// 3个参数分别是左树、当前节点和右树
function Tree(left, label, right){
this.left = left;
this.label = label;
this.right = right;
}
// 下面是中序(inorder)遍历函数。
// 由于返回的是一个便利器,所以要用generator函数。
// 函数体内采用递归算法,所以左树和右树要用yield*遍历
function* inorder(t){
if(t){
yield* inorder(t.left);
yield t.label;
yield* inorder(t.right);
}
}
// 下面生成二叉树
function make(array){
// 判断是否为叶节点
if(array.length == 1) return new Tree(null,array[0], null);
return new Tree(make(array[0]), array[1], make(array[2]));
}
let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
// 遍历二叉树
var result = [];
for(let node of inorder(tree)){
result.push(node);
}
console.log(result);
var clock = function* (){
while(true){
console.log('Tick!');
yield;
console.log('Tock!');
yield;
}
}
clock();
// 由于JavaScript是单线程语言,只能保持一个调用栈。引入协程以后,
// 每个任务可以保持自己的调用栈。这样做的最大好书,就是抛出错误的时候,
// 可以找到原始的调用栈。不至于像异步操作的回调函数那样,
// 一旦出错原始的调用栈早就结束。Generator函数是对协程的实现,但属于不完全实现。
// Generator函数可以改善代码运行流程
function* longRunningTask(value){
try{
var value2 = yield step1(value);
var value3 = yield step2(value2);
var value4 = yield step3(value3);
var value5 = yield step4(value4);
// Do something with value4
} catch(e) {
// handle any error from step1 through step4
}
}
// 使用一个函数按次序自动执行所有步骤
scheduler(longRunningTask(initalValue));
function scheduler(task){
var taskobj = task.next(task.value);
// 如果Generator函数未结束,就继续调用
if(!taskObj.done){
task.value = taskObj.value;
scheduler(task);
}
}
// Promise的最大问题是冗余,原来的任务被Promise包装之后,
// 无论什么操作,一眼看去都是很多then的堆积,原来的语义变得很不清楚。 协程:多个线程互相协作,完成异步任务。
// next返回值的value属性石Generate函数向外输出数据;
// next方法还可以接受参数,向Generator函数体内输入数据
function* gen(x){
var y = yield x + 2;
return y;
}
var g = gen(1);
g.next() //{value:3, done: false}
g.next(2) //{value:2, done: true}
// 传名调用
function f(m){
return m*2;
}
f(x+5);
// JavaScript语言是传值调用,
// 手动执行其实就是用then方法层层添加回调函数 手写自动执行器
function run(gen){
var g = gen();
function next(data){
var result = g.next(data);
if(result.done){
return result.value;
}
result.value.then(function(data){
next(data);
});
}
next();
}
// Node提供Stream模式读写数据,特点是一次只处理数据的一部分,
// 数据被分成一块一块依次处理,就好像“数据流”一样。
// async函数用一句话来说,它就是Generator函数的语法糖。
// async函数对Generator函数的改进体现在4点:1、自带执行器;2、更好的语义;3
// 更广的适用性:async函数的await命令后面,
// 可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作);
// 4、返回值是Promise:async函数完全可以看作由多个异步操作包装成的一个Promise对象,
// 而await命令就是内部then命令的语法糖。
//
// async指定多少毫秒后输出一个值
function timeout(ms){
return new Promise((resolve) =>{
setTimeout(resolve, ms);
});
}
async function aysncPrice(value, ms){
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
// 如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。
// 这样便于以后添加返回值,以及更改返回值的顺序
// bad
function processInput(input){
return [left, right, top, bottom];
}
// good
function processInput(input){
return {left, right, top, bottom};
}
const {left, right} = processInput(input);
// bad
[1,2,3].map(function(x){
return x*x;
});
// good
[1,2,3].map((x)=>{
return x*x;
});
// best
[1,2,3].map(x=>x*x);
// 箭头函数取代Function.prototype.bind,不再用self/_this/that绑定this
// bad
// const self = this;
const boundMethod = function(...params){
return method.apply(self,params);
}
// acceptable
const boundMethod = method.bind(this);
// best
const boundMethod = (...params) => method.apply(this, params);
// 多个await命令后面的异步操作如果不存在继发关系,最好让它们同时触发
// 写法一
let [foo, bar] =await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
// 上面两种写法中,getFoo和getBar都是同时触发,这样就会缩短程序的执行时间
// async函数的实现原理就是将Generator函数和自动执行器包装在一个函数里
async function fn(args){
// ...
}
// 等同于
function fn(args){
return spawn(function* (){
// ...
});
}
// spawn函数就是自动执行器
function spawn(genF){
return new Promise(function(resolve, reject){
var gen = genF();
function step(nextF){
try{
var next = nextF();
}catch(e){
return reject(e);
}
if(next.done){
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v){
step(function(){
return gen.next(v);
}),function(e){
step(function(){
return gen.throw(e);
});
}
})
}
step(function(){
return gen.next(undefined);
})
})
}
// aysnc实现:假如某个DOM元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。
// 如果当中与一个动画出错,就不再继续执行,而返回上一个成功执行的动画的返回值。
async function chainAnimationsAsync(elem, animations){
var ret = null;
try{
for(var anim of animations){
ret = await anim(elem);
}
}catch(e){
/* 忽略错误,继续执行 */
}
return ret;
}
// for await...of循环用于遍历异步的Iterator接口
async function f(){
for await (const x of createAsyncIterable(['a','b'])){
console.log(x);
}
}
// 基本上,ES6中的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰,更像面向对象编程的语法而已。
// Symbol值,达到私有方法和私有属性的效果
const bar = Symbol('bar');
const snaf = Symbol('snaf');
export default class myClass{
// 公有方法
foo(baz){
this[bar](baz);
}
// 私有方法
[bar](baz){
return this[snaf] = baz;
}
}
// 类的方法内部如果含有this,它将默认指向类的实例。一旦单独使用该方法,很可能会报错。
// 父类的静态方法可以被子类继承
class Foo {
static classMethod(){
return 'hello';
}
}
class Bar extends Foo{}
Bar.classMethod() // 'hello
// ES6可以自定义原生数据结构(比如Array、String等)的子类,这是ES5无法做到的。
class MyArray extends Array{
constructor(...args){
super(...args);
}
}
var arr = new MyArray();
arr[0] = 12;
arr.length // 1
arr.length = 0;
arr[0] // undefined
// 装饰器改写React与Redux库结合代码
class MyReactComponent extends React.Component{}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
// 可改写成如下
@connect(mapStateToProps, mapDispatchToProps)
export defulat class MyReactComponent extends React.Component{}
// 由于存在函数提升,修饰器不能用于函数。类是不会提升的,所以就没有这方面的问题。
// 将Mixin写成一个修饰器
export function mixins(...list){
return function(target){
Object.assign(target.prototype, ...list);
}
}
import {mixins} from './mixins';
const Foo = {
foo(){
console.log('foo')
}
};
@mixins(Foo)
class MyClass{}
let obj = new MyClass();
obj.foo() // "foo"
// CommonJs模块就是对象,输入时必须查找对象属性
// 如下代码只有运行时才能得到这个对象,称为"运行时加载",导致完全没办法在编译时进行"静态优化"
let {stat,exists,readFile} = require('fs');
// ES6"编译时加载"或者叫静态加载
import {stat,exists,readFile} from 'fs';
import { Socket } from 'dgram';
// 严格模式不能使用前缀0表示八进制数,否则报错
// 接口改名
export {foo as myFoo} from "my_module";
// defer是“渲染完再执行”,async是“下载完就执行”。
// CommonJs模块输出的是值的肤质,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
// ES6模块不会缓存结果,而是动态地去被加载的模块取值,并且斌梁总是绑定其所在的模块。
// 总是用Class取代需要prototype的操作。因为Class的写法更简洁,更易于理解。
// bad
function Queue(contents = []){
const value = this._queue[0];
this._queue.splice(0,1);
return value;
}
// good
class Queue{
constructor(contents = []){
this._queue = [...contents];
}
pop(){
const value = this._queue[0];
this._queue.splice(0,1);
return value;
}
}
// .eslintrc
{
"extends": "eslint-config-airbnb"
}
// WebGL,就是浏览器与显卡之间的通信接口,为了满足JavaScript与显卡之间大量、实时的数据交换。
// 它们自建的数据通信必须是二进制的,而不能是传统的文本格式。
// 二进制数组并不是真正的数组,而是类似数组的对象
// TypedArray视图与DataView视图的一个区别是,它不是一个构造函数,而是一组构造函数,代表不同的数据格式。TypedArray的数组成员都是同一个数据类型,而DataView的数组成员可以是不同的数据类型。
// 网页canvas元素输出的二进制像素数据就是TypedArray数组
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0,0,canvas.clientWidth,canvas.height);
var unit8ClampedArray = imgaeData.data;
// Websocket可以通过ArrayBuffer发送或接收二级制数据
var websocket = new WebSocket('ws://127.0.0.1:8081');
socket.binaryType = 'arraybuffer';
// Wait until socket is open
socket.addEventListener('open', function(event){
// send binary data
var typedArray = new Uint8Array(4);
socket.send(typedArray.buffer);
});
// Receive binary data
socket.addEventListener('message', function(event){
var arrayBuffer = event.data;
// ...
});