React快速上手2-ES6语法

在进一步学习react之前,你需要对js的新语法有一定的了解,因为有可能在react项目中我们会一遍又一遍的使用,如果你对js新语法有足够的了解,你可以跳过这一节。

变量

b变量是一种标识符号,你可以在接下来的程序中使用它,变量在使用前必须要声明。我们有三种方式来声明变量:var,let和const,这三种方式在声明之后的使用中会有所不同。

1. var

在ES2015之前,var是声明变量的唯一方式,它有下面这些特点:

如果你给一个未声明的变量赋值,结构可能有所不同。在现代环境中,如果严格模式开启,你会得到一个错误,而在旧的环境中(或者严格模式被禁止)你将会得到一个全局变量。

如果你声明了一个变量但是没给它赋值,这个变量将是undefined,直到你给其赋值为止。

var a  //typeof a ==='undefined'

你可以重复声明一个同一个变量,后面的值会覆盖掉前面的值。

var a = 1
var a = 2

你也可以使用var同时声明多个变量并赋值。

var a =1, b=2

在函数外用var声明的变量都是全局变量,在任何地方都能访问到它,而在函数里面用var声明变量,就只有局部作用域,这个变量只能在函数内部访问,其它地方都不能访问。重要的一点是var声明的变量没有块级作用域,只有函数作用域。

在函数内部,不管var声明的变量定义在函数的什么位置,哪怕是最后,它仍然能够在代码开始处获取到,因为javascript执行前会把所有的变量声明移动到最顶部(变量提升)。为了避免这种结果,所以每次都在函数最开始声明变量。

2.使用let

let是ES6提出的新特性,它比var多了一个快作用域,它的作用域限制在定义它的块,语句或表达式。
现代js开发者可能只选择使用let,而完全丢弃var,在函数外使用let定义的变量不会变成一个全局变量,这点适合var声明的变量完全不一致的。

3.使用const

使用let或者var声明的变量在程序后面可能被更改,或者重新定义,但是如果用const声明的值,它将不能再被更改。

const a = 'hello'

但是如果使用const声明了一个常量,它的值是一个对象,那么还是可以去更改这个常量的属性值的。

const a ={a:1}
a.a =2 //a.a的值会被改成2

const和let一样,也拥有块级作用域,我们会用const去声明一个在后面的代码中不会改变的值。

箭头函数

箭头函数是ES6中最具影响力的改变,并且在今天得到广泛的使用,它们和普通函数还是有一些区别的。从代码书写角度来看,箭头函数能够让我们以一种更简短的语法来定义一个函数:

const oldFunction = function(){
  //...
}

变成:

const arrowFunction = () => {
  //...
}

如果这个函数体只有一行表达式,你可以省略花括号并且只写成一行:

const myFunction = () => doSomething()

也可以通过括弧来传递参数:

const myFunction = (param1,param2) => doSomething(param1,param2)

如果只有一个参数,括弧也可以被省略掉

const myFunction = param => doSomething(param)

箭头函数对于一些简单的函数定义非常有用。

1.箭头函数的return

箭头函数有一个隐藏的return,如果箭头函数的函数体只是一个值,你可以不用return就能获得一个返回值。注意,花括号需省略掉:

const myFunction = () => 'test'
myFunction() //test

如果返回值是一个对象,那么就要注意需要用括弧包括这个对象,否则会认为是箭头函数的函数体,报语法错误:

const myFunction = () => ({a:1})
myFunction() //{a:1}

2.箭头函数的this

this在js中是一个复杂的概念,不同的函数上下文或者不同的javascript模式(严格或不严格)都会影响this的指向。对于我们来说,掌握好this的概念非常重要,因为箭头函数在这点上和普通函数有着完全不一样的区别。
在一个对象中,对象有一个普通函数定义的方法,在这里this指向是这个对象本身:

const obj = {
  name: 'Tom',
  age: 16,
  writeName: function() {
    console.log(this.name)
  }
}
obj.writeName() //Tom

调用obj.writeName()将会打印“Tom”

而箭头函数的this继承的是当前执行上下文,箭头函数至始至终都没有绑定this,所以this值将会在调用堆栈中查找,如果使用箭头函数来定义上面的对象,结果将是打印“undefined”

const obj = {
  name: 'Tom',
  age: 16,
  writeName: () => {
    console.log(this.name)
  }
}
obj.writeName() //undefined

因为这一点,箭头函数不适合在对象方法中使用。
箭头函数也不能作为构造函数使用,否则在创建一个对象时会抛出一个TypeError错误。
在DOM绑定事件的时候,如果使用箭头函数作为事件的回调,里面的this指向为window,而如果是普通函数作为事件回调,this指向的则是该DOM:

const ele = document.querySelector('#ele')
ele.addEventListener('click',() => {
  // this === window
})
const ele = document.querySelector('#ele')
ele.addEventListener('click',function() {
  // this === ele
})

使用扩展运算符(...)操作数组和对象

扩展运算符...在现代javascript中是一种非常有用的操作方式。我们可以从操作数组开始熟悉这一操作:

cosnt a = [1, 2, 3]

然后你可以这样创建一个新数组

cosnt b = [...a, 4, 5, 6] // [1, 2, 3, 4, 5, 6]

你也可以复制一个数组

cosnt c = [...a] // [1, 2, 3]

同样的,你也可以这里来复制一个对象

const newObj = { ...oldObj }

如果是一个字符串使用...,我们会用字符串中的每个字符创建一个数组

const str = 'Hello'
const arr = [...str] // [H, e, l, l, o]

...运算符还可以用来很方便的传递函数参数

cosnt func = (param1,param2) => {}
cosnt paramArr = [1, 2]
func(...paramArr)
//在ES6之前你可能使用f.apply(null,a)来传递参数,但是这样不美观,可读性也很差

扩展运算符在数组解构中运用:

const arr = [1, 2, 3, 4, 5]
[a1, a2, ...a3] = arr
/**
*a1 = 1
*a2 = 2
*a3 = [3, 4, 5]
**/

扩展运算符在对象解构中运用:

const {p1, p2, ...p3} = {
    p1: 1,
    p2: 2,
    p3: 3,
    p4: 4
}
p1 // 1
p2 // 2
p3 // {p3: 3, p4: 4}

对象和数组的解构赋值

第一个例子,我们使用解构语法定义一些变量:

const person = {
  firstName: 'Tom',
  age: 18,
  gender: 'boy'
}
const {firstName: name, age} = person
name // 'Tom'
age // 18

在这个例子中我们定义了两个变量:name和age,name的值是person.firstName,age的值是person.age,如果变量名和对象的属性名一致的话,可以省略写,也就是说:

const {firstName: name, age} = person
// 等同于const {firstName: name, age: age} = person

同样的,这样的写法在数组中也起作用:

const arr = [1, 2, 3, 4, 5]
const [a1, a2] = arr
a1 // 1
a2 // 2

如果我们想创建第三个变量,这个变量是数组arr中的第5个值:

const arr = [1, 2, 3, 4, 5]
const [a1, a2, , , a3 ] = arr
a1 // 1
a2 // 2
a3 // 5

模板字符串

在ES6中模板字符串是一种新的声明字符串的方式,非常有用。第一眼看上去时,它的语法很简单,只是在声明字符串时使用反引号(`)替换单引号(‘’)或双引号(“”):

const str1 = `my test string`

但是实际上他们很不一样,因为他们提供了不少比用‘’或“”建立的普通字符串没有的特性:

  • 方便定义多行字符串
  • 方便在字符串中使用变量或者表达式

多行字符串

ES6之前,定义多行字符串是比较麻烦的事:

const str = 
'first line\n \
second line'

或者

const str = 'first line\n' + 'second line'

使用模板字符串会非常简单和美观:

const str = `
first line
      second line
`

并且定义时输入的空白字符也会得到保留。

插值语法

我们可以使用${...}语法在模板字符串中插入变量或者表达式:

const name = 'Tom'
const str = `name is ${name}`
str // 'name is Tom'

const str1 = `age is ${10+8}`
str1 // `age is 18

const str2 = `gender is ${false?'male':'female'}`
str2 // gender is female

Classes(类)

JavaScript有一种非常罕见的实现继承的方式:原型继承,但与大多数其他流行的编程语言的继承实现不同,后者是基于类的。来自其他语言的人很难理解原型继承的复杂性,因此ECMAScript委员会决定在原型继承之上撒上语法糖,这样它就像基于类的继承在其他流行实现中的工作方式。
这很重要:底层下的JavaScript仍然相同,您可以通常的方式访问对象原型。

1.一个类的定义

下面就是一个简单的类定义:

class People {
  constructor(name) {
    this.name = name
  }

  sayHello() {
    return 'Hello, I am ' + this.name + '.'
  }
}
let people_tom = new People('Tom')
people_tom.sayHello()
// "Hello, I am Tom."

当一个对象初始化后,consturctor方法将会被调用,并且传递参数,这个对象也可以调用类中声明的方法。

2.类继承

一个类可以从其它类进行扩展,通过这个扩展类建立的对象,将会继承所有类的方法。如果继承的类具有与层次结构中较高的类之一具有相同名称的方法,则最接近的方法优先:

class Student extends People {
  sayHello() {
    return super.sayHello() + ' I am a student.'
  }
}
const  student_tom = new Student('Tom')
student_tom.sayHello()
// "Hello, I am Tom. I am a student."

类没有显式的类变量声明,但您必须初始化构造函数中的所有变量。在类中,您可以引用调用super()的父类。

3.静态方法

通常,方法是在实例上定义的,而不是在类上定义的,现在静态方法在类上执行:

class People {
  static genericHello() {
    return 'Hello'
  }
}
People.genericHello() 
//Hello

4.取值函数(getter)和存值函数(setter)

您可以添加以get或set为前缀的方法来创建getter和setter,它们是根据您正在执行的操作执行的两个不同的代码:访问变量或修改其值。
对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

class People {
  constructor(name) {
    this._name = name
  }
  set name(newName) {
    this._name = newName
  }
  get name() {
    return this._name.toUpperCase();
  }
}
let p1 = new People('Tom')
p1._name // Tom
p1.name // TOM
p1.name = 'Jack'
p1._name // 'Jack'
p1.name  // 'JACK'

如果您只有一个getter,则无法设置该属性,并且任何尝试这样做的操作都会被忽略:

class People {
  constructor(name) {
    this._name = name
  }
  get name() {
    return this._name
  }
}
let p1 = new People('Tom')
p1._name // Tom
p1.name // Tom
p1.name = 'Jack'
p1._name // 'Tom
p1.name  // 'Tom'

如果您只有一个setter,则可以更改该值,但不能从外部访问它:

class People {
  constructor(name) {
    this._name = name
  }
  set name(newName) {
    this._name = newName
  }
}
let p1 = new People('Tom')
p1._name // Tom
p1.name //undefined
p1.name = 'Jack'
p1._name // 'Jack'
p1.name  //undefined

Promises

Promise是在JavaScript中处理异步代码的一种方法,避免了在代码中写太多回调的问题。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Async函数使用promises API作为基础,因此理解它们是很基本的,即便在较新的代码中,您可能会使用异步函数而不是promises。

1.创建一个promise

我们使用new Promise()来初始化一个promise对象:

let isDone = true
const testPromise = new Promise((resolve, reject) => {
  if (isDone) {
    const result = 'success!'
    resolve(result)
  } else {
    const result = 'failed!'
    reject(result)
  }
})

正如您所看到的,promise会检查已完成的全局常量,如果值为true,我们使用resolve传回一个值,否则使用reject传回一个值。在上面的例子中我们只返回一个字符串,但它也可以是一个对象。

2.使用promise

我们使用上面创建的promise对象作为示例:

testPromise.then(res =>{
  console.log(res)
}).catch(err => {
  console.log(err)
})

promise并使用then回调等待它解析,如果有错误,它将在catch回调中处理它。

3.链式写法

一个promise对象可以返回另一个promise,因此可以使用链式的写法:

const statusFunc = response => {
  if (response.status >= 200 && response.status < 300) {
    return Promise.resolve(response)
  }
  return Promise.reject(new Error(response.statusText))
}
const toJson = response => response.json()
fetch('/todos.json')
  .then(statusFunc)
  .then(toJson)
  .then(data => {
    console.log('Success!', data)
  })
  .catch(error => {
    console.log('Failed', error)
  })

在这个例子中,我们调用fetch()去读取todos.json文件,然后创建一个promises链。
运行fetch()会返回一个响应,该响应具有许多属性:

  • status,表示HTTP状态代码的数值
  • statustext,状态消息,如果请求成功,则为OK

statusFunc方法读取JSON文件数据,返回是一个promise;
toJson方法把上一步骤中json数据通过json()方法转为json对象,它也是返回一个promise;
这一长串的方法会发生什么样的事情呢?链中的第一个promise是我们定义的方法statusFunc,它检查响应状态,如果实在200到300之间,就是reslove状态,否则就是失败的reject状态,如果是reject状态,就会跳出后面所有的promise,直接被catch()所捕获到,记录失败信息。
如果是成功的状态,下一个promise会把上一步promise的返回值当作输入值来做处理。

4.错误处理

我们使用catch方法来处理promise中的错误,当promise链中的任何内容失败并引发错误或reject时,代码执行都调转到链中最近的catch()语句中,此时的catch方法的输入会是代码执行的异常或者是reject方法的输入值。

5.Promise.all()

如果你定义了一个promises列表,需要等待所有promise都有执行结果后再进行下一步处理,Promise.all()是一个方便的处理方法:

const p1 = fetch('/test1.json')
const p2 = fetch('/test2.json')

Promise.all([p1, p2])
  .then(res => {
    console.log('results: ', res)
  })
  .catch(err => {
    console.error(err)
  })

而ES6的解构赋值语法也可以这样来写:

Promise.all([p1, p2]).then(([res1, res2]) => {
  console.log('Results', res1, res2)
})

6.Promise.race()

Promise.race()会在您传递给它的一个promise有执行结果后立即运行,并且只处理一次后面回调,执行的是第一个执行完成的promise的结果:

const p1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'first')
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'second')
})
Promise.race([p1,p2]).then(result => {
  console.log(result) // 'second'
})

ES6 modules简介

ES Modules是用于处理模块的ECMAScript标准。虽然Node.js多年来一直使用CommonJS标准,但是在浏览器中从未有过模块系统,直到ES6中modules标准的制定,浏览器才开始实施这个标准,现在ES模块在现代浏览器Chrome,Safari,Edge和Firefox中都得到了支持(从60版开始)。
模块功能非常有用,您可以封装各种功能,并将此功能公开给其他JS文件使用。

1.ES modules语法

导入一个模块的语法很简单:

import package from 'module-name'

模块是一个使用export关键字导出一个或多个值(对象,函数或变量)的js文件。 下面即为一个简单的模块:

// test.js
export default str => str.toUpperCase()

在例子中,模块定义了单个的default export,因此它可以是匿名函数,否则,它需要一个名称来区别于其它导出。然后任何其他js模块都可以通过导入test.js来导入它提供的功能:

import toUpperCase from './uppercase.js'

之后我们就可以在js代码中使用:

toUpperCase('test') //'TEST'

2.其它import/export选项

在前面的例子中,我们创建了一个默认的导出,但是有时候我们可能需要在一个js文件中导出多个内容:

const a = 1
const b = 2
const c = 3
export { a, b, c }

在其它js中的引用可以有多种写法:

  • 引入所有的export
import * from 'module'
  • 使用结构赋值引入一部分的export
import { a, b } from 'module'
  • 方便起见,你可以使用as重命名任何export
import { a, b as test } from 'module'
  • 您可以按名称导入默认export和任何非默认export,这种方式在react中比较常见
import React, { Component } from 'react'

持续更新中

上一篇:React快速上手1-react安装
下一篇:React快速上手3-JSX快速入门

你可能感兴趣的:(React快速上手2-ES6语法)