Skip to content

ES6 #24

Description

@chengfengliu

let和const

let命令所在的代码块内有效。实际上为 JavaScript 新增了块级作用域。
以后for就用let
变量不提升,存在暂时性死区。
块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。

// IIFE 写法
(function () {
  var tmp = ...;
  ...
}());

// 块级作用域写法
{
  let tmp = ...;
  ...
}

const声明一个只读的常量。一旦声明,常量的值就不能改变。
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

深入理解ES6
const声明不允许修改绑定,但允许修改绑定的值。
默认使用const,只有确实需要改变变量的值时使用let。因为大部分变量的值在初始化后不应再改变,而预料外的变量值的改变是很多bug的源头。

ES6 声明变量的六种方法

ES5 只有两种声明变量的方法:var命令和function命令。ES6 添加了let命令,const命令,import命令和class命令。
var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。

解构

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
模式名:变量名(冒号前面是用来匹配的,如果没有冒号等价于模式名:模式名,即变量名即模式名)

用途

(1)交换变量的值(2)从函数返回多个值(3)函数参数的定义(4)提取 JSON 数据

字符串

静态字符串一律使用单引号,动态字符串使用反引号。
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

函数

rest 参数(形式为...变量名)

用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
每个函数最多只能声明一个不定参数,而且一定要放在所有参数的末尾。

// arguments变量的写法
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}

// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();

箭头函数

var f = v => v;

// 等同于
var f = function (v) {
  return v;
};

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。一个参数不需要括号。函数体只有一行代码,不需要写return。
作用1、简化;2、函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象(普通函数的this经常指向document全局变量)。减少了显式绑定this对象的写法(call、apply、bind)。(this对象的指向是可变的,但是在箭头函数中,它是固定的。)

function foo() {
  setTimeout(() => {
    console.log(this);
    console.log('id:', this.id);
  }, 2000);
}
/*function foo() {
    console.log(this);//this = {id:42}
  setTimeout(function(){
    console.log(this);//this = window真正执行要等到 2000 毫秒后
    console.log('id:', this.id);
  }, 2000);
}*/
var id = 21;

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

// 相当于ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

默认参数

明确函数的多重用途

ES5及早期版本中的函数具有多重功能,可以结合new使用,函数内的this值将指向一个新对象库函数最终会返回这个新对象。
function Person(name) {
this.name = name;
}
var person = new Person("Nicholas");
var notAPerson = Person("Nicholas");

console.log(person); //"[Object object]"
console.log(notAPerson); //"undefined"

JavaScript函数有两个不同的内部方法:[[Call]]和[[Construct]]。当通过new关键字调用函数时,执行的是[[Construct]]函数,它负责创建一个通常被称作实例的新对象,然后再执行函数体,将this绑定到实例上。相当于Java的构造函数。 如果不通过new关键字调用函数,则执行[[Call]]函数,从而直接执行代码中的函数体。相当于普通函数。

深入理解ES6

let PageHandler = {
  id: "123456",
  init: function() {
    document.addEventListener("click", function(event) {
      this.doSomething(event.type);  // 抛出错误
    }, false);
  },
  doSomething: function(type) {
    console.log("handing " + type + "for" + this.id);
  }
};

实际上,因为this绑定的是事件目标对象的引用(在这段代码中引用的是document),而没有绑定PageHandler,且由于this.doSomething()在document中不存在,所以无法正常执行。
可以使用bind()方法显式地将函数的this绑定到PageHandler上来修正这个问题。

let PageHandler = {
  id: "123456",
  init: function() {
    document.addEventListener("click", (function(event) {
      this.doSomething(event.type); 
    }).bind(this), false);
  },
  doSomething: function(type) {
    console.log("handing " + type + "for" + this.id);
  }
};

为了避免创建一个额外的函数,使用箭头函数。如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this;否则,this的值会被设置为全局对象。

let PageHandler = {
  id: "123456",
  init: function() {
    document.addEventListener("click", event => {
      this.doSomething(event.type);  
    }, false);
  },
  doSomething: function(type) {
    console.log("handing " + type + "for" + this.id);
  }
};

数组

拓展运算符

拓展运算符可以让你指定一个数组,将它们打散后作为各自独立的参数传入函数。
在大多数使用apply()方法的情况下展开运算符可能是一个更合适的方案。

Array.from()

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']

实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。

数组实例的 entries(),keys() 和 values()

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

find()和findIndex()

如果要在数组中根据某个条件查找匹配的元素,那么find()方法和findIndex()方法可以很好地完成任务;如果只想查找与某个值匹配的元素,则indexOf()方法和lastIndexOf()方法是更好的选择。

对象

对象字面量语法拓展

属性初始值的简写

当一个对象的属性与本地变量同名时,不必再写冒号和值,简单地只写属性名即可。

function createPerson(name, age) {
  return {
    name,
    age
  }
}

对象方法的简写语法

var person = {
  name: "lcf",
  sayName() {
    ...
  }
}

简写语法可以使用super关键字。

可计算属性名

let lastName = "cf"
let person = {
  "first name" = "l",
  [lastname] = "cf"
}

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

Symbol

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
Symbol 值通过Symbol函数生成(不用new,因为不是对象)。Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
注意,Symbol 值作为对象属性名时,不能用点运算符。用方括号

Set和Map数据结构

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。WeakSet 的成员只能是对象。
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。WeakMap只接受对象作为键名。

Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
var proxy = new Proxy(target, handler);
target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是target)进行操作。
get(target, propKey, receiver):拦截对象属性的读取,set(target, propKey, value, receiver):拦截对象属性的设置。receiver参数可选,其实是proxy实例本身。

Promise

异步编程
简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果(pending(进行中)、fulfilled(已成功)和rejected(已失败))。
可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。???Promise对象提供统一的接口,使得控制异步操作更加容易。

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数(接收传出来的参数)。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

所有Promise都有then()方法,它接受两个参数:第一个是当Promise的状态变为fulfilled时要调用的函数,与异步操作相关的附加数据都会传递给这个完成函数。第二个是当Promise的状态变为rejected时要调用的函数,其与完成时调用的函数类似,所有与失败状态相关的附加数据都会传递给这个拒绝函数。
完成函数和拒绝函数总是在执行器(new Promise(executer))完成后被添加到任务队列的末尾。
其实then就是一个监听器。监听Promise的状态,根据状态调用相关函数。
那怎么转换状态呢?要用resolve和reject函数。
什么时候转换状态呢?例如fs.readFile(),要在成功读取文件数据完毕后将Promise状态从pending变为fulfilled,即在callback函数resolve(result)

callback
step1:主线程读取JS代码,此时为同步环境,形成相应的堆和执行栈;
step2: 主线程遇到异步任务,指给对应的异步进程进行处理(WEB API);
step3: 异步进程处理完毕(Ajax返回、DOM事件处理、Timer到等),将相应的异步任务推入任务队列;
step4: 主线程执行完毕,查询任务队列,如果存在任务,则取出一个任务推入主线程处理(先进先出);
step5: 重复执行step2、3、4;称为事件循环。

其中的异步进程有:
a、类似onclick等,由浏览器内核的DOM binding模块处理,事件触发时,回调函数添加到任务队列中;
b、setTimeout等,由浏览器内核的Timer模块处理,时间到达时,回调函数添加到任务队列中;
c、Ajax,由浏览器内核的Network模块处理,网络请求返回后,添加到任务队列中。

回调函数是事件完成后才被添加到任务队列中的,异步事件由各自的异步模块处理和决定是否完成。队列就是先进先出,哪个事件先完成其回调函数越早被调用。

Iterator

遍历器对象本质上,就是一个指针对象。
指针对象的next方法,用来移动指针。开始时,指针指向数组的开始位置。然后,每次调用next方法,指针就会指向数组的下一个成员。

for...of循环

循环对象属性 for...in
遍历数组 for...of
for...in循环出的是key,for...of循环出的是value
优点:可以保证输出顺序,能与break、continue、return配合使用

Generator

Generator 函数是一个状态机,封装了多个内部状态。还是一个遍历器对象生成函数返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。
调用遍历器对象的next方法,使得指针移向下一个状态。可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
next()、throw()、return()这三个方法本质上是同一件事,它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。
Generator 函数的暂停执行的效果,意味着可以把异步操作写在yield表达式里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield表达式下面,反正要等到调用next方法时再执行。(Ajax)
Generator函数的异步应用
异步操作需要暂停的地方,都用yield语句注明。value属性是yield语句后面表达式的,表示当前阶段的值;done属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。
Thunk函数在JavaScript里是一个只接受回调函数作为参数的单参数函数。
yield命令用于将程序的执行权移出 Generator 函数,那么就需要一种方法,将执行权再交还给 Generator 函数。

这种方法就是 Thunk 函数,因为它可以在回调函数里,将执行权交还给 Generator 函数。

function run(fn) {
  var gen = fn();
  function next(err, data) {
    var result = gen.next(data);//next方法还可以接受参数,向 Generator 函数体内输入数据
    if (result.done) return;
    result.value(next); //result.value其实是个Thunk函数,参数是一个回调函数
  }
  next();//递归函数
}
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);

var g = function* (){
  var r1 = yield readFileThunk('/etc/fstab'); //执行权将交给其他协程
  console.log(r1.toString());//r1得到data
  var r2 = yield readFileThunk('/etc/shells');
  console.log(r2.toString());
};
run(g);

深入理解ES6

function *createIterator() {
  let first = yield 1;
  let second = yield first + 2;
  yield second + 3;
}
let iterator = createIterator();
                                         每次yield前正在执行的语句
iterator.next().value; // 1                ...yield 1;
iterator.next(4).value; // 6  let first = ...  
                                                       ...yield first + 2;
iterator.next(5).value; // 8  let second = ... 
                                       yield second + 3;
iterator.next().value; // undefined

异步操作

// like fs.readFile
function fetchData() {
  // ...
  // doSomething
  // ...
  // like
  let a
  for(i = 0; i < 1000000000; i++) {
     a += i
  }

  // complete
  return function(callback) {
    //       err   result
    callback(null, "Hi!")
  }
}

fetchData()(function(err, result) {
  console.log(result)
})

只有fetchData完成所有操作(读文件,读数据库等)才会返回一个可以执行回调过程的函数。回调函数接收fetchData已读取的数据作为参数,意思是你可以在回调函数操作这些数据。
执行异步操作的传统方式

fs.readFile("./config.json", function(err, contents) {
  if(err) {
    throw err;
  }
 doSomethingWith(contents)
}

用同步代码执行异步操作
这里的yield作用是暂停run函数执行,直到readFile返回结果(什么时候返回结果由任务执行器决定)
像node.js中的fs.readFile和mongodb中的db.users.find函数返回的结果就是异步调用函数中的result参数。

run(function *() {
  let contents = yield readFile("./config.json")
  doSomethingWith(contents)
})

async函数

async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,它就是 Generator 函数的语法糖。
async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。
(1)内置执行器 所有koa2的 await next()要括号
async函数内部return语句返回的值,会成为then方法回调函数的参数。
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

class

模块

模块真正的魔力所在是仅导出和导入你需要的绑定,而不是将所用东西都放到一个文件。
导出默认值export default意义是不用知道模块导出函数具体名字,只需赋值给自己创建的对象即可导入。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions