cassiehuang / study

学习笔记整理
2 stars 0 forks source link

《ECMAScript 6 入门》 #18

Open cassiehuang opened 5 years ago

cassiehuang commented 5 years ago

babel

cassiehuang commented 5 years ago

let / const

cassiehuang commented 5 years ago

声明变量

cassiehuang commented 5 years ago

顶层对象的属性

顶层对象,在浏览器内指window对象,node环境指global对象

global对象

cassiehuang commented 5 years ago

变量的解构赋值

es6 新增解构

[x, y] = [y, x]; //先[y,x]=[2,1],然后解构

2.  函数参数和返回值解构,json对象解构
3.  函数参数默认值, 避免了函数内部 let foo = obj.foo || 'false';这样的语句
4. 遍历Map解构,获取键名和键值
for  of 遍历循环(任何部署了Iterator接口的对象都可以用)
```javascript
const map = new Map();
map.set('first','hello');
map.set('second','world');

for (let [key, value] of map) {
  console.log(key,value);
}
  1. 输入模块的指定方法 const { SourceMapConsumer, SourceNode } = require("source-map");
  2. 将集合转为数组 let set = new Set([1,2,3,3,5,6,4]); let array = [...set] //[1,2,3,5,6,4]
cassiehuang commented 5 years ago

扩展 generator iterator

cassiehuang commented 5 years ago

字符串扩展

cassiehuang commented 5 years ago

数值的扩展

es6在Math对象上新增17个与数学相关的方法

cassiehuang commented 5 years ago

函数的扩展

cassiehuang commented 5 years ago

扩展 Function对象

cassiehuang commented 5 years ago

数组的扩展

扩展运算符应用

cassiehuang commented 5 years ago

对象的扩展

Object数据结构

const obj = { find: function () { return super.foo; } }; //会报错,只能是对象方法的简写法,js引擎才能解析

#### 对象的解构赋值
* 扩展运算符的解构赋值,只读取对象本身的所有可遍历的属性,不复制继承自原型对象上的属性
```javascript
let {x, y, ...newObj} = {x:1, y: 2, a: 3, b: 4};  //newObj === {a:3, b: 4}

const o = Object.create({ x: 1, y: 2 });
o.z = 3;  // o === {z:3, __proto__: {x: 1, y: 2}}
let { x, ...newObj } = o; // x===1, newObj === {z: 3}
let { y, z } = newObj; //y=== undefined,z === 3;
let newVersion = {
  ...previousVersion,
  name: 'New Name' // Override the name property
};
let newVersion = {
  name: 'default Name' // 设置默认属性
  ...previousVersion,
};
cassiehuang commented 5 years ago

对象的新增方法

Object.is()

Object.keys() Object.values(), Object.entries()

cassiehuang commented 5 years ago

Class的基本语法

function Point(x, y) { let obj = {}; obj.x = x; obj.y=y; return obj; } var r = new Point(1,2); //这里也能生成一个新对象,但是只是执行Point函数返回的普通对象,并不是由构造函数生成,所以r.proto!==Point.prototype

* es6 为了更接近传统的面向对象语法,引入Class(类)这个概念,作为对象的模板,关键字class,定义类
```javascript
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
// 等同于 Point.prototype = { constructor() {},  toString(){}}
// typeof Point (function) 
// Point === Point.prototype.constructor(构造函数的prototype默认有两个属性,constructor指向构造函数本身)

logger = new Logger(); printName = logger.printName; printName() //此时函数内部this指向全局作用域,报错 class Logger { constructor() { this.printName = this.printName.bind(this); //或

} } //在构造函数内,绑定printName的this // 在构造函数内,使用箭头函数。这两种解决方式都会新增一个方法,而非改变了原型方法

#### 静态方法
* 定义在类的方法,都会被实例继承,加上static 关键字,则表示该方法不会被继承,而是直接通过类调用
* 静态方法内如果包含this,则this指向类本身,而不是实例
```javascript
class Foo {
  static bar() {
    console.log(this.name);  //this指向类Foo,this.name === 'Foo''
  }
  baz() {
    console.log(super.bar);
  }
 bar() {
    console.log('world');
 }
}
console.dir(Foo) //static定义的静态方法,则Foo类上新增该方法,而baz则定义在Foo.prototype上
let foo = new Foo();
foo.baz() //world  可通过super指向当前对象(foo)的原型对象(foo.__proto__,也为Foo.prototype)
// Foo.baz()报错,Foo的原型链上没有baz,然而生成的实例,foo.__proto__ === Foo.prototype,存在foo的原型链上,foo可以调用

实例属性的新写法

cassiehuang commented 5 years ago

class的继承

function Shape() {} function Rectangle() { Shape.call(this) //调用父类构造方法 } Rectangle.prototype = new Shape(); //先将shape复制到Rectangle.prototype,这里只生成了proto,没有constructor属性 Rectangle.prototype.constructor = Rectangle; //原型的constructor指向构造函数。 Rectangle.prototype.add = function(){} //添加构造函数Rectangle自己的原型方法 let rectangle = new Rectangle(); //rectangle.proto === Rectangle.prototype,

* es5的继承,先创建子类的this,然后将父类的方法添加到this上(Shape.call(this))
* es6的继承,是先将父类的实例对象属性添加到this上(super()),然后子类的构造函数在修改this
#### Object.getPrototypeOf()
* es6 Object.getPrototypeOf(Rectangle) === Shape 判断一个类是否是另一个类的父类
* 只能是es6实现的继承,es5通过原型链方式实现的为false
* 只能是父子类,不能判断祖辈类
* es6实现的或者es5实现的Rectangle.prototype = new Shape(); instanceof和isPrototypeOf 检测都通过
#### super关键字
* 可以作为函数使用,代表父类构造函数 super() [Parent.prototype.constructor.call(this)]
* 也可以当对象使用,普通方法中指向父类的原型对象,静态方法中,指向父类
```javascript
class A {
  constructor() {
    this.x = 1;
  }

  static method() {
    console.log(this.x);
  }

  method3() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();  //A.prototype.constructor.call(this),super作为函数使用,只能用于子类的构造函数内
    console.log(this);
    this.x = 2;
    super.x = 5;  // 赋值的时候相当于this.x = 5
    console.log(super.x);  //取值的时候当当与A.prototype.x,所以为undefined
    console.log(this.x);
  }

  static method1() {
    console.log(super.x);  // super.x === A.x
    super.method(); // super 作为对象使用,在静态方法内,super === A,里面的this === B
  }

  method4() {
    this.x = 3;
    super.x = 4;
    console.log(super.x);
    console.log(this.x);
    super.method3(); //super 作为对象使用,在普通方法内, super === A.prototype, 里面的this === b;
  }
}

类的prototype和proto属性

cassiehuang commented 5 years ago

Symbol

cassiehuang commented 5 years ago

Set

let c = new Set([...a, ...b]); let d = new Set([...a].filter(x => b.has(x))); let e = new Set([...a].filter(x => !b.has(x)));

#### WeakSet
* WeakSet 的成员只能是对象
* WeakSet中对象都是弱引用,不计入垃圾回收机制依赖的引用计数,所以成员可能随时会消失,所以WeakSet适合临时存放一组对象
* WeakSet不可遍历
* WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。
```jacascript
const foos = new WeakSet();
class Foo {
  constructor() {
    this.name = 'cassie';
    foos.add(this)
  }
  method() {
    if (!foos.has(this)) {
      throw new TypeError('只能在Foo实例上调用');
    }
  }
}
let foo1 = new Foo();
let foo2 = new Foo();

foo1.method();
foo1.method.call(null);  //这里会报错,因为非实例对象不存在于foos中,删除了实例,也不用考虑foos

Map

cassiehuang commented 5 years ago

Proxy

var fproxy = new Proxy(function(x, y) { return x + y; }, handler); //fproxy = { [[handler]], [[target]], [[isRevoked]] }

fproxy(1, 2) // 1 new fproxy(1, 2) // {value: 2} fproxy.prototype === Object.prototype // true fproxy.foo === "Hello, foo" // true

* get 拦截属性的读取
```javascript
let proto = new Proxy({}, {
  get(target, propertyKey, receiver) {
    console.log('GET ' + propertyKey);
    return target[propertyKey];
  }
});

let obj = Object.create(proto);
obj.foo   // GET foo; __proto__ 是一个Proxy实例
obj.foo = "haha";
obj.foo  //haha, 在obj上能获取到该值,还未到原型链上取值

// proto就是一个proxy实例
proto.foo1 = "haha";  //foo1被设置在[[target]]下
proto.foo1; // 会被get拦截
cassiehuang commented 5 years ago

Reflect

}

if (Reflect.defineProperty(target, key, descriptor)) { //success } else { // failure } //明显新写法更优,老写法如果没用try...catch报错会导致程序终止

* 让Object操作都变成函数行为,而不是命令式
```javascript
'assign' in Object  // 老写法
Reflect.has(Object, 'assign')  //新写法

//Proxy和Reflect里面的参数receiver,Proxy里指代proxy实例,Reflect用于改变this // 注意在Proxy中使用receiver一定要慎重,否则有可能造成死循环,例如如果return target.name修改为recevicer.name, get拦截会不断调用

* Reflect.construct(A, [...args])  等同于new A(...args),可以用来创建实例
* Reflect.apply  可以简化给一个函数绑定 this对象并执行的操作,更容易理解
```javascript
Math.max.apply(null, arr);
Object.prototype.toString.apply(arr)
Function.prototype.call.apply(Math.max, null, arr);
// 旧写法 fn.apply(this, args);
// 如果fn部署了自己的apply方法,则需要使用Function.prototype.apply方法,即Function.prototype.apply.call(fn, this, args);
Reflect.apply(fn, this, args); 
//新写法就是调用默认的apply,所以等价于Function.prototype.apply.call()

使用Proxy实现观察者模式

cassiehuang commented 5 years ago

Promise

let p2 = new Promise((resolve, reject) => { console.log('p2 start'); setTimeout(() => resolve(p1), 1000); //resolve一个promise,等待p1 resolved或rejected })

p2.then(result => { console.log(result); //11 })

* resolve,reject是在本轮事件循环的末尾执行,总是晚于同步任务,所以resolve和reject并不会终结Promise参数函数的执行。
* 一般来说,resolve,reject后的后续操作应放在then()后执行,所以一般加上return,return resolve()
#### Promise.prototyoe.then
* then方法返回的是一个新的Promise实例,可以使用链式方法, 前一个方法return的值就是后一个方法的参数
* .then()方法总是返回promsie实例,如果return 普通对象,会包装成Promsie.resolve(obj), 如果没有return,则会包装为Promise.resolve();
* .then()内总是期望传入函数作为回调,没有传入回调函数,则会返回promise本身
* 在then()内可以手动return Promise.reject()会进入,则会进入catch内
* promsie.then().then().catch()  catch内会捕获promsie回调内的错误和reject(),then内的错误和reject()
#### Promsise.prototype.catch
* .catch()是.then(null, rejection)的别名
* .catch() 会捕获前面所有.then()和promsie回调内的错误和reject
#### Promise.prototype.finally()
* es2018引入.finally()方法  babel-polyfill可以转码
* 不论promise的最后状态是什么都会执行
#### Promise.all()
* 用于将多个promise实例包装成新的Promise实例,接受数组作为参数
* const p = Promise.all([p1, p2, p3]) //p1,p2,p3如果不是Promise实例,会调用Promise.resolve()将其转为promise实例
* p1,p2,p3都resolved,此时p才resolved,此时p1,p2,p3的返回值组成数组作为p回调的参数
* p1,p2,p3其中一个reject,p马上reject,此时第一个reject的promise的返回值作为p的参数
* 如果p1,p2,p3自己定义了catch,如果p1发生了错误,实际上p1 = new Promsie().then().catch()到catch里,然后返回一个新的promise,状态时resolved,
```javascript
const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);  //如果promsie没有自己catch reject,则p2状态为reject,当catch了后,promise实际上return e ,相当于 resolve(e) ,p2状态为resolved
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
// 在获取数据上的应用
let promises = ['/url1', 'url2', 'url3'].map((val) => {
  return getData(val);  // 生成一个promise实例数组
})

Promise.all(promises)
  .then((result) => {
    result.forEach(() => {});  
    // 如果promises没有catch,则必须全部获取到数据才会到then
    // 如果promsies,catch了数据,则一定会到then,可以根据结果渲染需要的数据
  })
  .catch((error) => {
    console.log(error);
  })

Promise.race()

Promise.resolve().then(function () { console.log('two'); //本轮事件结束时执行 });

console.log('one'); //

// one // two // three

### Promise.reject
### Promise.try
* Promise.try是一个提案,事实上Promise库早就提供了这个方法
* 可以更好的管理异常
* 同步函数同步执行,异步函数异步执行,如果是同步错误也可以捕获在catch内处理
```javascript
//在new Promsie内fn是同步函数就先执行,然后promise状态变为resolved
//fn是异步函数,就等待异步函数的状态,异步函数的状态就是promsie的状态
new Promise(resolve => resolve(fn()) ).then().catch()
//等价于
Promise.try(fn()).then().catch()
cassiehuang commented 5 years ago

Iterator和for.....of

let myIterator = { *[Symbol.iterator]() { yield 1; yield 2; } }


#### 遍历器对象的return(), throw()
* 遍历器对象除了具有next()方法外,还可以具有return方法和throw方法
* next必须部署,return,throw方法可选部署
* 如果for......of循环提前退出,如出错或break,就会调用return方法
* 注意这里的return方法必须返回一个对象,这是Generator规格决定的
#### es6借鉴c++、java、c#、Python语言,引入了for.....of 循环,作为遍历所有数据结构的统一方法
* forEach内不能使用break跳出
cassiehuang commented 5 years ago

Generator

理解next(), throw(), return()

Generator 与状态机

cassiehuang commented 5 years ago

Generator函数的异步应用

const readFileThunk = Thunk(readFile); readFileThunk('test.html')((err, data) => {});

#### Thunkify模块
* 生成环境,建议使用Thunkify模块做转换,
#### Thunk函数的真正威力,在于可以自动执行Generator函数,实现方式是
```javascript
// 通过传入函数和函数参数, 返回一个只需要传入回调的函数
const thunk = function(fn) {
  return function(...args) {
    return function(callback) {
      fn.call(this, ...args, callback)
    }
  }
}
// 创建Generator函数自动执行函数,构造一个回调,来作为参数传入生成的Thunk函数中
// 这个函数就是it.next().value, 要在Generator函数内得到回调中的返回值,通过it.next(data)传递出去
const run = function(ge) {
  const g = ge();
  const callback= function(err, data) {
    let r = g.next(data);
    if (r.done) return;
    r.value(callback);
  }

  callback();
}
// Generator函数,yield每一个Thunk函数
const readFileThunk = thunk('fs.readFile');
var g = function* (){
  var f1 = yield readFileThunk('fileA');
  var f2 = yield readFileThunk('fileB');
  // ...
  var fn = yield readFileThunk('fileN');
};

run(g);

co模块的原理

cassiehuang commented 5 years ago

装饰器 Decorator