3.2 ES6语法基础
ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准,因为ES6是在2015年发布的,所以又称其为ECMAScript 2015。和上一版ES5不同,ES6引入了大量的新思路和语法规则,本节重点讨论ES6常用的一些新特性。
目前,并不是所有浏览器都兼容ES6,但是主流的浏览器都支持ES6,并且服务器端的代码基本上都支持ES6语法,越来越多的IDE纷纷默认支持ES6语法。对于一些老的项目,必要的时候还可以使用Babel将ES6转为ES5。
ES6提供了许多新的语法和代码特性来提高JavaScript的性能,并且在代码结构上更加简洁易懂。下面列举一些常见的特性。
3.2.1 组件的导入与导出
组件导出
在组件导出方面,在ES5语法里,导出类通过module.exports来导出。
//ES5 导出类
var MyComponent = React.createClass({
...
});
module.exports = MyComponent;
在ES6语法里,使用export default来导出类或组件。
//ES6 导出类
export default class MyComponent extends Component{
...
}
组件导入
在组件的导入方面,在ES5语法里,使用CommonJS标准导包,通过require来导入组件。例如:
//ES5 导入React Native组件 var ReactNative = require("react-native"); var { Image, Text, } = ReactNative; //ES5 导入自定义组件 var MyComponent = require('./MyComponent');
在ES6语法里,则使用import方式导入组件。
//ES6 导入React Native组件 import { Image, Text } from 'react-native' // ES6 导入自定义组件 import MyComponent from './MyComponent';
3.2.2 类
在传统的JavaScript中,通常使用function和protoType来模拟类的概念,每个函数(function)就是一个对象,而每个函数对象又包含一个prototype对象。但是,在ES6中引入了class关键字,添加了对类的支持(其实,在JavaScript中class一直作为保留字使用),使用类的概念之后,对象的创建和继承更加直观,对父类的调用、实例化、静态方法和构造函数等概念都更加形象化,代码结构也更加清楚直观。JavaScript本身就是面向对象的语言,ES6中提供的类,实际上更加方便了开发者对JavaScript原型模式的包装。
创建类
在ES5里,通过React.createClass来创建一个组件类。
//ES5创建类
var Demo = React.createClass({
…
});
在ES6里,通过继承自React.Component的class来定义一个组件类。
//ES6创建类
class Demoextends React.Component {
…
}
组件类方法
在传统的JavaScript中,通常使用function函数来给组件定义方法。在ES6中,则可以直接使用函数名字来定义方法,在方法结束外也不需要使用逗号。
//ES5定义类内部方法 var Demo = React.createClass({ componentWillMount: function(){ }, … }); //ES6 定义类内部方法 var Demo = React.createClass({ componentWillMount(){ } … });
组件的属性与属性类型
在ES5语法中,属性类型和默认属性通过propTypes和getDefaultProps()来实现。例如:
//ES5 属性与属性类型定义
var Video= React.createClass({
getDefaultProps: function(){
return {
autoPlay: false,
maxLoops: 10,
};
},
propTypes: {
autoPlay: React.PropTypes.bool.isRequired,
maxLoops: React.PropTypes.number.isRequired,
videoSrc: React.PropTypes.string.isRequired,
},
…
});
在ES6里,统一使用static成员来修饰属性类型和默认属性。例如:
//ES6 属性与属性类型定义
class Video extends React.Component {
static defaultProps = {
autoPlay: false,
maxLoops: 10,
};
static propTypes = {
autoPlay: React.PropTypes.bool.isRequired,
maxLoops: React.PropTypes.number.isRequired,
videoSrc: React.PropTypes.string.isRequired,
};
…
}
3.2.3 状态变量
在React框架中,所有的界面被视为一个简单的状态机,那么任意一个UI场景就是状态机中的一种状态。状态机的状态一旦发生变化,就会触发界面的重新渲染。
在ES5语法中,React Native的状态变量在getInitialState()中声明。例如:
//ES5 状态机变量声明
let Index = React.createClass({
getInitialState:function(){
return {
var1:'value of var1',
var2:30,
var3:true
} };
});
而在ES6语法中,状态机变量的声明必须在组件的构造函数中声明。例如:
//ES6 状态机变量声明
var Index extends Component {
constructor(props){
super(props);
this.state = {
var1: 'value of var1',
var2:30,
var3:true
}; }
}
3.2.4 回调函数
在ES5语法中,回调函数可以直接调用本组件的某个成员方法。例如:
//ES5 添加回调函数 let Login = React.createClass({ getInitalState:function(){ return { inputedNum:'' }; }, updateNum: function(num){ //更新成员数量作为回调函数 this.setState((state)=> { return { inputedNum:num }; }); }, //省略… render:function(){ return( <View style={styles.container}> <TextInput style={styles.innerViewStyle} placeholder={"请输入账号"} //指定第一个回调函数 onChangeText={(num)=> this.updateNum(num)} /> //省略… </View> ); } });
在使用ES6语法编写React Native组件时,组件的回调函数必须在组件的构造函数中执行绑定操作。使用ES5语法绑定组件操作也是在这一步,但是React类的creatClass()代劳了这一操作,React.createClass()会把所有的方法都绑定一遍。使用ES6绑定回调函数时,开发者必须手动在代码中添加绑定操作。例如:
//ES6 开发者在构造函数中手动添加绑定 class Login extends Component { constructor(props){ super(props); this.state = { inputedNum:'' }; //手动绑定回调函数 this.updateNum = this.updateNum.bind(this); } updateNum(num){ this.setState((state)=> { return { inputedNum: num }; }); } //省略… render(){ return( <View style={styles.container}> <TextInput style={styles.innerViewStyle} placeholder={"请输入账号"} //指定回调函数 onChangeText={(num)=> this.updateNum(num)} /> </View> ); } }
相对于ES5语法,使用ES6语法开发需要开发者自己手动绑定每一个回调函数,这对于开发者来说似乎是一种开发方便性上的退步。
3.2.5 参数
ES6对参数的写法做了较大的改动,主要体现在参数默认值、不定参数、拓展参数方面。
参数默认值
通常来说,函数调用者不需要传递所有可能存在的参数,没有被传递的参数可由默认的参数进行填充。JavaScript有严格的默认参数格式,未被传值的参数默认为undefined。
在ES5语法中,对于默认值的设定,往往需要通过逻辑或操作符运算来设置。而在ES6中,系统允许开发者在函数定义的时候就指定任意参数的默认值。例如:
//设置默认参数 function sayHello(name){ //传统的指定默认参数的方式 var name=name||'dude'; console.log('Hello '+name); } //运用ES6的默认参数 function sayHello2(name='dude'){ console.log('Hello ${name}'); } sayHello(); //输出:Hello dude sayHello('Wayou'); //输出:Hello Wayou sayHello2(); //输出:Hello dude sayHello2('Wayou'); //输出:Hello Wayou
不定参数
在传统的JavaScript代码中开发者可以通过arguments变量来接受任意数量的参数,例如:string.prototype.concat()就可以接受任意数量的字符串参数。ES6提供了一种可变参函数的新方式——不定参数。
不定参数指在函数中使用命名参数,同时接收不定数量的未命名参数。不定参数的格式是3个句点后跟代表所有不定参数的变量名。例如:
//将所有参数相加 function add(...x){ return x.reduce((m, n)=>m+n); } //传递任意个数的参数 console.log(add(1,2,3)); //输出结果:6
拓展参数
ES6语法提供了一种新的参数格式,它允许传递数组或者类数组,直接做为函数的参数,而不用通过apply方法。例如:
//ES6 扩展参数 var people=['Wayou', 'John', 'Sherlock']; function sayHello(people1, people2, people3){ console.log('Hello ${people1}, ${people2}, ${people3}'); } //以拓展参数的形式传递 sayHello(...people); //输出:Hello Wayou, John, Sherlock //ES5语法格式,需要使用函数的apply方法 sayHello.apply(null, people); //输出:Hello Wayou, John, Sherlock
3.2.6 箭头操作符
ES6新增了一种新的语法格式:箭头操作符(=>),用来简化函数的书写。该操作符左边为输入参数,使用圆括号包含参数部分,而右边则是操作结果或返回的值。例如,在JavaScript中,回调是一种常见的操作,在ES6之前,每次使用匿名回调函数都需要写一个function,甚是繁琐。
基本语法格式如下:
(形参列表)=>{ //函数体}
示例代码如下:
var array = [1, 2, 3];
//传统写法
array.forEach(function(v, i, a){
console.log(v);
});
//ES6
array.forEach(v = > console.log(v));
3.2.7 Symbol
在ES5之前没办法创建私有变量,只能通过封装来解决,而symbol的到来为JavaScript开发者带来了福音。利用Symbol创建私有变量的代码如下:
// 创建一个Symbol let name= Symbol(); let person = {}; person[name] = "张三";
使用Symbol创建私有变量的时候,还可以传入字符串。例如:
var s1 = Symbol("abc"); var s2 = Symbol("abc"); console.log(s1 == s2); //false
说明:任意两个Symbol创建的变量都不相等,即使使用了相同的参数。
Symbol作为基础类型,开发者可以使用typeof操作符来判断变量是否为Symbol类型,如果是Symbol类型则返回“symbol”。例如:
let symbol = Symbol();
console.log(typeof symbol); // 输出结果 "symbol"
由于每个Symbol的值都是不相等的,这意味着Symbol的值可以作为标识符用于对象的属性名,从而保证不会出现同名的属性。例如,下面两种写法其结果是相同的:
var mySymbol = Symbol(); // 第一种写法 var a = {}; a[mySymbol] = 'Hello! '; // 第二种写法 var a = { [mySymbol]: 'Hello! ' }
Symbol属性名遍历
Symbol作为属性名既不能作为for循环的对象,也不能使用Object.keys()、Object. getOwnPropertyNames()、JSON.stringify()来操作Symbol属性。但是可以通过Object. getOwnPropertySymbols()获取指定对象的所有Symbol属性名。相关示例代码如下:
var obj = {}; var a = Symbol('a'); var b = Symbol('b'); obj[a] = 'Hello'; obj[b] = 'World'; // 返回obj对象所有Symbol类型的属性名组成的数组 var objectSymbols = Object.getOwnPropertySymbols(obj); console.log(objectSymbols) //输出结果 [Symbol(a), Symbol(b)]
Symbol.for()和Symbol.keyFor()
Symbol.for(字符串参数):在全局环境中搜索以该字符串作为参数Symbol值,如果搜到则返回这个Symbol,如果搜不到则创建一个Symbol,并把它注册在环境中。
var obj = {}; //第一次搜不到,则新创建一个 var a = Symbol.for("tom"); //第二次加入搜索 var b = Symbol.for("tom"); console.log(a == b); //返回true
Symbol.keyFor(symbol):返回一个已经注册的Symbol对象的key。
var a = Symbol("tom"); var b = Symbol.for("tom"); console.log(Symbol.keyFor(a)); //输出 undefined console.log(Symbol.keyFor(b)); // 输出tom
3.2.8 解构
在ES5或更早的版本中,若一个函数要返回多个值,常规的做法是返回一个对象,将每个值作为这个对象的属性返回,这样就需要书写很多相似的代码。例如:
// 从对象中获取数据
let options = {
repeat: true,
save: false
};
let repeat = options.repeat,
save = options.save;
而在ES6中,利用解构这一特性,可以直接返回一个数组或者对象,然后利用数组或对象的属性将值解析到对应的变量中。
对象解构
对象结构,就是在赋值语句的左侧使用类似对象的结构。例如:
let node = {
type: " student ",
name: "tom"
};
//对象结构
let { type, name } = node;
如果你想要改变声明变量的值,可以使用解构表达式。例如:
let node = {
type: "student",
name: "tom"
},
type = "teacher",
name = "jack";
//注意:必须要在圆括号内才能使用解构表达式
({type, name} = node)
在上面的操作中,都是将对象的属性值赋值给同名变量,其实还可以将属性值赋值给不同的名的变量,不过需要注意的是,冒号后面才是要定义的新的变量。例如:
let node = { type: "teacher" }; let { type: localType, name: name = "tom" } = node; console.log(localType); // "teacher" console.log(name); // "tom"
数组解构
数据解构和对象解构类似,只是将操作的对象换成了数组,而解构操作使用的是数组内部的索引。例如:
let colors = [ "red", "green", "blue" ]; let [ firstColor, secondColor ] = colors; console.log(firstColor); // "red" console.log(secondColor); // "green"
如果只读取数组中某一项的值,对于其他的选项可以不用命名。例如:
let colors = [ "red", "green", "blue" ];
//只取数组中的第三项的值
let [ , , thirdColor ] = colors;
console.log(thirdColor); // "blue"
在数组解构中有一个常用的功能是交换两个变量的值,在传统的写法中,我们需要使用第三方变量进行交换。例如:
let a = 3, b = 4, temp; temp = a; a = b; b = temp; console.log(a); console.log(b)
而ES6完全抛弃了这种第三方变量的方式,直接使用数组解构表达式。
temp = a;
let a = 3, b = 4;
//利用数组解构表达式交换变量值
[a, b] = [b, a];
console.log(a);
console.log(b)