Open cassiehuang opened 5 years ago
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError ,并不会读取到全局的tmp
let tmp;
}
(ReferenceError:XX is not defined 未声明 | undefined 声明未赋值)
顶层对象,在浏览器内指window对象,node环境指global对象
import getGlobal from 'system.global';
const global = getGlobal(); //将顶层对象放入变量global。
es6 新增解构
let x = 1;
let y = 2;
[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);
}
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 }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"
生成器的一个常用功能是生成状态机
let state = function*() {
while (true) {
yield 'A';
yield 'B';
yield 'C';
}
}
let collection = {
items: [],
*[Symbol.iterator]() {
for (let item of this.items) {
yield item;
}
}
};
'z' '\z' '\172' '\x7A' '\u007A' '\u{7A}' 完全相等
123
//等同于alert('123');
`my name is ${name}, ${friendName} is my friend`
//相当于 i8n(['my name is ', ', ', is my friend], name, friendName)
hi\n${1+2}
//hi\n3function foo ({x, y = 1} = {}) {}
// 当没有参数是,参数设置为{},然后解构{}赋值x===undefined, y ===1
//rest 参数只能是最后一个参数
function add(...args) {
let num = 0;
for (let i of args) { num+= i}
return num;
}
add(2,5,2);
let name = 'name'
let obj = {
} // 把表达式放在方括号内
const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find() {
return super.foo; // super.foo ===proto.foo this.foo===obj.foo
}
};
Object.setPrototypeOf(obj, proto);
obj.find() //hello
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,
};
浅拷贝
class Point {
constructor(x, y) {
Object.assign(this, {x, y}) //为对象添加属性
}
}
Object.assign(Point.prototype, {
areas(x, y) {
},
length(x, y) {
} //为对象添加方法
}) //这里是为类Point添加方法,类的所有方法都为原型方法
function clone(origin) {
//return Object.assign({}, origin);
//return Object.assign(Object.create(Object.getPrototypeOf(origin)), origin); // 先生成一个以origin原型为原型的对象,然后复制
return Object.create(Object.getPrototypeOf(origin), Object.getOwnPropertyDescriptor(origin)) //一个取得原型对象,一个取得对象的属性描述符
}
function merge(target, ...source) {
return Object.assign(target, ...source)
}
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);//p.__proto__ === Point.prototype
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指向构造函数本身)
class Point {
constructor(x, y) {}
toString() {}
}
Object.assign(Point.prototype, {
toString1() {}
})
Object.keys(Point.prototype)//["toString1"]
Object.getOwnPropertyNames(Point.prototype)// ["constructor", "toString", "toString1"]
class Logger {
printName(name = 'there') {
this.print(`hello ${name}`)
}
print(text) {
console.log(text);
}
}
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可以调用
class Foo {
static arg2 = "str"; //静态属性,不继承
name = "cassie"; // 实例属性,不是原型属性
age = 18;
get arg1() { //get方法
return this.name;
}
set arg1(name) {
this.name = name;
}
static method1() {} //静态方法,不继承
method2() {} //所有的非静态方法都为原型方法
}
class Shape {
constructor() {
if (new.target === Shape){
throw new Error('本类不能实例化')
}
}
}
class Rectangle extends Shape {
constructor() {
super();
}
}
let x = new Shape(); //报错,Shape不能被实例化,只能被继承
let y = new Rectangle(); //正确
class Shape {
}
class Rectangle extends Shape {
constructor() { super() }
}
let shape = new Shape();
let rectangle = new Rectangle();
// shape.__proto__ === Shape.prototype rectangle.__proto__ ===Rectangle.prototype 普通的对象生成
// 因为类Rectangle继承自Shape, es6的实现方式Rectangle.__proto__ === Shape
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;
}
}
a instanceof A //instanceof 运算符,相当于调用函数A[Symbol.hasInstance](a) A.__proto__[Symbol.hasInstance]
let c = [1, 2] //控制数组concat时是否展开,原型链上没有Symbol.isConcatSpreadable属性,为undefined,默认展开,修改为false后不展开
class MyArray extends Array { //默认情况下,子类MyArray[Symbol.species] === MyArray,修改这个get属性,可以控制创建衍生对象时的构造函数
static get [Symbol.species]() { return this; } //默认情况下的设置
static get [Symbol.species]() { return Array; } //map,filter返回的对象就是调用构造函数Array创建的对象。
}
let a = new Set([1, 2, 3]);
let b = new Set([2, 3, 4]);
//并集1,2,3,4
//交集 2,3
//差集a与b的差集1,b与a的差集4
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
handler 参数配置,决定拦截操作,没有拦截的情况使用原对象操作
var handler = {
get: function(target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function(target, thisBinding, args) {
return args[0];
},
construct: function(target, args) {
return {value: args[1]};
}
};
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拦截
let { proxy, revoke } = Proxy.revocable({}, {}); proxy下的[[isRevoked]]变为true
revoke();
proxy.foo //TypeError: Revoked
try {
Object.defineProperty(target, key, descriptor);
} catch (e) {
}
if (Reflect.defineProperty(target, key, descriptor)) { //success } else { // failure } //明显新写法更优,老写法如果没用try...catch报错会导致程序终止
* 让Object操作都变成函数行为,而不是命令式
```javascript
'assign' in Object // 老写法
Reflect.has(Object, 'assign') //新写法
const obj = {
foo: 1,
get foo1() {
return this.foo;
},
set foo1(value) {
this.foo = value;
}
}
const handler = {
get(target, name, receiver) {
console.log(this,target, name, receiver);
Reflect.set(target, 'foo1', 2) // ovj.foo = 2
return target.name;
}
}
const p = new Proxy(obj, handler);
Reflect.get(obj, 'foo', p); //1, obj.foo,虽然this === p,但是并没有用到
Reflect.get(obj, 'foo1', p)// obj.foo1 return p.foo, p.foo触发handler, return obj.foo === 2
Reflect.get(p, 'foo') //p.foo触发handler, return p.foo,2
//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()
//实现
const handler = {
set(target, prop, value, receiver) {
const result = Reflect.set(target, prop, value, receiver);
for (let fn of observeSet) {
fn();
}
return result;
}
}
const observeSet = new Set();
const observable = obj => new Proxy(obj, handler);
const observe = fn => observeSet.add(fn);
//使用
const obj = {
name: 'cassie',
}
let observableObj = observable(obj);
observe(function(){ console.log('ok1') });
observe(function(){ console.log('ok2') });
observableObj.name = 'my'; //ok1, ok2,发布成功
let p1 = new Promise((resolve, reject) => {
console.log('p1 start');
setTimeout(() => resolve('11'), 3000);
}) //new Promise()时会立即执行,只是状态还是pending,等待resolve,reject改变状态
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);
})
setTimeout(function () {
console.log('three'); // 下轮事件循环时执行
}, 0);
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()
function makeIterator(array) {
let index = 0;
return {
next() {
return index < array.length ?
{ value: array[index++] } :
{ done: true }
}
}
}
一种数据结构只要部署了Symbol.iterator属性,就认为是可遍历对象
class Range {
constructor(value, end) {
Object.assign(this, {value, end});
}
next() {
if (this.value < this.end) {
return { done: false, value: this.value++ };
}
return { done: true, value: undefined };
}
[Symbol.iterator]() {
return this; //将Symbol.iterator部署到原型上
}
}
let b = new Range(0, 3);
for (let i in b) {
console.log(i);
}
let generator = function* (){
yield* [2, 3, 4];
}
let it = generator();
it.next(); // { value: 2, done: false }
let myIterator = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
}
}
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跳出
function* ge() {
yield 1;
yield 2;
return 3; // 结束执行,如果没有return,最后一个状态时{done: true, value: undefined}
}
function* demo() {
console.log('Hello' + (yield)); //yield后没值,所以第一个是undefined,console.log执行在yield之后,所以要下一次next()执行
console.log('Hello' + (yield 123));
}
let it = demo();
it.next(); // {value: undefined, done: false}
it.next() // {value: 123, done: false} console: "Helloundefined"
it.next() // {value: undefined, done: true} console: "Helloundefined"
在Generator函数内部,调用另一个Generator函数,只要用到yield* 表达式,才能执行
Array.prototype.myFlat = function(n) {
let f = n;
if (n < 1) return this;
function* tree(val) {
if (Array.isArray(val) && f >= 0) {
f--;
for (let i = 0; i < val.length; i++) {
yield* tree(val[i]);
}
} else {
yield val;
}
}
return [...tree(this)];
} //实现Array.prototype.flat()
function* asyncJob() { // asyncJob就是一个协程
var f = yield readFile(fileA); // 将执行权交给异步函数readFile,可以在合适的时机(fileA读取成功)时调用it.next() 收回控制权,继续执行到下一次转移执行权
}
f(x + 5) //调用函数时,先执行x + 5,然后将其值作为参数存入,即为传值调用
f(x + 5) //在函数体内,只有用到了x + 5所代表的参数,才计算其值,称为传名调用
const Thunk = function(fn) {
return function(...args) {
return function(callback) {
return fn.call(this, ...args, callback);
}
}
}
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);
babel