ES6知识点小结

ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准。以下总结内容都是es6中新的特性,也是开发中经常用到知识点。

变量声明const和let

在ES6之前,我们都是用var声明变量,无论声明在何处,都会有变量提升(被视为声明在函数的最顶部,不在函数内即在全局作用域的最顶部)。

而es6之后,新增了let和const,声明不会出现没有变量提升,且都不允许重复声明。const声明一个只读的常量。一旦声明,常量的值就不能改变。一旦声明就必须立即初始化。

let和const都是块级作用域,看下面经典例子

var a=[];
//执行for循环

for(var i=0;i<10;i++){
      a[i]=function(){ //因为这个是定义,并没有调用方法,不会执行
           console.log(i);
      };
}

//for循环之后,此时 i = 10;
再次执行a6;因为 i 一直被引用,所以不会回收,
进入到 a[i] 的方法里面, 打印的是 i ,也就是10a6; //输出10
如果使用let声明i

var a=[];
for(let i=0;i<10;i++){
        a[i]=function(){  
          console.log(i);
        };
}
a[6]();    //打印6

a[6]函数(闭包)这个执行环境中,它会首先寻找该执行环境中是否存在 i,没有找到,因为 i 是块级作用域,就沿着作用域链继续向上到了其所在的代码块执行环境,找到了i=6,于是输出了6,即a6;的结果为6。这时,闭包被调用,所以整个代码块中的变量i和函数a6被销毁。

模板字符串

传统的 JavaScript 语言,输出模板通常是这样写的(下面使用了 jQuery 的方法)。

$('#result').append(   
'There are <b>' + basket.count + '</b> ' +  

 'items in your basket, ' +   '<em>' + basket.onSale + 

   '</em> are on sale!'

);

上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题。

$('#result').append(
`   There are <b>${basket.count}</b> items 
  in your basket, <em>${basket.onSale}</em>   are on sale!`);

对于ES6当然也提供了很多字符串的方法。

// 1.includes:判断是否包含然后直接返回布尔值

const str = 'hahay'

console.log(str.includes('y')) // true

// 2.repeat: 获取字符串重复n次

const str = 'he'

console.log(str.repeat(3)) // 'hehehe'

//如果你带入小数, Math.floor(num) 来处理

// s.repeat(3.1) 或者 s.repeat(3.9) 都当做成 s.repeat(3) 来处理

// 3. startsWith 和 endsWith 判断是否以 给定文本 开始或者结束

const str =  'hello world!'

console.log(str.startsWith('hello')) // true

console.log(str.endsWith('!')) // true

解构赋值

以前我们给变量赋值,只能直接指定值

var a = 1;var b = 2;var c = 3;console.log(a,b,c); // 1 2 3
 现在用解构赋值的写法就变得简单了,只要模式匹配上了就行了,如下

 注意数组是有顺序的

var [a,b,c] = [11,22,33];
console.log(a,b,c); // 11 22 33

var [b,a,c] = [11,22,33];
console.log(a,b,c); // 22 11 33
 当然解构赋值还有嵌套比较复杂的写法,如下


let [foo,[[bar],[baz]]] = [111,[[222],[333]]];
console.log(foo,bar,baz); // 111 222 333

let [head,…foot] = [1,2,3,4];
console.log(head,foot); // 1 [2,3,4]

如果解构不成功,变量的值就等于undefined,如下

var [bar3,foo3] = [1000];
console.log(bar3,foo3); // 1000 undefined

另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功

let [x,y] = [10000,20000,30000];
console.log(x,y); // 10000 20000
 默认值可以引用解构赋值的其他变量,但该变量必须已经声明

let [a=1,b=a] = [2,3];
console.log(a,b); // 2 3
 对象的解构也可以指定默认值

var {x,y=5} = {x:1};
console.log(x,y); // 1 5

对象的解构赋值解构不仅可以用于数组,还可以用于对象(json)

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;

而对象的属性没有次序,变量必须与属性同名,才能取到正确的值

var {a,b} = {a:'apple',b:'banana'};
console.log(a,b); // apple banana

var {b,a} = {a:'apple',b:'banana'};
console.log(a,b); // apple banana

如果变量名与属性名不一致,必须写成下面这样

let obj = {first:'hello',last:'world'};
// first ---> f,那么此时f就是first,而不是undefined了,有点类似别名的概念

let {first:f,last} = obj;
console.log(f,last); // hello world

1.也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。 真正被赋值的是后者,而不是前者

2.v是匹配的模式,n才是变量。真正被赋值的是变量n,而不是模式v。

//注意,采用这种写法时,变量的声明和赋值是一体的

v —> n,那么此时n就是vue,而不是undefined了

var {v:n} = {v:'vue',r:'react'};
console.log(n); // vue

console.log(v); // Uncaught ReferenceError: v is not defined
console.log(r); // Uncaught ReferenceError: r is not defined

函数

函数默认参数

ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。

function log(x, y) { 
  if(typeof  y ==="undefined") {
        y="world"
 }  console.log(x, y);
}
log('Hello') // Hello World 
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World

上面代码检查函数log的参数y有没有赋值,如果没有,则指定默认值为World

ES6为参数提供了默认值。在定义函数时便初始化了这个参数,以便在参数没有被传递进去时使用。

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

箭头函数

ES6 允许使用“箭头”(=>)定义函数。

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

箭头函数最直观的三个特点:

不需要 function 关键字来创建函数

省略 return 关键字

继承当前上下文的 this 关键字,this固定化
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于

var sum = function(num1, num2)

 {  return num1 + num2;}

所谓箭头函数的 this 捕获的是所在的上下文,比如下面这个例子:b是一个箭头函数,然后它的 this是指向window,这是为什么呢,因为箭头函数捕获的是obj{}这个对象的环境,然后这个环境的this指向的是window,就相当于上一条的例子:在c方法里面return的那个箭头函数捕获的是c:function(){}这个环境的this,而这个环境的this是obj

var obj = {
  a: 10,
  b: () => {    console.log(this.a); //undefined
    console.log(this); //window
  },
  c: function() {    console.log(this.a); //10
    console.log(this); //obj{...}
  }
}
obj.b(); 
obj.c();

对于函数的this指向问题:

1、箭头函数的this永远指向其上下文的 this,任何方法都改变不了其指向,如call(), bind(), apply()

2、普通函数的this指向调用它的那个对象

对象的扩展

对象初始化简写

ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁

const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo};

function f(x, y){ 
 return {x, y};
}
// 等同于
function f(x, y) {  return {x: x, y: y};
}

除了属性简写,方法也可以简写

const o = {
  method() {  
   return "Hello!";
  }
};
// 等同于const o = {
  method: function() { 
   return "Hello!";
  }
};

ES6 对象提供了 Object.assign() 这个方法来实现浅复制。

Object.assign() 可以把任意多个源对象自身可枚举的属性拷贝给目标对象,然后返回目标对象。第一参数即为目标对象。在实际项目中,我们为了不改变源对象。一般会把目标对象传为{}。

const objA = { name: 'cc', age: 18 }  
const objB = { address: 'beijing' }  
const objC = {} // 这个为目标对象
const obj = Object.assign(objC, objA, objB)
    // 我们将 objA objB objC obj 分别输出看看

console.log(objA)   // { name: 'cc', age: 18 }

console.log(objB) // { address: 'beijing' }

console.log(objC) // { name: 'cc', age: 18, address: 'beijing' }

console.log(obj) // { name: 'cc', age: 18, address: 'beijing' }

// 是的,目标对象ObjC的值被改变了。

// so,如果objC也是你的一个源对象的话。请在objC前面填在一个目标对象{}

Object.assign({}, objC, objA, objB)

rest参数

ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

//利用rest参数, 可以向该函数传入任意数目的参数

function add(...values) {  
  let sum = 0;    for (var val of values) {
        sum += val;
    } 
    return sum;
} 

add(2, 5, 3) // 10

//rest参数中的变量代表一个数组, 所以数组特有的方法都可以用于这个变量

function push(array, ...items) {
    items.forEach(function(item) {
        array.push(item);        console.log(item);
    });
}
var a = [];
push(a, 1, 2, 3)

注:rest参数之后不能再有其他参数( 即只能是最后一个参数)

展开运算符

扩展运算符( spread) 是三个点( …) 。 它好比rest参数的逆运算, 将一个数组转为用逗号分隔的参数序列。

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

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

[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

//合并数组[1, 2, ...more]
[...arr1, ...arr2, ...arr3]

//转为真正的数组。
var nodeList = document.querySelectorAll('div');
var array = [...nodeList];

//map转数组
let map = new Map([
    [1, 'one'],
    [2, 'two'],
    [3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]

对于 Object 而言,还可以用于组合成新的 Object 。(ES2017 stage-2 proposal) 当然如果有重复的属性名,右边覆盖左边。

const first = {
        a: 1,
        b: 2,
        c: 6,
    } 
const second = {
        c: 3,
        d: 4
    }
const total = { ...first, ...second }
console.log(total) // { a: 1, b: 2, c: 3, d: 4 }

import 和 export

import导入模块、export导出模块:

//全部导入import people from './example'

//有一种特殊情况,即允许你将整个模块当作单一对象进行导入
//该模块的所有导出都会作为对象的属性存在
import * as example from "./example.js"
console.log(example.name)
console.log(example.age)
console.log(example.getName())

//导入部分
import {name, age} from './example'

// 导出默认, 有且只有一个默认
export default App

// 部分导出
export class App extend Component {};

导入的时候有没有大括号的区别是什么。下面是总结:

当用export default people导出时,就用 import people 导入(不带大括号)。

一个文件里,有且只能有一个export default。但可以有多个export。

当用export name 时,就用 import{name} 导入(记得带上大括号)。

当一个文件里,既有一个export default people, 又有多个export name 或者 export age时,导入就用 importpeople,{name,age}。

当一个文件里出现n多个 export 导出很多模块,导入时除了一个一个导入,也可以用 import*asexample。

  1. Promise
    Promise 是异步编程的一种解决方案,比传统的解决方案–回调函数和事件,

在promise之前代码过多的回调或者嵌套,可读性差、耦合度高、扩展性低。通过Promise,用同步编程的方式来编写异步代码,极大的降低了代码耦合性而提高了程序的可扩展性。

下面是一个用Promise对象实现的 Ajax 操作的例子。

const getJSON = function(url) {
   const promise = new Promise(function(resolve, reject){
        const handler = function() {
               if (this.readyState !== 4) {
                       return;       
                }
               if (this.status === 200) {
                        resolve(this.response);       
               } else {
                        reject(new Error(this.statusText));               
               }
         };
         const client = new XMLHttpRequest();
         client.open("GET", url);
         client.onreadystatechange = handler; 
         client.responseType = "json";
         client.setRequestHeader("Accept", "application/json");
         client.send();
       });        

      return promise;
};
getJSON("/posts.json")
.then(function(json) { 
  console.log('Contents: ' + json);
}, function(error) { 
     console.error('出错了', error);
});

Promise

在promise之前代码过多的回调或者嵌套,可读性差、耦合度高、扩展性低。通过Promise机制,扁平化的代码机构,大大提高了代码可读性;用同步编程的方式来编写异步代码,保存线性的代码逻辑,极大的降低了代码耦合性而提高了程序的可扩展性。

就是用同步的方式去写异步代码。

//Promise对象 ---> 用来传递异步操作过来的数据的
//Pending(等待、处理中) ---> Resolve(完成,fullFilled)   ---> Reject(拒绝,失败)
//这里只是定义,还没开始执行
var p1 = new Promise(function(resolve,reject){
    resolve(1); // 成功了,返回一个promise对象1
    // reject(2); // 失败了
});

// 接收成功和失败的数据,通过then来传递
// then也是返回一个promise对象,会继续往下传递数据,传递给下一个then
p1.then(function(value){
    // resolve
    console.log(value); //执行打印1
    return value + 1; // 1
    alert(`成功了:${value}`);
},function(value){
    // reject
    alert(`失败了:${value}`);
}).then(function(value){
    console.log(value); // 2
});

//catch捕获异常错误
var p1 = new Promise(function(resolve,reject){
    resolve('成功了'); //返回一个promise对象“成功了”
});
//then也是返回一个promise对象,会继续往下传递数据
p1.then(function(value){
    console.log(value); //打印“成功了”
    // throw是用来抛错误的
    throw '发生了点小意外';
}).catch(function(e){
    // catch用来捕获这个错误的 ---> 追踪
    console.log(e);
});

//all ---> 全部,用于将多个promise对象,组合,包装成
//Promise.all([p1,p2,p3,...]); 所有的promise对象,都正确,才走成功
//否则,只要有一个错误,就走失败
var p1 = Promise.resolve(1);
var p2 = Promise.reject(0);
Promise.all([true,p1,p2]).then(function(obj){
    console.log(`成功了:${obj}`);
},function(obj){
    console.log(`失败了:${obj}`);
});

// race ---> 返回的也是一个promise对象
//最先执行的的promise结果,哪个最快我用哪个,所以下面打印的是one
var p1 = new Promise(function(resolve,reject){
    setTimeout(resolve,50,'one');
});
var p2 = new Promise(function(resolve,reject){
    setTimeout(resolve,100,'two');
});
Promise.race([p1,p2]).then(function(val){
    console.log(val);
});

//resolve ---> 生成一个成功的promise对象
//语法规则:Promise.resolve(val); // 普通值
// Promise.resolve(arr); // 数组之类
//Promise.resolve(promise); // 传递另一个promise对象
//传递普通值
Promise.resolve('success').then(function(val){
    // 注意resolve,走得是这里
    console.log(val); // success
},function(err){
    console.log("err:"+ err);
});
//传递数组
Promise.resolve([1,2,3]).then(function(val){
    // 注意resolve,走得是这里
    console.log(val); // [1,2,3]
},function(err){
    console.log(err);
});
//传递一个promise对象
var p1 = Promise.resolve(520);
var p2 = Promise.resolve(p1);
p2.then(function(val){
    //从p1那边传递过来的
    console.log(val); // 520
});

再来一道经典面试题:

setTimeout(function() {
  console.log(1)
}, 0);
new Promise(function executor(resolve) {
  console.log(2);
  for( var i=0 ; i<10000 ; i++ ) {
    i == 9999 && resolve();
  }
  console.log(3);
}).then(function() {
  console.log(4);
});
console.log(5);

首先先碰到一个 setTimeout,于是会先设置一个定时,在定时结束后将传递这个函数放到任务队列里面,因此开始肯定不会输出 1 。

然后是一个 Promise,里面的函数是直接执行的,因此应该直接输出 2 3 。

然后,Promise 的 then 应当会放到当前 tick 的最后,但是还是在当前 tick 中。

因此,应当先输出 5,然后再输出 4 。

最后在到下一个 tick,就是 1 。

“2 3 5 4 1”

Generators

在异步编程中,还有一种常用的解决方案,它就是Generator生成器函数。顾名思义,它是一个生成器,它也是一个状态机,内部拥有值及相关的状态,生成器返回一个迭代器Iterator对象,我们可以通过这个迭代器,手动地遍历相关的值、状态,保证正确的执行顺序。

当你调用一个generator时,它将返回一个迭代器对象。这个迭代器对象拥有一个叫做next的方法来帮助你重启generator函数并得到下一个值。next方法不仅返回值,它返回的对象具有两个属性:done和value。value是你获得的值,done用来表明你的generator是否已经停止提供值。

Generator的声明方式类似一般的函数声明,只是多了个*号,并且一般可以在函数内看到yield关键字

function* showWords() {
     yield 'one';     
     yield 'two';
     return 'three'; 
}
var show = showWords(); 
show.next() // {done: false, value: "one"}
show.next() // {done: false, value: "two"}
show.next() // {done: true, value: "three"}
show.next() // {done: true, value: undefined}

如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的

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

这个就需要用到yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。

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

Ajax 是典型的异步操作,通过 Generator 函数部署 Ajax 操作,可以用同步的方式表达

function* main() {
  var result = yield request("http://some.url");
  var resp = JSON.parse(result);
  console.log(resp.value);
}
function request(url) {
  makeAjaxCall(url, function(response){
    it.next(response);
  });
}
var it = main();
it.next();

上面代码的main函数,就是通过 Ajax 操作获取数据。可以看到,除了多了一个yield,它几乎与同步操作的写法完全一样。注意,makeAjaxCall函数中的next方法,必须加上response参数,因为yield表达式,本身是没有值的,总是等于undefined。

async 函数

es6引入了 async 函数,使得异步操作变得更加方便。

async 函数是什么?一句话,它就是 Generator 函数的语法糖。

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog')
  .then(function (result) {  console.log(result);});

一比较就会发现,async函数就是将 Generator 函数的星号“*” 替换成async,将yield替换成await,仅此而已。

async函数对 Generator 函数的改进,体现在以下四点:

内置执行器
更好的语义
更广的适用性
返回值是 Promise

Class基本语法

JavaScript 语言中,生成实例对象的传统方法是通过构造函数:

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(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
基本上,es6 的可以看作只是一个语法糖,它的绝大部分功能,es5 都可以做到,新的写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 es6 的%(red)[class]改写,就是下面这样。

//定义类
class Point {
  constructor(x, y) { 
   this.x = x;    this.y = y;
  }
  toString() { 
      return '(' + this.x + ', ' + this.y + ')';
  }
}

上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,es5 的构造函数Point,对应 es6 的Point类的构造方法。
Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。


转载请注明: MrLi ES6知识点小结

本篇
ES6知识点小结 ES6知识点小结
ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准。以下总结内容都是es6中新的特性,也是开发中经常用到知识点。 变量声明const和let在ES6之前,我们都是用var声明变量,无论声明在何处,都会有变量提升
2018-10-10
下一篇
javaScript常用数组方法总结 javaScript常用数组方法总结
数组是javaScript非常重要的数据结构,任何一位前端工程都应该也是必须掌,握,今天整理了常用的方法。做得不好,仅供参考 改变自身值的方法(9个)push、pop、reverse、shift、sort、splice、unshift,以及
2018-09-07
目录