在进一步学习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快速入门