React-Native_02:语法篇

1.简介


ECMAScript 6.0(以下简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

JavaScript的创造者Netscape公司,之后将JavaScript提交给国际标准化组织ECMA,希望这种语言能够成为国际标准,ECMAScript和JavaScript的关系是,前者是后者的规格,后者是前者的一种实现,

之所以不叫JavaScript,有两个原因。
一是商标,Java是Sun公司的商标,根据授权协议,只有Netscape公司可以合法地使用JavaScript这个名字,且JavaScript本身也已经被Netscape公司注册为商标。
二是想体现这门语言的制定者是ECMA,不是Netscape,这样有利于保证这门语言的开放性和中立性。

在线将ES6代码转为ES5代码(https://babeljs.io/repl/)
Babel是一个广泛使用的ES6转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。这意味着,你可以用ES6的方式编写程序,又不用担心现有环境是否支持。

2.变量声明
ES5只有2种声明变量的方法:var、function。
ES6共有6种声明变量的方法:var、function、let、const、import(require)、class。

2.1 var命令

var a = 10;
var b = 20;
var c = 30;

var a = 10,b = 20,c = 30;

var arr = [1,2,3,4,5];
var a = arr[0];
var b = arr[1];
var c = arr[3];

var obj = {
  name: 'gary',
  age: 20
}
var a = obj.name;
var b = obj.age;

没有用var关键字,使用直接赋值方式声明的是全局变量,例如:
a = 10;

全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。ES5之中,全局对象的属性与全局变量是等价的。
window.a = 1;
a // 1
a = 2;
window.a // 2



2.2 function命令
var 方式定义的函数,不能先调用函数,后声明,只能先声明函数,然后调用。
function方式定义函数可以先调用,后声明。

aaa();//这样调用就会出错   
var aaa = function(){   
      alert("aaa");   
  }   
aaa();//这样就不会出错   
    
//先调用后声明   
bbb();   
function bbb(){   
      alert("bbb");   
}   


2.3 let命令

块级有效
ES5只有全局作用域和函数作用域,没有块级作用域,在ES6中,let实际上为JavaScript新增了块级作用域。
用来声明变量,用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}

for循环的计数器,就很合适使用let命令。例如:
var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[5](); //10
a[6](); // 10

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[5](); //5
a[6](); // 6

变量提升
let不像var那样会发生“变量提升”现象??????


console.log(foo); // 输出undefined
console.log(bar); // 报错ReferenceError
var foo = 2;
let bar = 2;

实测结果两个都是undefined,应该是网上资料错误,可以通过Babel来了解底层原理


暂时性死区
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp='dev';
if (true) { 
  console.log(tmp);

  let tmp; 
  console.log(tmp);

  tmp = 123;
  console.log(tmp);
}
在let命令声明变量tmp之前,都属于变量tmp的“死区”。


不允许重复声明
let不允许在相同作用域内,重复声明同一个变量。
// 报错
function test() {
  let a = 10;
  var a = 1;
}
// 报错
function test() {
  let a = 10;
  let a = 1;
}
因此,不能在函数内部重新声明参数。
function func(arg) {
  let arg; // 报错
}
function func(arg) {
  {
    let arg; // 不报错
  }
}


2.4 const命令

const声明一个只读的常量。一旦声明,就必须立即初始化,不能留到以后赋值。也不能改变。
const PI = 3.1415;
console.log(PI); // 3.1415
PI = 3;// TypeError: Assignment to constant variable.

const的作用域与let命令相同:只在声明所在的块级作用域内有效,声明的常量,也与let一样不可重复声明。

const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个对象声明为常量必须非常小心。
const foo = {};
foo.prop = 123;
console.log(foo.prop);// 123
foo = {}; // TypeError: "foo" is read-only

var命令和function命令声明的全局变量,依旧是全局对象的属性;
let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。也就是说,从ES6开始,全局变量将逐步与全局对象的属性脱钩。

var a = 1;
// 如果在Node的REPL环境,可以写成global.a
// 或者采用通用方法,写成this.a
window.a // 1
let b = 1;
window.b // undefined


2.5 import命令

模块的功能主要由 export 和 import 组成.每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口,通过import来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。

ES6将一个文件视为一个模块,通过export 向外输出了一个变量。一个模块也可以同时往外面输出多个变量。
//test.js
var name = 'Rainbow';
var age = '24';
export {name, age};

定义好模块的输出以后就可以在另外一个模块通过import引用。
import {name, age} from './test.js'

整体输入,module指令
//test.js
export function getName() {
  return name;
}
export function getAge(){
  return age;

通过 import * as 就完成了模块整体的导入。
import * as test form './test.js';

通过指令 module 也可以达到整体的输入。
 module test from 'test.js';
 test.getName();

export default
不用关系模块输出了什么,通过 export default 指令就能加载到默认模块,不需要通过 花括号来指定输出的模块,一个模块只能使用 export default 一次

// default 导出
export default function getAge() {} 
 
// 或者写成
function getAge() {}
export default getAge;

// 导入的时候不需要花括号
import test from './test.js';

一条import 语句可以同时导入默认方法和其它变量.
import defaultMethod, { otherMethod } from 'xxx.js';

2.6 class命令

如果你用过纯面向对象语言,那么你会对class的语法非常熟悉。
class People {
    constructor(name) { //构造函数
          this.name = name;
    }
    sayName() {
          console.log(this.name);
    }
}

var p = new People("Tom");
p.sayName();

上面定义了一个People类,他有一个属性 name 和一个方法 sayName(),还有一个构造函数; 
就像函数有函数声明和函数表达式两种定义方式,类也可以通过类表达式来定义:
let People = class {
    constructor(name) { //构造函数
          this.name = name;
    }
    sayName() {
          console.log(this.name);
    }
  }

你可能以为类声明和类表达式的区别在于变量提升的不同。但是事实是无论是类声明还是类表达式的方式来定义,都不会有变量提升。


通过关键字 extends 来继承一个类,并且,可以通过 super 关键字来引用父类。
class Student extends People {
    constructor(name, grade) { //构造函数
        super(name);    //通过 super 调用父类的构造函数的。
          this.grade = grade;
    }
    sayGrade() {
          console.log(this.grade);
    }
}
上面的例子中我们定义了一个 Student ,他是 People 的子类。


下面我们给 name 属性定义 getter 和 setter
class People {
    constructor(name) { //构造函数
          this.name = name;
    }
    get name() {
        return this._name.toUpperCase();
    }
    set name(name) {
        this._name = name;
    }
    sayName() {
          console.log(this.name);
    }
}
var p = new People("tom");
console.log(p.name);    //TOM
console.log(p._name);    //tom
p.sayName();    //TOM
仔细看上面的例子,搞清楚最后三行分别会输出什么,就明白getter 和 setter该怎么用了。

主要是要区分 this._name 和 this.name 的区别。因为我们定义了 name 的读写器,而没有定义 _name 的读写器,所以访问这两个属性的结果是不同的。

但是要注意一点,不要这样写:

set name(name) {
    this.name = name;
}
因为给 this.name 赋值的时候会调用 set name ,这样会导致无限递归直到栈溢出。

通过 static 关键字定义静态方法:
class People {
    constructor(name) { //构造函数
          this.name = name;
    }
    sayName() {
          console.log(this.name);
    }
    static formatName(name) {
        return name[0].toUpperCase() + name.sustr(1).toLowerCase();
    }
}
console.log(People.formatName("tom"));


3.解构赋值

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。
如果解构不成功,变量的值就等于undefined。


let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [x, , y] = [1, 2, 3];//不完全解构
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

解构赋值允许指定默认值。
var [foo = true] = [];
foo // true

[x, y = 'b'] = ['a']; // x='a', y='b'

[x, y = 'b'] = ['a', undefined]; // x='a', y='b'

注意,ES6内部使用严格相等运算符(===),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。

默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
let [x = 1, y = x] = [];     // x=1; y=1
let [x = 1, y = x] = [2];    // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = [];     // ReferenceError

解构赋值不仅适用于var命令,也适用于let和const命令。
var [v1, v2, ..., vN ] = array;
let [v1, v2, ..., vN ] = array;
const [v1, v2, ..., vN ] = array;

对于Set结构,也可以使用数组的解构赋值。
let [x, y, z] = new Set(["a", "b", "c"]);
x // "a"

对象的解构赋值
解构不仅可以用于数组,还可以用于对象。
var { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
var { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

var { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

如果变量名与属性名不一致,必须写成下面这样。
var { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'
这实际上说明,对象的解构赋值是下面形式的简写。
var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
var { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined

变量的声明和赋值是一体的。对于let和const来说,变量不能重新声明,所以一旦赋值的变量以前声明过,就会报错。
let foo;
let {foo} = {foo: 1}; // SyntaxError: Duplicate declaration "foo"
let baz;
let {bar: baz} = {bar: 1}; // SyntaxError: Duplicate declaration "baz"
上面代码中,解构赋值的变量都会重新声明,所以报错了。不过,因为var命令允许重新声明,所以这个错误只会在使用let和const命令时出现。如果没有第二个let命令,上面的代码就不会报错。
let foo;
({foo} = {foo: 1}); // 成功
let baz;
({bar: baz} = {bar: 1}); // 成功
上面代码中,let命令下面一行的圆括号是必须的,否则会报错。因为解析器会将起首的大括号,理解成一个代码块,而不是赋值语句。
 
和数组一样,解构也可以用于嵌套结构的对象。
var obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};
var { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

注意,这时p是模式,不是变量,因此不会被赋值。
var node = {
  loc: {
    start: {
      line: 1,
      column: 5
    }
  }
};
var { loc: { start: { line }} } = node;
line // 1
loc  // error: loc is undefined
start // error: start is undefined

默认值生效的条件是,对象的属性值严格等于undefined。

如果要将一个已经声明的变量用于解构赋值,必须非常小心。
// 错误的写法
var x;
{x} = {x: 1};
// SyntaxError: syntax error
上面代码的写法会报错,因为JavaScript引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免JavaScript将其解释为代码块,才能解决这个问题。
// 正确的写法
({x} = {x: 1});

由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
var arr = [1, 2, 3];
var {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3

字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5

数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。

解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

函数参数的解构也可以使用默认值。
function move({x = 0, y = 0} = {}) {
  return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]


函数move的参数是一个对象,通过对这个对象进行解构,得到变量x和y的值。如果解构失败,x和y等于默认值。
function move({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

函数move的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。

事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。
function* fibs() {
  var a = 0;
  var b = 1;
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}
var [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5

解构赋值用途:

(1)交换变量的值
[x, y] = [y, x];

上面代码交换变量x和y的值,这样的写法不仅简洁,而且易读,语义非常清晰。


(2)从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
// 返回一个数组
function example() {
  return [1, 2, 3];
}
var [a, b, c] = example();
// 返回一个对象
function example() {
  return {
    foo: 1,
    bar: 2
  };
}

var { foo, bar } = example();


(3)函数参数的定义

解构赋值可以方便地将一组参数与变量名对应起来。
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }

f({z: 3, y: 2, x: 1});


(4)提取JSON数据

解构赋值对提取JSON对象中的数据,尤其有用。
var jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]

上面代码可以快速提取JSON数据的值。


(5)函数参数的默认值
jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
}) {
  // ... do stuff
};

指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || 'default foo';这样的语句。


(6)遍历Map结构
任何部署了Iterator接口的对象,都可以用for...of循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。
var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world
如果只想获取键名,或者只想获取键值,可以写成下面这样。
// 获取键名
for (let [key] of map) {
  // ...
}
// 获取键值
for (let [,value] of map) {
  // ...

}


(7)输入模块的指定方法
加载模块时,往往需要指定输入那些方法。解构赋值使得输入语句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");

4.编程风格
4.1 采用严格模式:'use strict';

主要有以下限制:
变量必须声明后再使用
函数的参数不能有同名属性,否则报错
不能使用with语句
不能对只读属性赋值,否则报错
不能使用前缀0表示八进制数,否则报错
不能删除不可删除的属性,否则报错
不能删除变量delete prop,会报错,只能删除属性delete global[prop]
eval不会在它的外层作用域引入变量
eval和arguments不能被重新赋值
arguments不会自动反映函数参数的变化
不能使用arguments.callee
不能使用arguments.caller
禁止this指向全局对象
不能使用fn.caller和fn.arguments获取函数调用的堆栈
增加了保留字(比如protected、static和interface)


4.2 let取代var

在块级作用域下,let完全可以取代var,因为两者语义相同,而且let没有副作用。

4.3 全局常量和线程安全

在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。所有的函数都应该设置为常量。这符合函数式编程思想,有利于将来的分布式运算。
const声明常量还有两个好处,一是阅读代码的人立刻会意识到不应该修改这个值,二是防止了无意间修改变量值所导致的错误。


长远来看,JavaScript可能会有多线程的实现(比如Intel的River Trail那一类的项目),这时let表示的变量,只应出现在单线程运行的代码中,不能是多线程共享的,这样有利于保证线程安全。

4.4 字符串
静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。

4.5 解构赋值
使用数组成员对变量赋值时,优先使用解构赋值。

函数的参数如果是对象的成员,优先使用解构赋值。

如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。

4.6 对象
单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。

对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。

如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义。

另外,对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写。

4.7 数组
使用扩展运算符(...)拷贝数组。

使用Array.from方法,将类似数组的对象转为数组。

4.8 函数
立即执行函数可以写成箭头函数的形式。

那些需要使用函数表达式的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了this。

简单的、单行的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法。

所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。

不要在函数体内使用arguments变量,使用rest运算符(...)代替。因为rest运算符显式表明你想要获取参数,而且arguments是一个类似数组的对象,而rest运算符可以提供一个真正的数组。

4.9 Map结构
注意区分Object和Map,只有模拟现实世界的实体对象时,才使用Object。如果只是需要key: value的数据结构,使用Map结构。因为Map有内建的遍历机制。

4.10 Class
总是用Class,取代需要prototype的操作。因为Class的写法更简洁,更易于理解。

使用extends实现继承,因为这样更简单,不会有破坏instanceof运算的危险。

4.11 模块
首先,Module语法是JavaScript模块的标准写法,坚持使用这种写法。使用import取代require。

如果模块只有一个输出值,就使用export default,如果模块有多个输出值,就不使用export default,不要export default与普通的export同时使用。

不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。

如果模块默认输出一个对象,对象名的首字母应该大写。

5.网络资源
http://www.w3school.com.cn/js/index.asp
http://es6.ruanyifeng.com/
http://babeljs.io/

你可能感兴趣的:(React-Native)