ES6 学习笔记

推荐前端开发学习javaScript网站

https://zh.javascript.info/object-basics?map

根据阮一峰阮一峰ES6学习整理

.koa2推荐阅读

Nodejs API文档

vuejs API文档

eslint

vue Eslint

我觉得别人整理的不错的ESLint

javascript设计模式推荐阅读

Typescript库

Babel---JavaScript 编译器

上面学的差不多了,可以看看引擎,对于底层优化有很大帮助

v8 javascript engine引擎

a compiler engineer that loves giving talks.

A tour of V8: Garbage Collection

精灵图标大全

目录

推荐前端开发学习javaScript网站

https://zh.javascript.info/object-basics?map

一、let、const

(一)let

1、不存在变量提升  暂时性死区

2、同一作用域里,不能重复定义变量

(二)const

1、只读常量,一旦声明,常量的值就不能改变

2、必须在定义的时候就赋值,才能使用,不能后赋值

3、只在声明所在的块级作用域内有效。

let、const、class命令声明的全局变量,不属于顶层对象的属性。

 

二、解构赋值

(一)结构两边格式要保持一致

(二)变量名与属性名不一致时

(三)指定默认值

(四)函数参数结构赋值

(五)用途

三、字符串

(一)字符串模板

(二)查找字符串

四、函数

(一)函数参数可以是默认参数

(二)扩展运算符,reset

使用注意点

五、数组

(一)循环

(二)es6 数组操作

1、扩展运算符

2、Array.form()

3、Array.of()

4、find() findIndex()

5、fill()

6、includes()

7、for..of entries() keys() values()

六、对象

(一)简洁写法

(二)Symbol 新增的对象类型

1、描述

2、作为属性名的Symbol

3、Symbol.for

(三)Super

(四)新增的方法

1、Onbject.is()

2、Object.assign()

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

七、Set和WeakSet

(一)Set

(二)WeakSet

八、Map 和 WeakMap

(一)Map

(二)WeakMap

九、Promise

(一)语法

(二)catch

(三)Promise.all

十、Generator函数

(一)特征

(二)yield* 表达式

(三)作为对象属性的Generator函数

(四)应用

异步编程

十一、Class

(一)定义

(二)constructor方法

(三)类的实例

(四)取值函数(getter) 存值函数(setter)

(五)属性表达式

(六)Class表达式

(七)注意点

(八)静态方法

(九)静态属性

(十)实例属性

(十一)私有方法和私有属性

十二、Class继承extends

(一)简介

(二)Object.getPrototypeOf() 从子类上获取父类

(三)super关键字

(四)类的prototype属性和 _proto_属性

(五)原生构造函数的继承

(六)Mixin模式的实现

十三、Module模块化

(一)import 和 default

(二)export default 命令

(三)export 和 default 的复合写法

(四)模块的继承

(五)跨模常量

(六)import()函数

适用场合

注意点

(七)浏览器加载

(八)ES6模块 与 commenJs

(九)node.js加载

(十)内部变量

(十一)循环加载

十四、async、await

(一)async

(二)读取文件案例

Promise

Generator

async

(三)特点

(四)解决async函数中跑出错误,中断影响之后代码

十五、Proxy

语法

支持拦截操作方法

案例一:拦截读取操作判断对象中是否有该属性

案例二:实现生成DOM节点的通用函数dom

this问题

十六、Reflect

十七、编程风格

(一)变量 块级作用域

(二)字符串

(三)结构赋值

(四)对象

(五)数组

(六)函数

(七)Map结构

(八)Class

(九)模块

(十)ESLint



一、let、const

(一)let

匿名立即执行函数 块级作用域 IIFE

{

     //TODO

}

ES6的块级作用域必须有大括号{}

1、不存在变量提升  暂时性死区

先定义再使用

{

alert(a);//TDZ

let a = 5;

}

2、同一作用域里,不能重复定义变量

{

   {

      let a = 1;

}

let a = 2;

//a = 2

}

(二)const

1、只读常量,一旦声明,常量的值就不能改变

2、必须在定义的时候就赋值,才能使用,不能后赋值

3、只在声明所在的块级作用域内有效。

const arr=['apple','orange'];

// arr = ['banana']//Assignment to constant variable.

arr.push('banana')

console.log(arr);//["apple", "orange", "banana"]

let、const、class命令声明的全局变量,不属于顶层对象的属性。

var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined

 

二、解构赋值

(一)结构两边格式要保持一致

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]

(二)变量名与属性名不一致时

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

(三)指定默认值

var {x, y = 5} = {x: 1};
x // 1
y // 5

默认值生效的条件是,对象的属性值严格等于undefined

var {x = 3} = {x: undefined};
x // 3

var {x = 3} = {x: null};
x // null
// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error

因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。

// 正确的写法
let x;
({x} = {x: 1});

(四)函数参数结构赋值

function move({x = 0, y = 0} = {}) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

(五)用途

(1)交换变量的值

let x = 1;
let y = 2;

[x, y] = [y, x];

上面代码交换变量xy的值,这样的写法不仅简洁,而且易读,语义非常清晰。

(2)从函数返回多个值

函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

// 返回一个数组

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

// 返回一个对象

function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();

(3)函数参数的定义

解构赋值可以方便地将一组参数与变量名对应起来。

// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});

(4)提取 JSON 数据

解构赋值对提取 JSON 对象中的数据,尤其有用。

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number);
// 42, "OK", [867, 5309]

上面代码可以快速提取 JSON 数据的值。

(5)函数参数的默认值

jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
} = {}) {
  // ... do stuff
};

指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || 'default foo';这样的语句。

(6)遍历 Map 结构

任何部署了 Iterator 接口的对象,都可以用for...of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。

const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world

如果只想获取键名,或者只想获取键值,可以写成下面这样。

// 获取键名
for (let [key] of map) {
  // ...
}

// 获取键值
for (let [,value] of map) {
  // ...
}

(7)输入模块的指定方法

加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。

const { SourceMapConsumer, SourceNode } = require("source-map");

三、字符串

(一)字符串模板

`${变量名}`

<ul>

</ul>

<script>

let data = [

{title: 'haha',num: 100},

{title: 'lala',num: 90},

{title: 'dada',num: 80}

]

window.onload = function(){

let oUl = document.querySelector('ul');

for(let i = 0 ; i < data.length; i++){

var oLi = document.createElement('li');

oLi.innerHTML = `<span>${data[i].title}</span>

<span>${data[i].num}</span>

<a href="">详情</a>`;

oUl.appendChild(oLi);

}

}

(二)查找字符串

str.indexOf('a')   找到返回索引,没找到返回-1

str.includes('a') 返回true/false

判断浏览器

if(navigator.userAgent.includes('Chrome')){

alert(true)

}

字符串以谁开头

str.startsWith(检测的东西)  (url)

以谁结尾

str.endsWith(检测的东西)

重复字符串

str.repeat(重复次数)

填充字符串

向前填充

str.padStr(str.length+padStr.length,填充的东西)

向后填充

str.padStr(str.length+padStr.length,填充的东西)

四、函数

(一)函数参数可以是默认参数

函数参数默认已经定义,不能再使用let或const声明

 

function show(a = 10){

let a = 100;

console.log(a);

}

show();//Identifier 'a' has already been declared

(二)扩展运算符,reset

...numbers

展开

let arr = [1,2,3,4];

console.log(...arr);//1 2 3 4

重置

function f(...a){

console.log(a);// [1, 2, 3, 4]

}

f(1,2,3,4)

剩余运算符

必须放在参数最后

function f(a,b,...c){

console.log(a,b,c);//1 2 [3, 4, 5]

}

f(1,2,3,4,5)

复制数组

let arr = [1,2,3];

let arr2 = [...arr];

(三)箭头函数

() => {}

let f = v => v;

let f = () => 5;

let sum = (sum1, sum2) => sum1 + sum2;

let sum = (num1, num2) => { return num1 + num2; }

let getTempItem = id => ({id: id, nam2: "temp"});

[1,2,3].map(x => x*x);

const numbers = (...nums) => nums;

numbers(1,2,3,4)

this问题

函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象

this对象的指向是可变的,但是在箭头函数中,他是固定的

要维护一个 this 上下文的时候,就可以使用箭头函数。

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// id: 42

箭头函数使this从动态变成静态

第一个场合是定义对象的方法,且该方法内部包括this

const cat = {
  lives: 9,
  jumps: () => {
    this.lives--;
  }
}

上面代码中,cat.jumps()方法是一个箭头函数,这是错误的。调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。

第二个场合是需要动态this的时候,也不应使用箭头函数。

var button = document.getElementById('press');
button.addEventListener('click', () => {
  this.classList.toggle('on');
});

上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。

使用注意点

箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

五、数组

(一)循环

for foreach(item,index,arr){}   map(item,index,arr){ return } filter(item,index,arr){ return true }

arr.som() 数组里只要有一个元素符合,就返回true

arr.every() 数组里所有的元素豆芽符合,才返回true

arr.reduce((prev,cur,index,arr)=>{return prev + cur}) 数组求和

map

正常情况下,需要配合return ,返回一个新数组

重新整理数据结构

let arr = [

{title: 'aaa',read:100,hot: true},

{title: 'bbb',read:100,hot: true},

{title: 'bbb',read:100,hot: true}

];

let newArr = arr.map((item,index,arr) => {

let json = {};

json.t = `@${item.t}`;

json.r = item.read + 200;

json.h = item.hot == true && 'haha';

return json;

})

console.log(newArr);

// Array(3)

// 0: {t: "@undefined", r: 300, h: "haha"}

// 1: {t: "@undefined", r: 300, h: "haha"}

// 2: {t: "@undefined", r: 300, h: "haha"}

filter 

如果返回条件为true,留下来

let arr = [

{title: 'aaa',read:100,hot: true},

{title: 'bbb',read:100,hot: false},

{title: 'bbb',read:100,hot: true}

];

let newArr = arr.filter((item,index,arr) => {

return item.hot == false;

})

console.log(newArr);

// 0: {title: "bbb", read: 100, hot: false}

 

(二)es6 数组操作

1、扩展运算符

复制数组

const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;

合并数组

[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

2、Array.form()

将类数组转换成真正的数组

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

3、Array.of()

将一组值转换为数组

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

4、find() findIndex()

返回找到的第一个符合条件的成员

[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10

findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2
function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);    // 26

上面的代码中,find函数接收了第二个参数person对象,回调函数中的this对象指向person对象。

5、fill()

填充数组

6、includes()

返回boolean值

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true

7、for..of entries() keys() values()

可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

六、对象

(一)简洁写法

(二)Symbol 新增的对象类型

防止属性名的冲突

let s = Symbol();

console.log(typeof s);// "symbol"

Symbol函数前不能使用new命令,是因为生成的 Symbol 是一个原始类型的值,不是对象,不能添加属性

七个数据类型: string  number object boolean  undefined function symbol

1、描述

const sym = Symbol('foo');
String(sym) // "Symbol(foo)"
sym.toString() // "Symbol(foo)"
sym.description // "foo"

2、作为属性名的Symbol

let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

上面代码通过方括号结构和Object.defineProperty,将对象的属性名指定为一个 Symbol 值。

注意,Symbol 值作为对象属性名时,不能用点运算符。

const mySymbol = Symbol();
const a = {};

a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"

上面代码中,因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个 Symbol 值。

同理,在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。

let s = Symbol();

let obj = {
  [s]: function (arg) { ... }
};

obj[s](123);

上面代码中,如果s不放在方括号中,该属性的键名就是字符串s,而不是s所代表的那个 Symbol 值。

3、Symbol.for

Symbol.for("bar") === Symbol.for("bar")
// true

Symbol("bar") === Symbol("bar")
// false

上面代码中,由于Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。

Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

上面代码中,变量s2属于未登记的 Symbol 值,所以返回undefined

Symbol 作为key,用for in循环,循环不出来

let sym = Symbol('aaaaa');

let json = {

a: 'aa',

b: 'bb',

[sym]: 'sss'

}

for (const key in json) {

console.log(key)// a b

}

(三)Super

super,指向当前对象的原型对象。

const proto = {
  foo: 'hello'
};

const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};

Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

上面代码中,对象obj.find()方法之中,通过super.foo引用了原型对象protofoo属性。

注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。

(四)新增的方法

1、Onbject.is()

用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

2、Object.assign()

用来合并对象

用途:赋值对象,合并参数

obj._proto_ = aomoOtherObj;

Es6: var obj = Object.create(someOtherObj);(生成操作)

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

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}

for (let value of values(obj)) {
  console.log(value); // 1, 2, 3
}

for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]

七、Set和WeakSet

(一)Set

类似于数组,成员的值都是唯一的,有序的

可以用作去除重复的数组

// 去除数组的重复成员
[...new Set(array)]

上面的方法也可以用于,去除字符串里面的重复字符。

[...new Set('ababbc')].join('')
// "abc"
  • Set.prototype.add(value):添加某个值,返回 Set 结构本身。
  • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
  • Set.prototype.clear():清除所有成员,没有返回值。
s.add(1).add(2).add(2);
// 注意2被加入了两次

s.size // 2

s.has(1) // true
s.has(2) // true
s.has(3) // false

s.delete(2);
s.has(2) // false

提供了去除数组重复成员的另一种方法。

function dedupe(array) {
  return Array.from(new Set(array));
}

dedupe([1, 1, 2, 3]) // [1, 2, 3]

 

  • Set.prototype.keys():返回键名的遍历器
  • Set.prototype.values():返回键值的遍历器
  • Set.prototype.entries():返回键值对的遍历器
  • Set.prototype.forEach():使用回调函数遍历每个成员
  • keys方法和values方法的行为完全一致。

  • let set = new Set(['red', 'green', 'blue']);
    
    for (let item of set.keys()) {
      console.log(item);
    }
    // red
    // green
    // blue
    
    for (let item of set.values()) {
      console.log(item);
    }
    // red
    // green
    // blue
    
    for (let item of set.entries()) {
      console.log(item);
    }
    // ["red", "red"]
    // ["green", "green"]
    // ["blue", "blue"]

    省略values方法,直接用for...of循环遍历 Set。

  • let set = new Set(['red', 'green', 'blue']);
    
    for (let x of set) {
      console.log(x);
    }
    // red
    // green
    // blue

    forEach()

let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9

去除数组的重复元素

let arr = [1,1,2,3];

let arr2 = [...new Set(arr)]

console.log(arr2);//[1,2,3]

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

(二)WeakSet

WeakSet 的成员只能是对象,而不能是其他类型的值。 没有size

const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set

WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

// const b = [1,2];

// const w = new WeakSet(b);

// console.log(w);//: Invalid value used in weak set

 

const b = [[1,2]];

const w = new WeakSet(b);

console.log(w);//:WeakSet {Array(2)}[[Entries]]0: Array(2)value: (2) [1, 2]__proto__: WeakSet

八、Map 和 WeakMap

(一)Map

键值对,键可以任意,“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应

const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

遍历:

const map = new Map([
  ['F', 'no'],
  ['T',  'yes'],
]);

for (let key of map.keys()) {
  console.log(key);
}
// "F"
// "T"

for (let value of map.values()) {
  console.log(value);
}
// "no"
// "yes"

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

forEach

map.forEach(function(value, key, map) {
  console.log("Key: %s, Value: %s", key, value);
})

(二)WeakMap

WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名,WeakMap的键名所指向的对象,不计入垃圾回收机制。

没有遍历操作(keys,values,entries),没有size属性,不支持clear

常见用途

DOM节点作为键名

let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();

myWeakmap.set(myElement, {timesClicked: 0});

myElement.addEventListener('click', function() {
  let logoData = myWeakmap.get(myElement);
  logoData.timesClicked++;
}, false);

myElement是一个 DOM 节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是myElement。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

九、Promise

异步编程的解决方案

(一)语法

et a = 2;

let promise = new Promise(function(resolve,reject){

if( a == 1){

resolve('success');

}else{

reject('error');

}

})

promise.then(res=> {

console.log(res);

},err => {

console.log(err);

})

模拟用户登录获取用户信息

let status = 1;

let userLogin = ( resolve, reject) => {

setTimeout(() => {

if(status == 1){

resolve({data: 'aa', msg:'success', token: 'aaaaaaa'});

}else{

reject('fail');

}

},2000)

}

let getUserInfo = ( resolve, reject) => {

setTimeout(() => {

if(status == 1){

resolve({data: 'bb', msg:'success', token: 'bbbbbb'});

}else{

reject('fail');

}

},3000)

}

new Promise(userLogin).then(res => {

console.log('success');

// console.log(res);

return new Promise(getUserInfo);

}).then(res => {

console.log('get info');

console.log(res)

})

 

(二)catch

一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch方法,而不使用then方法的第二个参数。

一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。

(三)Promise.all

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

十、Generator函数

解决异步编程,遍历器(Iterator)对象生成函数。

(一)特征

一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

function* gen(){
                yield 'yellow';
                yield 'green';
                return 'color';
            }
            let g = gen();
            console.log(g.next());//{value: "yellow", done: false}
            console.log(g.next());//{value: "green", done: false}
            console.log(g.next());//{value: "color", done: true}
            console.log(g.next());//{value: undefined, done: true}

for ... of

for(let val of g){
                console.log(val);//yellow green
            }

next()throw()return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。

(二)yield* 表达式

任何数据结构只要有 Iterator 接口,就可以被yield*遍历。

​
let read = (function* () {
  yield 'hello';
  yield* 'hello';
})();

read.next().value // "hello"
read.next().value // "h"

​
function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"
function* inner() {
  yield 'hello!';
}

function* outer1() {
  yield 'open';
  yield inner();
  yield 'close';
}

var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"

function* outer2() {
  yield 'open'
  yield* inner()
  yield 'close'
}

var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
上面例子中,outer2使用了yield*,outer1没使用。结果就是,outer1返回一个遍历器对象,outer2返回该遍历器对象的内部值。

在有return语句时,则需要用var value = yield* iterator的形式获取return语句的值

(三)作为对象属性的Generator函数

如果一个对象的属性是 Generator 函数,可以简写成下面的形式。

let obj = {
  * myGeneratorMethod() {
    ···
  }
};

它的完整形式如下,与上面的写法是等价的。

let obj = {
  myGeneratorMethod: function* () {
    // ···
  }
};

(四)应用

Generator 可以暂停函数执行,返回任意表达式的值。这种特点使得 Generator 有多种应用场景。

异步编程

“协程”: 多个线程相互合作,完成异步任务

  • 第一步,协程A开始执行。
  • 第二步,协程A执行到一半,进入暂停,执行权转移到协程B
  • 第三步,(一段时间后)协程B交还执行权。
  • 第四步,协程A恢复执行。
function* asyncJob() {
  // ...其他代码
  var f = yield readFile(fileA);
  // ...其他代码
}

上面代码的函数asyncJob是一个协程,它的奥妙就在其中的yield命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield命令是异步两个阶段的分界线。

协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。

步操作需要暂停的地方,都用yield语句注明

十一、Class

(一)定义

通过class关键字,定义类。

  • 定义“类”方法的时候,前面不需要加上function这个关键字。直接把函数定义放进去就可以, 方法间不需要逗号分隔。
class Point{
            constructor(x,y) {
                this.x = x;
                this.y = y;
            }

            toString(){
                return `(${this.x},${this.y})`;
            }
        }
        var point = new Point(1,2);
        
        console.log(point.toString());//(1,2)
        console.log(typeof Point);//function
        console.log(typeof point)//object
        console.log(Point == Point.prototype.constructor);//true

类的数据类型就是函数,类本身就指向构造函数

 

  • 在类的实例上面调用方法,其实就是调用原型上的方法。
 console.log(point.toString == Point.prototype.toString);//true

 

  • 由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign方法可以很方便地一次向类添加多个方法。
Object.assign(Bar.prototype, {
            toString(){
                console.log("toString")
            },
            toValue(){}
        })
b.toString();//toString
  • 类的内部所有定义的方法,都是不可枚举的(non-enumerable)

      即可以通过for..in 遍历,不可以通过object.keys()遍历

     通过Object.getOwnPropertyNames()获取圆形属性的方法名

class Point {
  constructor(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

什么是枚举属性呢?

  1. 枚举属性是可以通过直接赋值,和属性初始化的属性。标识符是true。
  2. 可以通过Object.defineProperty等定义的属性。标识符是false。
  3. 可枚举属性可以通过for ... in循环遍历(除非该属性名是一个Symbol)。
  4. 详细解释见对象的属性属性

(二)constructor方法

类的默认方法,通过new声称对象,如果没有显式定义,一个空的constructor方法会被默认添加。

类必须通过new调用

(三)类的实例

实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。

class Point {
            constructor(x, y){
                this.x = x; 
                this.y = y;

            }

            toString() {

            }
        }
        var p1 = new Point(1,2);
        var p2 = new Point(2,3);
        console.log(p1._proto_ == p2._proto_); // true
        let proto = Object.getPrototypeOf(p1);
        console.log(proto.constructor.name)// Point
        proto.printName = function() {
            return 'oops'
        };

        console.log(p1.printName());// oops
        console.log(p2.printName());// oops

        var p3 = new Point(3,4);
        console.log(p3.printName())//oops

上面代码在p1的原型上添加了一个printName方法,由于p1的原型就是p2的原型,因此p2也可以调用这个方法。

不推荐使用实例的_proto_ 属性改写原型。会改变类的原始定义。

(四)取值函数(getter) 存值函数(setter)

class MyClass {
  constructor() {
    // ...
  }
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: '+value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'

(五)属性表达式

let methodName = 'getArea';

        class Square {
            [methodName]() {

            }
        }

        console.log(Object.getOwnPropertyNames(Square.prototype));//["constructor", "getArea"]

 

Square类的方法名时getArea

(六)Class表达式

内部可以用类的名字调用,但是在外部,只能使用类实例化对象调用。 

const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined

可以写出立即执行函数 ,person 是一个立即执行的类的实例

let person = new class{
            constructor(name) {
                this.name = name;
                console.log(this.name);//张三
                console.log(name);//张三
            }

            sayName() {
                console.log(this.name);//张三
            }

        }("张三")

        console.log( person.sayName());//undefined

(七)注意点

1、类和模块内部默认就是严格模式,不需要使用use strict.

现代模式use strict

2、不存在变量提升,与继承有关。

{
      let Foo = class{};
      class Bar extends Foo {
                
      }

}

3、ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。

console.log(Foo.name);//Foo
console.log(Bar.name);//Bar

4、Generator方法

方法前加*号

 class Foo {
            constructor(...args) {
                this.args = args;
            }

            * [Symbol.iterator]() {
                for (const arg of this.args) {
                    yield arg;
                }
            }
        }

        for (const x of new Foo('hello', 'world')) {
            console.log(x);
        }

5、this指向

如果将方法单独提出来,方法中的this指向会指向该方法运行时所在的环境,实际指向的是undefined。

 class Logger {
            constructor() {
               this.getThis = () => this; //true
               this.printName = this.printName.bind(this);//Hello three

            }
            printName(name = 'three') {
                this.print(`Hello ${ name }`);
            }

            print(text) {
                console.log(text);
            }
        }

        const logger = new Logger();
        const { printName} = logger;
        console.log(logger.getThis() === logger);//true
        printName();//Uncaught TypeError: Cannot read property 'print' of undefined

解决办法: 

在构造方法中绑定this,会找到print方法

使用箭头函数。this指向定义是坐在的对象。this会总是指向实例对象。

(八)静态方法

  • 类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo {
            static classMethod() {
                return 'hello';
            }
        }
        console.log(Foo.classMethod());//hello
        var foo = new Foo();
        console.log(foo.classMethod())//foo.classMethod is not a function
  • 静态方法中的this指的是类,而不是实例
class Foo {
            static classMethod() {
                this.baz();
            }
            static baz() {
                console.log('static baz');
            }

            baz() {
                console.log('baz');
            }
        }
        Foo.classMethod();//static baz

等同于调用Foo.baz();

  • 父类的静态方法,可以被子类继承
class Bar extends Foo {}
Bar.classMethod();//static baz

(九)静态属性

Class本身的属性。Class.propName。

class Foo {}
Foo.prop = "haha";
console.log(Foo.prop);//haha

在类Foo上定义了静态属性prop。

新提案,累的静态属性写在实力属性的前面,加上static关键字。

class Foo {
static prop = "haha";
}
console.log(Foo.prop);//haha

(十)实例属性

实例属性指的是在构造函数方法中定义的属性,属性和方法都是不一样的引用地址

写法:

  • 构造函数中
class IncreasingCounter {
  constructor() {
    this._count = 0;
  }
}
  • 定义在类的最顶层(建议)

foo类有两个实例属性,一目了然。

class foo {
  bar = 'hello';
  baz = 'world';

  constructor() {
    // ...
  }
}

(十一)私有方法和私有属性

只能在类的内部访问的方法和属性,外部不能访问。有利于代码的封装。

但是ES6不提供,只能通过变通方法实现。

提案:为class加私有属性,在属性名前,加#

        class Point {
            #x;

            constructor(x = 0) {
                this.#x = +x;
            }
            get x() {
                return this.#x;
            }
            set x(value) {
                this.#x = +value;
            }
        }

        let point = new Point();
        console.log( point.x = 4 );

也可用作私有属性,私有方法。

私有属性和私有方法前,也可以加上static关键字,表示静态的私有属性或私有方法。

十二、Class继承extends

(一)简介

子类必须在constructor方法中,在this之前调用super方法,否则新建实例会报错。

        class Point {
            constructor(x,y) {
                this.x = x;
                this.y = y;

            }

            toString() {
                return this.x + this.y;
            }

        }

        class ColorPoint extends Point {
            constructor(x, y, color) {
                 super(x, y);  
                this.color = color;
            }
            toString() {
                return this.color + ' ' + super.toString();
            }
        }

        let colorPoint = new ColorPoint('red','yellow','blue');
        
       console.log( colorPoint.toString() );// blue redyellow

将子类构造方法constructorthis之前的super去掉,会报如下错误:

如果子类没有定义constructor方法,这个方法会被默认添加,不管有没有显示定义,任何一个子类都有constructor方法。

class ColorPoint extends Point {
}

// 等同于
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}

colorPoint 是ColorPoint 和 Point两个类的实例

        console.log(colorPoint instanceof ColorPoint);//true
        console.log(colorPoint instanceof Point);//true

父类的静态方法也会被子类继承

class A {
  static hello() {
    console.log('hello world');
  }
}

class B extends A {
}

B.hello()  // hello world

(二)Object.getPrototypeOf() 从子类上获取父类

Object.getPrototypeOf(ColorPoint) === Point // true
 

(三)super关键字

既可以当作函数使用,也可以当作对象使用。

1、 当作函数

super作为回调函数,代表父类的构造函数,必须写在子类的constructor方法中,并且写在this之前。

super()相当于A.prototype.constructor.call(this)

class A {
  constructor() {
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A() // A
new B() // B

new.target()指向当前正在执行的函数,super()执行时,指向的是子类B的构造函数,也就是说super()内部的this指向的是子类B

2、作为对象

普通方法中,指向父类的原型对象;在静态方法中,指向父类。

    <script>
        class A {
            p() {
                return 2;
            }
        }
        class B extends A {
            constructor() {
                super();
                console.log(super.p() == A.prototype.p())//true
                console.log(super.p());//2
            }
        }

        let b = new B();

 

子类Bsuper.b()就是讲super当做一个对象。指向A.prototype,所以super.p() 相当于A.prototype.p().

        class Parent {
            static myMethod(msg){
                console.log('static', msg);

            }
            myMethod(msg) {
                console.log('instance', msg);
            }
        }

        class Child extends Parent {
            static myMethod(msg){
                super.myMethod(msg);
            }
            myMethod(msg){
                super.myMethod(msg);
            }
        }
        Child.myMethod(1);// static 1
        var child = new Child();
        child.myMethod(2); //instance 2

上面代码中,super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。

定义在父类实例上的方法和属性是无法通过super调用的。

class A {
  constructor() {
    this.p = 2;
  }
}

class B extends A {
  get m() {
    return super.p;
  }
}

let b = new B();
b.m // undefined

上面代码中,p是父类A实例的属性,super.p就引用不到它。

如果属性定义在父类的原型对象上,super就可以取到.A.prototype.x = 2;super.x  //2

在子类普通方法中通过super调用父类方法时,方法内部的this指向当前子类实例。

class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;  //相当于 this.x
    console.log(super.x); // undefined  A.prototype.x
    console.log(this.x); // 3
  }
}

let b = new B();

(四)类的prototype属性和 _proto_属性

class A {}
class B extends A {}
A.__proto__
ƒ () { [native code] }

B.__proto__
class A {}

B.__proto__ === A
true

B.prototype
A {constructor: ƒ}constructor: class Barguments: (...)caller: (...)length: 0prototype: A {constructor: ƒ}constructor: class B__proto__: Objectname: "B"__proto__: class A[[FunctionLocation]]: es6.html:18[[Scopes]]: Scopes[2]__proto__: Object

A.prototype
{constructor: ƒ}constructor: class A__proto__: Object

B.prototype.__proto__
{constructor: ƒ}constructor: class Aarguments: (...)caller: (...)length: 0prototype: {constructor: ƒ}name: "A"__proto__: ƒ ()[[FunctionLocation]]: es6.html:17[[Scopes]]: Scopes[2]__proto__: Object

A.prototype.__proto__
constructor: ƒ Object()
arguments: (...)
caller: (...)
length: 1
name: "Object"


B.prototype.__proto__ == A.prototype
true

Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

  1. 子类的__proto__属性,表示构造函数的继承,总是指向父类。B.__proto__ === A
  2. 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。 B.prototype.__proto__ == A.prototype

可以理解为:

作为一个对象,子类B的原型(_proto_属性)是父类A;

作为一个构造函数,子类B的源性对象(prototype属性)是父类A的原型对象(prototype属性)的实例。

子类继承Object:

A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true

不存在任何继承

A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true

类的原型的原型,是父类的原型

var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');

p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true

(五)原生构造函数的继承

原生构造函数是指语言内置的构造函数,通常用来生成数据结构。

Boolean() 、Number() 、 String() 、Aqqay()、 Date()、 Function()、 RegExp()、 Error()、Object()
        class MyArray extends Array {
            constructor(...args){
                super(...args);
            }
        }
        var arr = new MyArray();
        arr[0] = 12;
        console.log(arr.length);
        console.log(arr[0])

注意,继承Object的子类,有一个行为差异

class NewObj extends Object{
  constructor(){
    super(...arguments);
  }
}
var o = new NewObj({attr: true});
o.attr === true  // false

上面代码中,NewObj继承了Object,但是无法通过super方法向父类Object传参。这是因为 ES6 改变了Object构造函数的行为,一旦发现Object方法不是通过new Object()这种形式调用,ES6 规定Object构造函数会忽略参数。

(六)Mixin模式的实现

Mixin是指多个对象合成一个新的对象,新对象具有各个组成成员的接口。

JavaScript 中的 Mixin 模式

十三、Module模块化

(一)import 和 default

  • ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
  • 编译时加载,自动采用严格模式。
import { stat, exists, readFile } from 'fs';
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export { firstName, lastName, year };
  • 主要由export import 命令构成。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
  • export命令使用 as 关键字重命名。
function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};
  • 注意: export必须与模块内部变量一一对应。
// 报错
export 1;

// 报错
var m = 1;
export m;

正确写法:

// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};
  • export import 如果处在块级作用域中,就会报错,没法静态化。
  • import有提升效果,会提升到整个模块的头部,首先执行。
  • 整体加载,用 * 指定一个对象,所有输出值都加载在这个对象上面。
// circle.js

export function area(radius) {
  return Math.PI * radius * radius;
}

export function circumference(radius) {
  return 2 * Math.PI * radius;
}
import * as circle from './circle';

console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));

(二)export default 命令

  • export default 命令

为模块指定默认输出。

// export-default.js
export default function () {
  console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
  • 不需要知道原模块的函数名。
  • 注意,export default命令在一个模块中只能使用一次,所以import后面不使用大括号。
  • 正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。
// 正确
export var a = 1;

// 正确
var a = 1;
export default a;

// 错误
export default var a = 1;
  • 同一个import命令同时输入默认方法和其他接口
import _, { each, forEach } from 'lodash';
  • 对应上面代码的export语句如下。
export default function (obj) {
  // ···
}

export function each(obj, iterator, context) {
  // ···
}

export { each as forEach };
  • 输出类
// MyClass.js
export default class { ... }

// main.js
import MyClass from 'MyClass';
let o = new MyClass();

(三)export 和 default 的复合写法

  • 在一个模块中,先后输入输出,import export语句可以结合。
import { foo, bar } from 'my_module';
export { foo, bar };

结合成:

export { foo, bar } from 'my_module';
  • 默认接口的写法如下。
export { default } from 'foo';
  • 具名接口改为默认接口的写法如下。
export { es6 as default } from './someModule';

// 等同于
import { es6 } from './someModule';
export default es6;
  • 同样地,默认接口也可以改名为具名接口。
export { default as es6 } from './someModule';

(四)模块的继承

//circle.js
export function area(radius) {
    return Math.PI * radius * radius;
}

export function circumference(radius) {
    return 2 * Math.PI * radius;
}

circleplus.js 继承 circle.js 并将circle.js 中的area 方法改名为circleArea

//circleplus.js
export { area as circleArea } from 'circle'

export var e = 2.718492738;

export default function(x) {
    return Math.exp(x);
}

其中继承的是实现语句,只是对其方法进行了改名,export * 会忽略circledefault方法

export * from 'circle'
//main.js
import * as math from 'circleplus.js';
import exp from 'circleplus';
console.log( exp(math.e) );

(五)跨模常量

  • 由于const命令声明的常量只在当前代码快有效,想要设置跨模块(跨多个文件)的常量可以写成下面:
/ constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;

// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3

// test2.js 模块
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3
  • 如果使用的常量非常多,可以建一个专门的constants目录,将各种常量写在不同的文件里面,保存在该目录下。

//db.js
export const db = {
    url: 'http://my.couchdbserver.local:5984',
    admin_username: 'admin',
    admin_password: 'admin password'
};
// user.js
export const user = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];

index.js中将输出的常量及合并。

// index.js
export { db } from './db';
export { user } from './user';

script.js中使用,直接加载index.js

//script.js
import {db, user } from './index';

(六)import()函数

import命令是在编译时执行,被javascript静态分析,只能在模块的顶层,不能再代码之中。

引入import()函数,支持动态加载模块。

import(specifier)

import函数的参数specifier,指定所要加载的模块的位置。import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载。

适用场合

1、按需加载

import()可以在需要的时候,再加载某个模块。

button.addEventListener('click', event => {
  import('./dialogBox.js')
  .then(dialogBox => {
    dialogBox.open();
  })
  .catch(error => {
    /* Error handling */
  })
});

上面代码中,import()方法放在click事件的监听函数之中,只有用户点击了按钮,才会加载这个模块。

2、条件加载

import()可以放在if代码块,根据不同的情况,加载不同的模块。

if (condition) {
  import('moduleA').then(...);
} else {
  import('moduleB').then(...);
}

3、动态的模块路径

import()允许模块路径动态生成。

import(f())
.then(...);

上面代码中,根据函数f的返回结果,加载不同的模块。

注意点

1、import() 加载成功以后,输出的接口可以用解构获得。

import('./myModule.js')
.then(({export1, export2}) => {
  // ...·
});

export1export2都是myModule.js的输出接口,可以解构获得。

2、具名输入

import('./myModule.js')
.then(({default: theDefault}) => {
  console.log(theDefault);
});

3、同时加载多个模块

Promise.all([
  import('./module1.js'),
  import('./module2.js'),
  import('./module3.js'),
])
.then(([module1, module2, module3]) => {
   ···
});

4、import() 也可以用在async 函数之中

async function main() {
  const myModule = await import('./myModule.js');
  const {export1, export2} = await import('./myModule.js');
  const [module1, module2, module3] =
    await Promise.all([
      import('./module1.js'),
      import('./module2.js'),
      import('./module3.js'),
    ]);
}
main();

(七)浏览器加载

1、两种异步加载的方式:defer async

<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

defer async 的区别:

  • defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;
  • async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。

defer是“渲染完再执行”,async是“下载完就执行”。

另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。

2、浏览器加载ES6模块

type="module"属性。

异步加载,不会造成堵塞。等到整个页面渲染完,在执行模块脚本。等同于defer属性。

<script type="module" src="./foo.js"></script>

一旦使用了async属性,<script type="module">就不会按照在页面出现的顺序执行,而是只要该模块加载完成,就执行该模块。

(八)ES6模块 与 commenJs

commenJs w3c

AMD , CMD, CommonJS,ES6 Module,UMD之间的区别

ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

// lib.js
export let counter = 3;
export function incCounter() {
  counter++;
}

// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4

由于 ES6 输入的模块变量,只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错。

// lib.js
export let obj = {};

// main.js
import { obj } from './lib';

obj.prop = 123; // OK
obj = {}; // TypeError

(九)node.js加载

Node.js 要求 ES6 模块采用 .mjs 后缀文件名。也就是说,只要脚本文件里面使用import或者export命令,那么就必须采用 .mjs 后缀名。Node.js 遇到 .mjs 文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定"use strict"

.mjs文件总是以 ES6 模块加载,.cjs文件总是以 CommonJS 模块加载,.js文件的加载取决于package.json里面type字段的设置。

{
   "type": "module"
}

设置了之后,就被解释为ES6模块

package.json文件有两个字段可以指定模块的入口文件:mainexports。比较简单的模块,可以只使用main字段,指定模块加载的入口文件。

// ./node_modules/es-module-package/package.json
{
  "type": "module",
  "main": "./src/index.js"
}

上面代码指定项目的入口脚本为./src/index.js,它的格式为 ES6 模块。如果没有type字段,index.js就会被解释为 CommonJS 模块。

然后,import命令就可以加载这个模块。

// ./my-app.mjs

import { something } from 'es-module-package';
// 实际加载的是 ./node_modules/es-module-package/src/index.js

上面代码中,运行该脚本以后,Node.js 就会到./node_modules目录下面,寻找es-module-package模块,然后根据该模块package.jsonmain字段去执行入口文件。

这时,如果用 CommonJS 模块的require()命令去加载es-module-package模块会报错,因为 CommonJS 模块不能处理export命令。

(十)内部变量

  • this关键字。ES6 模块之中,顶层的this指向undefinedCommonJS 模块的顶层this指向当前模块,这是两者的一个重大差异。
  • 其次,以下这些顶层变量在 ES6 模块之中都是不存在的。

  • arguments
  • require
  • module
  • exports
  • __filename
  • __dirname

(十一)循环加载

a依赖bb依赖cc又依赖a这样的情况.

强耦合。

// a.js
var b = require('b');

// b.js
var a = require('a');

CommonJS 输入的是被输出值的拷贝,不是引用。

CommonJS 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。

var a = require('a'); // 安全的写法
var foo = require('a').foo; // 危险的写法

exports.good = function (arg) {
  return a.foo('good', arg); // 使用的是 a.foo 的最新值
};

exports.bad = function (arg) {
  return foo('bad', arg); // 使用的是一个部分加载时的值
};

上面代码中,如果发生循环加载,require('a').foo的值很可能后面会被改写,改用require('a')会更保险一点。

十四、async、await

(一)async

它就是 Generator 函数的语法糖。

异步。

常见于koa2.koa2推荐阅读

Nodejs API文档

async function fn() {// 表示异步,这个函数里面有异步操作
    let result = await .....  //表示后面有结果需要等待
}

注意: await 只能放在 async 函数中

(二)读取文件案例

Promise

const fs = require('fs');

const readFile = function(file) {
// 封装Promise
    return new Promise(( resolve, reject ) => {
        fs.readFile(file, (err,data) => {
            if(err) reject(err);
            resolve(data.toString());
        });
    });

}

readFile('data/a.txt').then(res => {
    console.log(res);
    return readFile('data/b.txt');
}).then(res => {
    console.log(res);
    return readFile('data/c.txt');
}).then(res => {
    console.log(res);
})

Generator

function * gen() {
    yield readFile('data/a.txt');
    yield readFile('data/b.txt');
    yield readFile('data/c.txt');
}

let g1 = gen();

g1.next().value.then(res => {
    console.log(res);
    return g1.next().value;
}).then(res => {
    console.log(res);
    return g1.next().value;
}).then(res => {
    
    console.log(res);
})

async

通常与await配合使用。

异步。

async function f() {
    let f1 = await readFile('data/a.txt');
    console.log(f1);
    let f2 = await readFile('data/b.txt');
    console.log(f2);
    let f3 = await readFile('data/c.txt');
    console.log(f3);
}
f();

运行结果都是:node promise.js

aaa bbb ccc

(三)特点

1、await 只能放到async 函数中。

2、相比generator语义化更强。

3、await后面可以使Promise对象,也可以是数字、字符串、布尔

4、async函数返回一个promise对象

        async function fn() {
            return 'hello'
        }
        console.log(fn())//Promise
       fn().then(res => {
           console.log(res);//hello
       })

失败:

        async function fn() {
            throw new Error('error');
        }
        fn().then(res => {
            console.log(res);
        }).catch(err => {
            console.log(err);
            // Error: error
            // at fn(es6.html: 19)
            // at es6.html: 21
        })

5、只要await语句后面Promise状态变成reject,那么整个async函数会中断执行。

        async function fn() {
            let a = await Promise.resolve('success');
            console.log(a);//success
            await Promise.reject('error');
        }
        fn().catch(err => {
            console.log(err);//error
        })

当把reject放在resolve上面

        async function fn() {
            await Promise.reject('error');
            let a = await Promise.resolve('success');
            console.log(a);
        }
        fn().catch(err => {
            console.log(err);//error
        })

没有执行到resolve(),只输出error

(四)解决async函数中跑出错误,中断影响之后代码

1、try{}catch(e){}

        async function fn() {
            try {
                await Promise.reject('error');
            } catch (error) {}
            
            let a = await Promise.resolve('success');
            console.log(a);//success
        }
        fn().catch(err => {
            console.log(err);
        })

只输出 success

2、Promise本身catch

        async function fn() {
            await Promise.reject('error').catch(err => {
                console.log(err)//error
            });
            let a = await Promise.resolve('success');
            console.log(a);//success
        }
        fn();

个人建议:只要有await,都放在 try catch中。

如果在项目中执行没有联系的await Promise 时,可以用Promise.all([])

async function f() {
    let [a, b, c] = await Promise.all([
        readFile('data/a.txt'),
        readFile('data/b.txt'),
        readFile('data/c.txt')
    ]);
    console.log(a);//aaa
    console.log(b);//bbb
    console.log(c);//ccc
}

f();

十五、Proxy

代理器,属于一种“元编程”,即对编程语言进行编程。

扩展增强对象的一些功能。

作用:vue中拦截

预警,上报,扩展功能,统计,增强对象等。

proxy是设计模式中的代理模式。javascript设计模式——代理模式

语法

new Proxy(target, handler);  返回一个对象。

target:被代理的对象。是一个对象

handler: 对代理的对象进行什么操作。是一个json中放方法。

支持拦截操作方法

这些方法都是写在handler中的。

  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.fooproxy['foo']
        const obj = {
            name: 'gsy'
        }
        const myObj = new Proxy(obj, {
            get(target, property, receiver) {
                console.log(target, property);
                // Object        "name"
                // name: "gsy
            }
        })
        myObj.name;
        myObj.aaa;// property = "aaa"

myObj点什么 property就是什么,如果没有或者不知道目标对象是什么,Proxy第一个参数可以传{}

案例一:拦截读取操作判断对象中是否有该属性

        const obj = {
            name: 'gsy'
        }
        const myObj = new Proxy(obj, {
            get(target, property, receiver) {
                if(property in target){
                    return target[property];
                }else {
                    console.warn(`${ property } 不在 ${ target } 上`);
                    throw new ReferenceError(`${ property } 不在 ${ target } 上`);
                }
            }
        })
        // console.log( myObj.name );
        myObj.aaa;

案例二:实现生成DOM节点的通用函数dom

 const dom = new Proxy({}, {
            get(target, property) {
                // console.log(property);//dom.a is not a function
                return function(attr = {},...children) {
                    // console.log(property);//a li li ul div
                    const el = document.createElement(property);
                    // 设置属性
                    for (let prop of Object.keys(attr)) {
                        el.setAttribute(prop,attr[prop]);
                    }
                    // 创建节点
                    for (let child of children) {
                        if(typeof child == 'string'){
                            child = document.createTextNode(child);
                        }
                        el.appendChild(child)
                    }
                    return el;

                }

            }
        })
        const el = dom.div(
            {id:'div1',class:'div1'},
            'i am div',
            dom.a({href:'http://www.baidu.com'},
                  'baidu'),
            dom.ul(
                dom.li({},'111'),
                dom.li({},'222')
            )
            )
          document.body.appendChild(el);

  • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。

       依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }

    // 对于满足条件的 age 属性以及其他属性,直接保存
    obj[prop] = value;
  }
};

let person = new Proxy({}, validator);

person.age = 100;

person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错

结合getset方法,就可以做到防止这些内部属性被外部读写。

        const handler = {
            get(target, property){
                invariant(property, 'get')
                return target[property];
            },
            set(target, property, value){
                invariant(property,'set');
                target[property] = value;
                return true;
            }
        }

        function invariant(property, action){
            if(property[0] == '_'){
                throw new Error(`Invalid attempt to ${action} private "${property}" property`);
            }
        }
        let proxy = new Proxy({},handler);
        // proxy._aaa;//Invalid attempt to get private "_aaa" property
        proxy._aaa = 'aaa';//nvalid attempt to set private "_aaa" property

 

  • has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。

has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。

has方法可以接受两个参数,分别是目标对象、需查询的属性名。

let stu1 = {name: '张三', score: 59};
let stu2 = {name: '李四', score: 99};

let handler = {
  has(target, prop) {
    if (prop === 'score' && target[prop] < 60) {
      console.log(`${target.name} 不及格`);
      return false;
    }
    return prop in target;
  }
}

let oproxy1 = new Proxy(stu1, handler);
let oproxy2 = new Proxy(stu2, handler);

'score' in oproxy1
// 张三 不及格
// false

'score' in oproxy2
// true

for (let a in oproxy1) {
  console.log(oproxy1[a]);
}
// 张三
// 59

for (let b in oproxy2) {
  console.log(oproxy2[b]);
}
// 李四
// 99
  • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。

        let json = {
            a:1,
            b:2
        }
        let newJson = new Proxy(json, {
            deleteProperty(target, property){
                console.log(`您要删除${ property } 吗`);
                delete target[property];
            },
            has(target, property){
                console.log(`判断是否有${ property }`);
                return true;
            }
        
        })
        console.log('a' in newJson);
        // 判断是否有a
        // true
        delete newJson.a;
        console.log(newJson)
        // 您要删除a 吗
        // Proxy {b: 2}
  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)。分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

construct方法用于拦截new命令,下面是拦截对象的写法。

var handler = {
  construct (target, args, newTarget) {
    return new target(...args);
  }
};

construct方法可以接受两个参数。

  • target:目标对象
  • args:构造函数的参数对象
  • newTarget:创造实例对象时,new命令作用的构造函数(下面例子的p

construct方法返回的必须是一个对象,否则会报错。

this问题

目标对象内部的this关键字会指向 Proxy 代理。

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m()  // true

上面代码中,一旦proxy代理target.m,后者内部的this就是指向proxy,而不是target

十六、Reflect

reflect 反射,增强方法的一些功能。

        function sum(a, b) {
            return a + b;
        }
        let newSum = new Proxy(sum, {
            apply(target, context, args) {
                console.log(target, context, args);
                console.log(...arguments);
                // ƒ sum(a, b) {
                //     return a + b;
                // }
                // undefined
                //(2)[2, 3]
                return '拦截函数sum()';
            }
        })

        console.log(newSum(2, 3)) //拦截函数sum()

在使用apply()拦截函数sum()时,sum()函数不能执行。

这是需要reflect反射,

其中apply()中的参数与 ...arguments值是相等的,

  return Reflect.apply(...arguments);//5

Reflect.apply(目标函数名,this指向,参数数组)

效果相当于call()  aplly()

        function show(...args){
            console.log(this);
            console.log(args);
        }

        show.call('aaa',1,2,3);
        show.apply('aaa',[1,2,3])
        Reflect.apply(show,'aaa',[1,2,3]);
        // String {"aaa"}
        // (3) [1, 2, 3]

 

下面的这些都是Object.xxx语言内部的方法,将来可能都放在Reflect身上,直接通过Reflect使用。

比如:

‘assign’ in Object -----> Reflect.has(Object, 'assign')

Reflect对象一共有 13 个静态方法。

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)

Reflect.construct方法等同于new target(...args),这提供了一种不使用new,来调用构造函数的方法

  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)

Reflect.defineProperty方法基本等同于Object.defineProperty,用来为对象定义属性。未来,后者会被逐渐废除,请从现在开始就使用Reflect.defineProperty代替它。

  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)

Reflect.ownKeys方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNamesObject.getOwnPropertySymbols之和。

var myObject = {
  foo: 1,
  bar: 2,
  [Symbol.for('baz')]: 3,
  [Symbol.for('bing')]: 4,
};

// 旧写法
Object.getOwnPropertyNames(myObject)
// ['foo', 'bar']

Object.getOwnPropertySymbols(myObject)
//[Symbol(baz), Symbol(bing)]

// 新写法
Reflect.ownKeys(myObject)
// ['foo', 'bar', Symbol(baz), Symbol(bing)]

如果Reflect.ownKeys()方法的第一个参数不是对象,会报错。

  • Reflect.isExtensible(target)

Reflect.isExtensible方法对应Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展。

const myObject = {};

// 旧写法
Object.isExtensible(myObject) // true

// 新写法
Reflect.isExtensible(myObject) // true
  • Reflect.preventExtensions(target)

Reflect.preventExtensions对应Object.preventExtensions方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功。

var myObject = {};

// 旧写法
Object.preventExtensions(myObject) // Object {}

// 新写法
Reflect.preventExtensions(myObject) // true
  • Reflect.getOwnPropertyDescriptor(target, name)

Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象,将来会替代掉后者。

  • Reflect.getPrototypeOf(target)

Reflect.getPrototypeOf方法用于读取对象的__proto__属性,对应Object.getPrototypeOf(obj)

  • Reflect.setPrototypeOf(target, prototype)

Reflect.setPrototypeOf方法用于设置目标对象的原型(prototype),对应Object.setPrototypeOf(obj, newProto)方法。它返回一个布尔值,表示是否设置成功。

十七、编程风格

(一)变量 块级作用域

  • 建议不再使用var命令,而是使用let命令取代
  • letconst之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。
// bad
var a = 1, b = 2, c = 3;

// good
const a = 1;
const b = 2;
const c = 3;

// best
const [a, b, c] = [1, 2, 3];

const声明常量还有两个好处,一是阅读代码的人立刻会意识到不应该修改这个值,二是防止了无意间修改变量值所导致的错误。

(二)字符串

静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。

// bad
const a = "foobar";
const b = 'foo' + a + 'bar';

// acceptable
const c = `foobar`;

// good
const a = 'foobar'; // 静态字符串
const b = `foo${a}bar`;

(三)结构赋值

  • 使用数组成员对变量赋值时,优先使用解构赋值。

    const arr = [1, 2, 3, 4];
    
    // bad
    const first = arr[0];
    const second = arr[1];
    
    // good
    const [first, second] = arr; // 1 2
  • 函数的参数如果是对象的成员,优先使用解构赋值。

    // bad
    function getFullName(user) {
      const firstName = user.firstName;
      const lastName = user.lastName;
    }
    
    // good
    function getFullName(obj) {
      const { firstName, lastName } = obj;
    }
    
    // best
    function getFullName({ firstName, lastName }) {
    }
  • 如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。

    // bad
    function processInput(input) {
      return [left, right, top, bottom];
    }
    
    // good
    function processInput(input) {
      return { left, right, top, bottom };
    }
    
    const { left, right } = processInput(input);

(四)对象

  • 单行定义的对象,最后成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾一个。

    // bad
    const a = { k1: v1, k2: v2, };
    const b = {
      k1: v1,
      k2: v2
    };
    
    // good
    const a = { k1: v1, k2: v2 };
    const b = {
      k1: v1,
      k2: v2,
    };
  • 对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。

    // bad
    const a = {};
    a.x = 3;
    
    // if reshape unavoidable
    const a = {};
    Object.assign(a, { x: 3 });
    
    // good
    const a = { x: null };
    a.x = 3;
  • 如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义。

    // bad
    const obj = {
      id: 5,
      name: 'San Francisco',
    };
    obj[getKey('enabled')] = true;
    
    // good
    const obj = {
      id: 5,
      name: 'San Francisco',
      [getKey('enabled')]: true,
    };

最好采用属性表达式,在新建obj的时候,将该属性与其他属性定义在一起。这样一来,所有属性就在一个地方定义了。

  • 对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写。
let ref = 'ref';
const atom = {
  ref,//简洁写法

  value: 1,

  addValue(value) {
    return atom.value + value;
  },
};

(五)数组

  • 使用扩展运算符  ... 拷贝数组
        const foo = document.querySelectorAll('.foo');
        console.log(foo);//对象数组 NodeList(3) [div.foo, div.foo, div.foo]
        const nodes = Array.from(foo);
        console.log(nodes)//数组 (3) [div.foo, div.foo, div.foo]
        console.log([...foo]);//结果同Array.from

(六)函数

  • 使用箭头函数(替代匿名函数,立即执行函数,Function.prototype.bind)

立即执行函数

        (() => {
            console.log('lijizhixing ')
        })()

匿名函数

// bad
[1, 2, 3].map(function (x) {
  return x * x;
});

// good
[1, 2, 3].map((x) => {
  return x * x;
});

// best
        let arr = [1,2,3].map( x => x * x);
        console.log(arr);//[1, 4, 9]

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);

const bound = (...params) => Reflect.apply(method,this,params)
  • 简单的、单行的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法。
  • 所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。

    // bad
    function divide(a, b, option = false ) {
    }
    
    // good
    function divide(a, b, { option = false } = {}) {
    }
  • 在函数体内用 rest(...)运算符替代arguments。

arguments是一个类数组对象,rest运算符是一个真正的对象。

// bad
function concatenateAll() {
  const args = Array.prototype.slice.call(arguments);
  return args.join('');
}

// good
function concatenateAll(...args) {
  return args.join('');
}
  • 使用默认值语法设置函数参数的默认值。

    // bad
    function handleThings(opts) {
      opts = opts || {};
    }
    
    // good
    function handleThings(opts = {}) {
      // ...
    }

(七)Map结构

注意区分 Object 和 Map,只有模拟现实世界的实体对象时,才使用 Object。如果只是需要key: value的数据结构,使用 Map 结构。因为 Map 有内建的遍历机制。

let map = new Map(arr);

for (let key of map.keys()) {
  console.log(key);
}

for (let value of map.values()) {
  console.log(value);
}

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}

(八)Class

  • 总是用 Class,取代需要 prototype 的操作。因为 Class 的写法更简洁,更易于理解。

    // bad
    function Queue(contents = []) {
      this._queue = [...contents];
    }
    Queue.prototype.pop = function() {
      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;
                }
            }
    
            let queue = new Queue('hello');
            console.log(queue.pop());//h
  • 使用extends实现继承,因为这样更简单,不会有破坏instanceof运算的危险。
// bad
const inherits = require('inherits');
function PeekableQueue(contents) {
  Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function() {
  return this._queue[0];
}

// good
class PeekableQueue extends Queue {
  peek() {
    return this._queue[0];
  }
}

(九)模块

  • 使用import取代require
  • 使用export取代module.export
// commonJS的写法
var React = require('react');

var Breadcrumbs = React.createClass({
  render() {
    return <nav />;
  }
});

module.exports = Breadcrumbs;

// ES6的写法
import React from 'react';

class Breadcrumbs extends React.Component {
  render() {
    return <nav />;
  }
};

export default Breadcrumbs;
  • 如果模块只有一个输出值,就使用export default,如果模块有多个输出值,就不使用export defaultexport default与普通的export不要同时使用。
  • 不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。

    // bad
    import * as myObject from './importModule';
    
    // good
    import myObject from './importModule';
  • 如果模块默认输出一个函数,函数名的首字母应该小写。

    function makeStyleGuide() {
    }
    
    export default makeStyleGuide;
  • 如果模块默认输出一个对象,对象名的首字母应该大写。

    const StyleGuide = {
      es6: {
      }
    };
    
    export default StyleGuide;

(十)ESLint

见官网

ESLint官网

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值