ES6学习系列(1):先来记住这些常用要点

2017-08-06  本文已影响0人  huangyh_max

前言

七月中旬,集中时间学习使用React框架的时候,接触到了很多ES6的语法。一开始看的时候,简直一脸蒙蔽,后面对看不懂的代码在google之后知道基本都是ES6的语法的使用。那么,我的学习领域肯定也得跟进扩展到ES6啦。现在,ES6也是前端工作者的基本标配了。
当然,我们可以按照阮一峰大大的教程来详细学习,http://es6.ruanyifeng.com/
但是考虑到时间成本,肯定先挑重点常用的来学习和记忆。
七月底的时候整理了ES6的几个比较常用和重要的语法,今天修整发布下,当然是配合栗子来记录的。
后面打算,对ES6的学习整理为一个系列,发布在博客记录,这是系列记录第一篇~嘻嘻。

目前ES6的支持情况:

使用gulp搭建一个ES6的开发转换环境,以作学习测试使用:
https://segmentfault.com/a/1190000004394726?utm_source=APP&utm_medium=iOS&utm_campaign=socialShare

let和const

对于var,我们通过例子来知晓var的变量影响的范围:
栗子1:
这个例子可以看到var a变量提升到全局,同名变量会被覆盖:

function aa(){
    var a=1;
    console.log(a); //1
    if(true){
        var a=2
    };
    console.log(a) //2
}

这是闭包中函数内的嵌套函数的使用情况下,嵌套函数内定义为var b的变量的变量提升程度的例子。可以看到,只提升到嵌套函数的顶部而已,没有再往上升一级。

function test() {
    var b=1;
    function a() {
        var b = 2
        console.log('闭包中的b:',b)
      
    }
    console.log('全局变量的b',b)
    return a;
}
var result=test();
result();

配合这个例子看:

function test() {
    var b=1;
    function a() {
      if (true) {
        var b = 2
        console.log(b)
      }
      return a;
    }
    console.log('b: ', b)
}
test() //b:1
let

let的用法和var类似,区别就是let所声明的变量,只在let命令所在的代码块内有效。也就是,let声明的变量不会提升,被锁在当前块。
看下面一系列的例子:

function test() {
    if(true) {
      console.log(a)//TDZ,俗称临时死区,用来描述变量不提升的现象
      let a = 1
    }
}
test()  //a is not defined
function test() {
    if(true) {
      let a = 1
    }
     console.log(a)
}
test()  //a is not defined

正确的使用就是,在let声明变量后,紧接着在同个作用域下使用:

function test() {
    if(true) {
      let a = 1
      console.log(a)
    }
}
test() // 1
const

const是声明常量用的。声明之后的常量,是read only,而不能被赋值的,否则会报错。

const  a=233;
a=666;
console.log(a)  //

而如果我们用const来声明一个对象Object ,那么这种情况下,反而可以修改对象内属性的值。记住const常量修改自身的值不可以,而修改test.a的值可以:

const test={
  a:1
}
test.a=2
console.log(test)

let和const的异同点:
相同点:let和const都只在当前块的作用域内有效而已,不会变量提升。
不同点:let声明的变量是可以重复赋值的,而const声明的常量不能被赋值。

在这里延伸一道经典面试题:
正常我们使用的for循环:

for(var i = 0; i < 5; i++) {
    console.log(i) //0,1,2,3,4
}

如果在其中加入回调:

for(var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i) //5, 5, 5, 5, 5
  }, 0)
}
console.log(i) //5 i跳出循环体污染外部函数

将var改成let之后


for(let i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i) // 0,1,2,3,4
  }, 0)
}
console.log(i)//i is not defined i无法污染外部函数
应用:

当希望变量保证不被恶意修改,使用const。例如在react中,props传递的对象是不可更改的,所以使用const声明;
声明一个对象的时候,推荐使用const;
当需要修改声明的变量值时,使用let,var能用的场景都可以使用let替代。

解构赋值

函数的扩展

rest参数

rest参数就是“扩展运算符...”。

let a=[1,2,3];
let [c,...d]=a;
console.log(c) //1
console.log(d) //2,3

注意:rest参数只能作为最后一个参数,之后不能有其他参数。否则会报错:

let a=[1,2,3];
let [...d,c]=a;

console.log(d) //报错:SyntaxError:Rest element must be last element in array

函数的length属性,不包括rest参数
(function(a){}).length //1
(function(a,...b){}).length //1

一个我觉得很好的数组push的例子:

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

另一个使用res参数来取代arguments对象的例子:

const pa=(...args) =>{
   console.log(args)
   return args.reduce((pre,cur)=>{
     return pre+cur;
   },0)
};
pa.apply(this,[1,2,3,4]) //10

在这个例子中做一个知识点延伸就是reduce()方法的使用。
Array.reduce(callback,[initialValue])
就是先得是一个数组来使用这个reduce()方法,然后reduce方法会对数组中的每一个元素都调用一次callback函数。initialValue是可填项,如果填了的话,就传入给callback函数中,作为累加的初始项。
而callback函数就是比如:

function add(first,second){
   return first+second;
}

学了这个reduce方法后,以后可以替代使用for循环来做累加的操作了。

所以这个例子就等同于:

const pa=function(...args){
   console.log(args)
   return args.reduce(function(pre,cur){
     return pre+cur;
   },0)
}
pa.apply(this,[1,2,3,4])
箭头函数

学习这个箭头函数,只需要谨记:

var f=v =>v;

等同于

var f=function(v){
   return v;
 };

如果函数没有参数或有多个参数时,那么就需要写一个圆括号()

var a=()=>5;
//等同于
var a=function(){
   return 5;
}
var sum=(a,b) =>{
  return a+b;
}
//等同于
var sum=function(a,b){
   return a+b;
}
const s=function(){
   return {
       a:'hello world'
   }
}

等价于

const s=()=>{
  return {
       a:'hello world'
   }
}

就等于后面用{}括住的代码块,直接就是function()后面跟着这一块,不用用{}再括第二次。

箭头函数这块,我觉得阮一峰大大的教程里的例子就非常能让人理解了,直接看他的说明教程中的例子就行。具体摘抄来看下:
箭头函数使用起来很简洁对不对,当我们用在回调函数里的时候,就更觉得简洁了。
看这个例子:

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

如果是用箭头函数来写:

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

哎呀,好简洁对不对~~~~
这个例子,我看了又可以延伸一个知识点了:map()方法的使用。
map()方法在MDN上的解释:对数组的每个元素调用定义的回调函数,并返回包含结果的数组。
这样写言简意赅,一目了然这个方法的用法。
就是把[1,2,3]这个数组中的每个元素,执行x*x,得到相应的结果,组成一个数组返回,结果就是[1,4,9]

箭头函数中this的指向

重点要说箭头函数的this指向。从此不用再使用var _this=this的用法来重新修改this的指向了。
所以有人会说箭头函数,是一种语法糖,因为使用箭头函数时,使用者就不用去确认不用情形使用下this的指向了。
具体也是看阮一峰教程的例子来理解:
函数体内的this的指向,是定义时所在的对象

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}
var id = 21;
foo.call({ id: 42 });
// id: 42

之前学this篇的时候,我写的博客中专门说了,setTimeout()方法和 setInterval()中this的指向是全局对象window,尽管如果setTimeout()方法是在dom元素的事件下使用的,也不会指向dom元素对象本身。
所以这个例子中,按这个道理,setTimeout()中的this是指向全局对象window的,那this.id按理应该是等于全局的id变量21才对。但是使用箭头函数时,this的指向指向了foo这个函数对象。所以this.id对应的就是通过call()方法传入的id的值42,输出为id:42

看阮一峰教程里写的两个对比例子:

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数
  setInterval(() => this.s1++, 1000);
  // 普通函数
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0

一样道理, setInterval()方法是箭头函数的写法的话,就指向定义所在的Timer函数对象,那么this.s1就取this.s1=0的值。而setInterval()函数是普通写法的话,尽管是包裹在Timer函数下定义的,但this指向运行时所在的作用域(即全局对象)。但是我们看全局下没有timer.s2的定义值,所以就一次都不更新。所以,3100毫秒之后,timer.s1被更新了3次,而timer.s2一次都没更新。

再看这个DOM 事件的回调函数封装在一个对象里面的例子,对回调函数使用箭头函数的写法的好处:
正常使用ES5的写法,我们需要使用var _this=this,才能在document对象的点击事件的函数内调用doSomething这个属性。

var handler = {
  id: '123456',
  init: function() {
     var _this=this
    document.addEventListener('click',
      function(event){
          _this.doSomething(event.type), false);
      }
  },
  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
};

现在换成箭头函数的写法,我们就可以摒弃_this的借位写法了:本来init()方法中的this.doSomething这一行的this是会指向document对象的,但使用箭头函数后是指向handler对象了。

var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
};

class的使用:取代构造函数实例化得到对象的做法

基本使用

在JavaScript中,我们面向对象的写法,在ES5规范下,都是使用构造函数,然后实例化这个构造函数,然后来得到对象的。现在ES6引入了其他语言中的class概念来实现面向对象,来等价于我们以往习惯的构造函数的写法:
例如:下面这个构造函数:

funtion constr(){
    this.a=1;
    this.b=2;
}
constr.prototype.init=function(){
   console.log(this.b)
};
var sum=new constr();
sum.init;

可以使用class语法,等价转化为:

class constr {
  constructor() {
    this.a=1;
    this.b=2;
  }
  init() {
    console.log(this.b);
  } //注意有个括号
}
let sum  = new constr ();
sum.m()  //2
继承

Class通过extends关键字实现继承,和ES5中的原型链继承不一样,相对简洁很多。
例如:

class Point{
}
class ColorPoint extends Point{
}

子类ColorPoint 继承了父类Point的所有属性和方法,等于就是把父类Point中的所有属性方法都复制到子类ColorPoint 中

重点是要结合super()关键字使用。记住下面的话:子类必须在constructor方法中调用super方法,否则新建实例时会报错。

class Point{
}
class ColorPoint extends Point{ 
    constructor(){
       
    }
}
let cp=new ColorPoint (); //ReferenceError

因为子类没有自己的this对象,而是继承父类的this对象,然后对其加工的。如果不用super()方法,那么子类就得不到this对象。

再说一点,在子类的构造函数中,只有调用super之后才能使用this关键字,否则报错。
在下面这个例子中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法后的this.color就正确。

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

class ColorPoint extends Point {
  constructor(x, y, color) {
    this.color = color; // ReferenceError
    super(x, y);
    this.color = color; // 正确
  }
}

最后来说下,生成子类实例的写法,也是一样使用new的方式来生成实例,传入数值:

let cp=new ColorPoint (25,8,'green')

然后看例子可知,cp这个实例继承自子类,也继承自父类:

cp instanceof ColorPoint  //true
cp instanceof Point //true
super关键字的使用

super关键字,当做函数使用和当做对象使用,两种情况下的用法完全不同。
这点待我补充。

Module

JavaScript是一直没有模块(module)体系的,在浏览器端没办法实现I/O。而前端工作者们为了适应大型复杂的前端项目所需的模块化需求,逐渐发展出前端模块化的规范:commonJS、AMD和CMD。(这一块的知识,我也专门写了一篇前端模块化规范发展的记录博客)。这些规范,无非都是使用import、require、export这一类的方法,来讲一个大程序拆分成相互依赖的小文件。
而到ES6,就终于实现了模块功能,等于JavaScript语法上就原生支持模块的实现方式了,而不必再依赖框架(例如requireJS框架)来转换过渡了。
ES6的Module有两个关键字import和export。export是将本身的属性方法给暴露出去,给其他模块使用。而一个模块想使用其他模块中的属性方法时,就使用import
具体看栗子:
在lean.js中将属性a的值传递出去:

export let a=1;

然后我们想在init.js模块中使用a的值:

import {a} from 'lean.js';  //或具体为lean.js所在的文件路径
console.log(a) //1

这其中实现的本质是,前端是无法实现I/O的,所以import和export的使用,就是需要借助webpack或gulp这样的配置环境,使用babel.js插件,将其转化为commonJS中的require,在node.js服务端运行的。

Promise

下一篇继续讲这个知识点。

Generator

下一篇继续讲这个知识点。

上一篇下一篇

猜你喜欢

热点阅读