js es6 语法 通读

卧槽,我的giao giao
参考地址:
http://es6.ruanyifeng.com/
2015 年 6 月正式发布了es6 版本。是的js现在能够编写大型复杂的程序了。

  • let 和 const

let 是用来声明局部变量的。
声明的变量只在他的作用域有效。例如:

{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1

由于a 是let 声明的,所以在 { } 外使用就报错了。
另外常见的就是 for 循环了。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

上面的for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域,有点继承的感觉,有子作用域,就先用子的。
带有 { } 的运算都看做是代码块,let 在其中,代码块外面的就不能使用

  • 不存在变量提升

var 声明的变量,其实可以在未声明前使用的,只不过值为underfined,多少让人感到奇怪。
let 就不一样了,声明前使用直接报错。例如下面:

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
  • 暂时性死区

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错 甚至使用 typeof 也不行

{typeof x; // ReferenceError  程序直接报错了
let x; }
#但是如果吗,下面没有 let 就没问题了
////////////////////
typeof x;  // undefined

也就是说let 和 const 具有严格的作用域隔离作用。使用的话,必须在当前作用域一开始就声明

  • 不允许重复声明

let不允许在相同作用域内,重复声明同一个变量

// 报错
function func() {
  let a = 10;
  var a = 1;
}

// 报错
function func() {
  let a = 10;
  let a = 1;
}
function func(arg) {
  let arg;
}
func() // 报错

function func(arg) {
  {
    let arg;
  }
}
func() // 不报错
  • ES6 的块级作用域
    let实际上为 JavaScript 新增了块级作用域
    块级作用域可以任意嵌套
{{{{
  let insane = 'Hello World';
  {let insane = 'Hello World'}
}}}};
  • const 命令

const声明一个只读的常量。一旦声明,常量的值就不能改变。

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

const foo;
// SyntaxError: Missing initializer in const declaration

const声明的常量,也与let一样不可重复声明

  • ES6 声明变量的六种方法

ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。

  • 变量的解构赋值

  • 数组赋值

以前,为变量赋值,只能直接指定值。

let a = 1;
let b = 2;
let c = 3;

现在允许这样了:

let [a, b, c] = [1, 2, 3];

如果解构不成功,变量的值就等于undefined

let [foo] = [];
let [bar, foo] = [1];

以上两种情况都属于解构不成功,foo的值都会等于undefined。
右边数量多,也是可以成功的,这个和函数多传的参数有点类似

let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
  • 对象的解构赋值

解构不仅可以用于数组,还可以用于对象。
数组解构是按照顺序赋值,而对象结构则是按照key名字

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

写成这样可以把变量叫做其他名字

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
  • 模板字符串

这个已经写过了

  • es6函数的扩展

这个扩展太多了,记不住,选择常用的讲讲

  • 箭头函数

  • ES6 允许使用“箭头”(=>)定义函数。
    一定注意 箭头函数并没有 this, 所以不要用 箭头函数作为元素的事件的触发函数
    会作为变量一直向上级词法作用域查找,直至找到为止,经常导致 Uncaught TypeError: Cannot read property of undefined 或其他错误
var f = v => v;

// 等同于
var f = function (v) {
  return v;
};

箭头函数不需要参数,或者多余一个参数,需要用 var f = ([..+ 或者 0参数])
=> xxx

  • 箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
var sum = (num1, num2) => { ..; .. ; return num1 + num2; }

如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错(因为和多余一条语句的写法有冲突)

let getTempItem = id => ({ id: id, name: "Temp" });

箭头函数最大的好处就是简化代码

  • Class 的基本语法

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

es5 写法

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

ps :
定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错

使用方法:

class Bar {
  doStuff() {
    console.log('stuff');
  }
}

var b = new Bar();
b.doStuff() // "stuff"
  • Class 的继承

Class 可以通过extends关键字实现继承

class Point {
}

class ColorPoint extends Point {
  constructor (x, y){
      super(x, y)   // 调用父类的 constructor 如果子类有构造函数,就必须先调
用super(...)  this is 规定。
 }
}

为什么一定在 子类的 构造函数中使用super 呢?因为子类需要使用父类的构造函数,来生成自己的构造this 对象。
如果子类不需要构造函数, es6 也会默认存在一个自动调用 super的构造方法

class ColorPoint extends Point {
}

// 等同于
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}

在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    this.color = color; // ReferenceError
    super(x, y);
    this.color = color; // 正确
  }
}
  • 静态方法
    在类中使用 static 方法声明一个类的静态方法:
    静态方法和 py 这些其实是类似的,就是里面没办法使用this 关键字获取到实例或类的属性(ps py 函数里面只要使用是实例或者类属性,都要用 this 或者cls 来连接通道,这个要牢记)

super虽然代表了父类A的构造函数,但是返回的是子类B的实例
除了构造函数里面,使用 super 方法,其他地方则可以使用 super.xx 来直接复用 父类的属性和方法

还有一点就是子类中使用super .xx = xx 赋值 ,这时的super就和子类中的this 等价。但是作为读取属性值,依然是从父类中读取。(其实这里有点乱)

class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(super.x); // undefined
    console.log(this.x); // 3
  }
}

let b = new B();

上面的 super.x = 3 等价于 this.x = 3
但是读取的时候依然是 等价于直接从父类中读取
super.x 相当于 A.protopyte.x

  • 有一点,类属性可以 使用 classname.prototype.varname = .. 赋值
A.prototype.x = 2;  
A 是当前的类名。 
上面就在类A 中声明了一个叫做 x 的类属性,没有通过 this声明哦,通过this 声明的是实例属性了
  • 在静态方法中,子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。因为静态方法,不能使用到实例里面的东西。(好像这里的静态方法是类方法 ?)
  • Module 的语法

这个也很重要了
模块功能主要由 export import 组成

  • export 命令

一个独立的文件就是一个模块。要在外面使用它里面的东西,就需要export 出来。

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

或者这样:
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export { firstName, lastName, year };

一般采用第二种,因为一目了然  { 里面写变量名}
但是下面这是错的,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。



// 报错
export 1;

// 报错
var m = 1;
export m;     // 相当于直接导出 1 ,而没有和内部有一一对应的关系了。
但是加上 { xx }就是对的
这是正确的
export {m}

这样也是对的
export var m =1 ;


所以总结一下:
直接导出模块内的所有已经声明的变量,就必须加 { } 了。

  • 给导出的变量 换个对外名字

使用 export { xx as xxx} 那么外面是别的变量名就是xxx 了。

  • export 导出的是动态数据

其实你导出的所有数据,变量也好,外面只是使用它而已。在这个模块内部,你依然可以动态修改她,这样外面拿到的变量就是动态变化的了,有点可变类型的感觉,因为模块本身就是一个 大的对象,里面的东西都是这个对象里面的key 和value(这厮我的看法,这样好理解一点)

export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。

export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);

上面代码输出变量foo,值为bar,500 毫秒之后变成baz。
就是说,你在import foo 的模块里面使用500ms 后使用 foo,他的值就是 baz 了。这样的好处就是模块隔离够彻底。ლ(′◉❥◉`ლ)每一个 模块都完成自己的特定的功能。

export 可以出现在模块的最外层就可以,最上面或者最下面,都是最好的选择。(但是不能放在代码块里面)
下导出机会报错:

function foo() {
  export default 'bar' // SyntaxError
}
foo()
直接报错 了
  • import 命令

讲完了 export ,下面说说 import
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。

// main.js
import { firstName, lastName, year } from './profile.js';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

上面代码的import命令,用于加载profile.js文件,并从中输入变量。import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。

同样这里可以使用 as 重命变量

  • import 的东西都是只读的
import {a} from './xxx.js'

a = {}; // Syntax Error : 'a' is read-only;
重新赋值就会报错

但是如果a是一个对象,改写a的属性是允许的

// xxx.js
export var a = {"foo":xx}
//////////////
import {a} from './xxx.js'

a.foo = 'hello'; // 合法操作
但是建议凡是输入的变量,都当作完全只读,因为在其他模块改本模块的东西,会增加耦合性

  • import 路径
    后面的from 后面写文件路径 相对路径或者绝对路径都可以, 同时可以省略文件后缀 .js
    注意一点 import 的位置在那里,都默认首先执行。所以你把使用变量放到第一行也不会报错啦
foo();

import { foo } from 'my_module';
  • 模块的整体加载
// circle.js
export {foo,bar}
// 导入
import * as circle from './circle';
使用可以
circle.foo   
circle.bzr 


  • export default 命令

// export-default.js
export default function () {
  console.log('foo');
}

使用 (不加 { } 的就默认defalut 的导出)

// import-default.js
import customName from './export-default';
customName(); // 'foo'

  • export default命令只能使用一次,只能导出一个 default ,有点类似main 函数的意思,简化程序接口给使用者
  • 动态的模块路径 import()

这个用过,动态导入模块 里面是模块路径
import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。

import('./myModule.js')
.then(({export1, export2}) => {
  // ...·
});
  • Module 的加载实现

参考,就不详细描述了
http://es6.ruanyifeng.com/#docs/module-loader

你可能感兴趣的:(js es6 语法 通读)