卧槽,我的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