重读 ES6 标准入门(第3版)

风流不在谈锋胜,袖手无言味最长。这篇文章主要讲述重读 ES6 标准入门(第3版)相关的知识,希望能为你提供帮助。
前言仅将自己的理解做整理、归类并结合实际遇到的问题做记录,更推荐阅读  ??ECMAScript 6 入门??。
扩展篇数组的扩展

  • 解构赋值
“ES6 中允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)”
作用:可以快速取得数组或对象当中的元素或属性,而无需使用arr[x]或者obj[key]等传统方式进行赋值
let [a,b,c] = [1,2,3];

  • 扩展运算符
“扩展运算符是三个点,它如同rest参数的逆运算,将一个数组转为用逗号分隔的参数序列”
作用:把数组或类数组对象展开成一系列用逗号隔开的值
console.log(...[1,2,3])
//1 2 3
console.log([1,2,3].join())
//1,2,3
console.log(typeof(...[1,2,3]))
//Uncaught SyntaxError: Unexpected token ...
console.log(typeof([1,2,3].join()))
//string
* 为什么typeof(...[1,2,3])报错Uncaught SyntaxError: Unexpected token ...???

常用
let [head,...tail] = [1,2,3,4]; //head:1,tail:[2,3,4]

let colors = ["red","green","blue"];
let [...cloneColors] = colors;
console.log(cloneColors)//["red", "green", "blue"]

1.将数组转为函数的参数,替代 apply 方法
Math.max.apply(null,[14,3,77])//ES5
Math.max(...[14,3,77])//ES6

2.合并数组
var arr1=[a,b]
var arr2=[c,d]
console.log(arr1.concat(arr2))//ES5
console.log([...arr1,...arr2])//ES6

  • Array.from()
“Array.from方法用于将两类对象转成真正的数组”
  • Array.of()
“Array.of方法用于将一组值转换为数组”
常用
console.log(Array.of(3,11,8))//[3, 11, 8]

  • copyWithin()
复制替换数据
Array.prototype.copyWithin(target,start=0,end=this.length)

target:必选,从该位置开始替换 start:替换内容的起点 end:到该位置前停止读取
console.log([1,2,3,4,5].copyWithin(0,3,4))//[4, 2, 3, 4, 5]
console.log([a,b,c].copyWithin(0,1,3))//["b", "c", "c"]

  • etc. find()、fill()、entries()、keys()、values()、includes、数组的空位(非重点关注)
对象的扩展
  • 属性简洁表示、属性名表达式、方法的name属性、Object.is()、Object.assign():合并对象-浅复制、属性可枚举性
  • 遍历对象的属性
1.for...in
2.Object.keys()
var obj =0: a, 1: b, 2: c ;
console.log(Object.keys(obj)); // console: [0, 1, 2]

  • 对象的解构赋值
对象的属性没有次序,变量必须与属性同名,才能取到正确的值
letfoo, bar=foo: "aaa", bar: "bbb" ;
foo // "aaa"
bar // "bbb"

字符串的扩展
  • 字符串遍历 for...of
console.log(foo.split())//["f", "o", "o"]//ES5

for(let codePoint of foo)
console.log(codePoint)//f,"o","o"//ES6

  • 返回字符串给定位置的字符at()
console.log(lallallal.indexOf(a))//1 Number//ES5

console.log(lallallal.charAt(a))//1 String//ES6

  • includes():是否找到参数字符串、startsWith()/endswith():参数字符串是否在源字符串头/尾部...
函数的扩展
  • 函数参数的默认值
function log(x,y=world)
console.log(x,y)

log(moximoxi)//moximoxi world

  • rest参数(...变量名)
function test(...values)
console.log(values)
for(var val of values)
console.log(val)
//for...of可以不仅可以遍历字符串,还可以遍历数组 ES6

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

  • name属性
name属性:function foo() foo.name//"foo"

  • 箭头函数 →→→
函数名=参=> 返回值
  • 尾调用、尾递归
某个函数的最后一步是调用另一个函数
正则的扩展数值的扩展承诺篇Promisefrom MDN:
var promise1 = new Promise(function(resolve, reject)
setTimeout(function()
resolve(foo);
, 300);
);

promise1.then(function(value)
console.log(value);
// expected output: "foo"
);

console.log(promise1);
// expected output: [object Promise]

Generatorfrom  ??CSDN??:
function* helloGenerator()
console.log("this is generator");

var h = helloGenerator();
h.next();

function* helloGenerator()
yield "hello";
yield "generator";
return;

var h = helloGenerator();
console.log(h.next()); // value: hello, done: false
console.log(h.next()); // value: generator, done: false
console.log(h.next()); // value: undefined, done: true

yield实际就是暂缓执行的标示,每执行一次next(),相当于指针移动到下一个yield位置。
总结一下,Generator函数是ES6提供的一种异步编程解决方案。通过yield标识位和next()方法调用,实现函数的分段执行。
async迭代器=> Generator=> async
1.迭代器:不暴露对象的内部表示的情况下,能够遍历整个元素
遍历 ~ Traverse 访问一个集合(广义)的每个元素
迭代 ~ Iterate 反复调用同一个过程最终达成目的(迭代是循环的一种方式),这个过程如果是一个函数,那就是递归,如果是一个循环体,那就是狭义上的迭代。递归和迭代之间的关系、转换、优化等等又是另一个故事了。
function makeIterator (arr)
let nextIndex = 0;
//返回一个迭代器方法
return
next: () =>
if(nextIndex < arr.length)
return value: arr[nextIndex++], done: false
else
return done: true




const it = makeIterator([1,2,3]);
console.log(1:, it.next());
console.log(2:, it.next());
console.log(3:, it.next());
console.log(end:, it.next());

2.Generator函数执行后会返回一个迭代器
3.async函数是Generator的语法糖
async 函数返回一个Promise对象,可以使用 then 方法添加回调函数
实际遇到
(function()
let pro=new Promise((resolve,reject)=>
resolve(true)
)
console.log(pro)//promise对象
)()

(async function()
let pro=await new Promise((resolve,reject)=>
resolve(true)
)
console.log(pro)//true
//(async函数的返回值是一个promise对象; waite命令后面是一个Promise对象,如果不是,会被转成一个立即resolve的Promise对象)
)()

数据结构篇Set、Map
  • Set
Set 是成员值唯一的数据结构,类似于数组 Array
所以,有一种重要且好用的去重方法:
var set1 = Array.from(new Set([1,1,2,2,33,aa,aa,bb
]))
console.log(set1)//[1, 2, 33, "aa", "bb"]

或:

var set1 = [...new Set([1,1,2,2,33,aa,aa,bb
])]
console.log(set1)//[1, 2, 33, "aa", "bb"]

常用
Set 实例的方法分为两大类:操作方法(用于操作数据)、遍历方法(用于遍历成员)
分类
方法
操作方法
Set.prototype.add(value):添加某个值,返回 Set 结构本身


Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。


Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。


Set.prototype.clear():清除所有成员,没有返回值。
遍历方法
Set.prototype.keys():返回键名的遍历器


Set.prototype.values():返回键值的遍历器


Set.prototype.entries():返回键值对的遍历器


Set.prototype.forEach():使用回调函数遍历每个成员
?
  • Map
Map 是可以用非字符串当作键的键值对数据结构,类似于对象 Object
Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。
常用
Map 实例的方法分为两大类:操作方法(用于操作数据)、遍历方法(用于遍历成员)
分类
方法
操作方法
Map.prototype.set(key, value):设置


Map.prototype.get(key):获取


Map.prototype.has(key):判断存在


Map.prototype.delete(key):删除


Map.prototype.clear():清除
遍历方法
Map.prototype.keys():返回键名的遍历器。


Map.prototype.values():返回键值的遍历器。


Map.prototype.entries():返回所有成员的遍历器。


Map.prototype.forEach():遍历 Map 的所有成员。
## Proxy、Reflect
?
  • Proxy
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
Proxy就是对象的拦截器
典例:set()方法:用validator拦截person.age的创建;
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 // The age is not an integer
person.age = 300 // The age seems invalid

  • Reflect
将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。
为了修改某些Object方法的返回结果,让其变得更合理。
例:用defineProperty,不抛出错误,返回false:
// 老写法
try
Object.defineProperty(target, property, attributes);
// success
catch (e)
// failure


// 新写法
if (Reflect.defineProperty(target, property, attributes))
// success
else
// failure

重点食用:使用 Proxy 实现观察者模式?
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。
const person = observable(
name: 张三,
age: 20
);

function print()
console.log(`$person.name, $person.age`)


observe(print);
person.name = 李四;
// 输出
// 李四, 20

上面代码中,数据对象person是观察目标,函数print是观察者。一旦数据对象发生变化,print就会自动执行。
下面,使用 Proxy 写一个观察者模式的最简单实现,即实现observable和observe这两个函数。 思路是observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数。
const queuedObservers = new Set();

const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, set);

function set(target, key, value, receiver)
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;

上面代码中,先定义了一个Set集合,所有观察者函数都放进这个集合。 然后,observable函数返回原始对象的代理,拦截赋值操作。拦截函数set之中,会自动执行所有观察者。
lterator和for...of循环
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
lterator有点难理解了  ??传送门??  结合迭代器、生成器理解
默认调用 Iterator 接口(即Symbol.iterator方法)的场合:
(1)解构赋值
对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
let set = new Set().add(a).add(b).add(c);

let [x,y] = set;
// x=a; y=b

let [first, ...rest] = set;
// first=a; rest=[b,c];

(2)扩展运算符
扩展运算符(...)也会调用默认的 Iterator 接口。
// 例一
var str = hello;
[...str] //[h,e,l,l,o]

// 例二
let arr = [b, c];
[a, ...arr, d]
// [a, b, c, d]

上面代码的扩展运算符内部就调用 Iterator 接口。
实际上,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,转为数组。也就是说,只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。
let arr = [...iterable];

(3)yield*
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
let generator = function* ()
yield 1;
yield* [2,3,4];
yield 5;
;

var iterator = generator();

iterator.next() //value: 1, done: false
iterator.next() //value: 2, done: false
iterator.next() //value: 3, done: false
iterator.next() //value: 4, done: false
iterator.next() //value: 5, done: false
iterator.next() //value: undefined, done: true

(4)其他场合
由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。
for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(比如new Map([[a,1],[b,2]]))
Promise.all()
Promise.race()

模块化篇Class
ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
∴ JS实现继承还是通过原型链的方式!
ES5:
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);

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


toString()
return ( + this.x + ,+ this.y + );


Modlule
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
// circle.js

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


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

// main.js

importarea, circumferencefrom ./circle;

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

编程风格的变化(1) let 取代 var
(2) 在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。
(3) 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
// bad
const a = "foobar";
const b = foo + a + bar;

// acceptable
const c = `foobar`;

// good
const a = foobar;
const b = `foo$abar`;

(4) 善用解构赋值?
(5) 对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用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;

(6) 使用扩展运算符(...)拷贝数组。?
// bad
const len = items.length;
const itemsCopy = [];
let i;

for (i = 0; i < len; i++)
itemsCopy[i] = items[i];


// good
const itemsCopy = [...items];

(7)立即执行函数可以写成箭头函数的形式
(() =>
console.log(Welcome to the Internet.);
)();

【重读 ES6 标准入门(第3版)】(8)善用Map?
(9)Module 语法是 javascript 模块的标准写法,坚持使用这种写法。使用import取代require。
(10)ESLint




我是掘金安东尼: 一名人气前端技术博主(文章 100w+ 阅读量)
终身写作者(INFP 写作人格)
坚持与热爱(简书打卡 1000 日)
我能陪你一起度过漫长技术岁月吗(以梦为马)
觉得不错,给个点赞和关注吧(这是我最大的动力 )b( ̄▽ ̄)d





    推荐阅读