React Native移动开发实战
上QQ阅读APP看书,第一时间看更新

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)