你不知道的ES6-语法的用途

1.函数尾调用、
2.proxy和reflect、
3.symbol、
4.promise、
5.Iterater 和for…of
6.Generator
7.async
8.ArrayBuffer
9.装饰器

1.函数尾调用

指某个函数的最后一步是调用另一个函数。

function f(x){
  return g(x);
}

尾递归
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。

如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

解决递归内存泄露问题:
我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

“尾调用优化”对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6 亦是如此,第一次明确规定,所有 ECMAScript 的实现,都必须部署“尾调用优化”。这就是说,ES6 中只要使用尾递归,就不会发生栈溢出(或者层层递归造成的超时),相对节省内存。

尾调用:调用记录
尾调用不一定出现在函数尾部,只要是最后一步操作即可。

2.Symbol

总所皆知:ES5 的对象属性名都是字符串,这容易造成属性名的冲突。
可以作为对象的唯一key值

现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的原因。

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它属于 JavaScript 语言的数据类型之一,其他数据类型是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、大整数(BigInt)、对象(Object)。

Symbol 值通过Symbol()函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

let s = Symbol();

typeof s

3.proxy和reflect

1.从Reflect对象上可以拿到语言内部的方法
2. 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false
3.Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。

proxy和reflect13种方法思考

4.promise

Promise.any()跟Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。
只要有一个变成fulfilled,Promise.any()返回的 Promise 对象就变成fulfilled。如果所有三个操作都变成rejected,

5.interator和for of

一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for…of循环遍历它的成员。也就是说,for…of循环内部调用的是数据结构的Symbol.iterator方法。

for…of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。
对象不能使用for of因为无interator属性
你不知道的ES6-语法的用途_第1张图片

迭代器的方法entries、keys、values:
entries() 返回一个遍历器对象,用来遍历[键名, 键值]组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用entries方法。
keys() 返回一个遍历器对象,用来遍历所有的键名。
values() 返回一个遍历器对象,用来遍历所有的键值。
你不知道的ES6-语法的用途_第2张图片
重点说明:对象不是迭代器对象
对象的方法object.keys、object.values

使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组。
遍历器对象:数组,map,set,字符串,arguments,domlist ,具有iterator

遍历的缺点:
for…in

循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。

for (var key of Object.keys(obj)) {
  console.log(key + ': ' + obj[key]);//a: 1
}
for (var key in obj) {
  console.log(key + ': ' + obj[key]);//a: 1   b: 2
}

某些情况下,for…in循环会以任意顺序遍历键名。

for…of
不同于forEach方法,它可以与break、continue和return配合使用

6.Generator

调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束

Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。

Generator 函数赋值给Symbol.iterator属性,从而使得myIterable对象具有了 Iterator 接口,可以被…运算符遍历了。.next()返回yield后面的值。
next 方法的参数 § ⇧
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]

next()、throw()、return() 的共同点
分别是将yeild表达式替换为参数、throw语句,return语句

const g = function* (x, y) {
  let result = yield x + y;
  return result;
};

const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}// 相当于将 let result = yield x + y// 替换成 let result = 1;

上面代码中,第二个next(1)方法就相当于将yield表达式替换成一个值1。如果next方法没有参数,就相当于替换成undefined。

7.async

内部原理就是将generator函数和自执行包装装一个函数里。spawn自执行函数返回一个promise

async function fn(args) {
  // ...}
// 等同于
function fn(args) {
  return spawn(function* () {
// ...  
});}

扩展:
模块:严格模式后,ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this

8.ArrayBuffer

ArrayBuffer 和字符串的相互转换,使用原生 TextEncoder 和 TextDecoder 方法。

SharedArrayBuffer
允许 Worker 线程与主线程共享同一块内存。SharedArrayBuffer的 API 与ArrayBuffer一模一样,唯一的区别是后者无法共享数据。

Atomics 对象
防止两个线程同时修改某个地址,或者说,当一个线程修改共享内存以后,必须有一个机制让其他线程同步。SharedArrayBuffer API 提供Atomics对象,保证所有共享内存的操作都是“原子性”的,并且可以在所有线程内同步。

ArrayBuffer对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(TypedArray视图和DataView视图)来读写,视图的作用是以指定格式解读二进制数据

同一段内存,不同数据有不同的解读方式,这就叫做“视图”(view)

应用:
worker
ajax下载文件流,arrayBuffer,blob
FileReader

9.装饰器

装饰器可以用来装饰整个类。

@testable
class MyTestableClass {
  // ...
}

function testable(target) {
  target.isTestable = true;
}

MyTestableClass.isTestable // true

注意,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。

前面的例子是为类添加一个静态属性,如果想添加实例属性,可以通过目标类的prototype对象操作。

function testable(target) {
  target.prototype.isTestable = true;
}

@testable
class MyTestableClass {}

let obj = new MyTestableClass();
obj.isTestable // true

你可能感兴趣的:(javascript,es6)