安装babel
由于浏览器对ES6的支持还不是很好,编写ES6代码前我们要安装一个babel工具将ES6代码编译成ES5代码,用如下命令安装babel:
npm install -g babel-core //安装babel核心库
npm install -g babel-cli //安装babel命令行工具
安装成功后,我们将a.js的ES6代码编译为b.js的ES5代码的命令为:
babel a.js -o b.js
这里要注意由于babel最新版本还有对jsx,ES7等规范的支持,所以还要在当前目录新建一个.babelrc文件来指定当前使用JS的候选版本,其内容为:
{
"presets": ["es2015"] //指定当前JS版本为es2015
}
区块作用域let与常量const
ES6里用let声明具有区块作用域的变量,什么是区块作用域,学习过C,C++,Java等语言的同学都知道,如循环、if/else等、或用花括号代码块里声明的变量是具有作用域的,超出该范围即无法使用,js一直没有这个概念,ES6补上了这个缺口。
用const声明的常量无法在后面的代码中改值。
Spread表达式
ES6里还有一个很方便的就是spread表达式,举个例子:
function cook(dessert, ...drink) {
let temp = dessert;
for (let i = 0; i < drink.length; ++i) {
temp += ',' + drink[i];
}
console.log(temp);
}
cook('cake', 'juice', 'cola');
// 运行结果为 cake,juice,cola
...后声明的drink里以数组的形式存放了函数的剩余参数,是不是很方便。
1、新增let命令
-
与var不同的是:let声明的变量只在let命令所在的代码块内有效。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
有一种情况很适合用let命令:for循环的计数器。
for(let i =0; i<arr.length; i++){
}
console.log(i) //ReferenceError: i is not defined
下面就用一个例子来比较一下let和var在for循环计数器的不同,为什么let更适合:
var a = [];
for(var i = 0; i < 8; i++){
a[i] = function(){
console.log(i);
}
}
a[5](); // 8
var a = [];
for(let i = 0; i < 8; i++){
a[i] = function(){
console.log(i);
}
}
a[5](); // 5
2、只要块级作用域内存在let命令,那么它所声明的变量就不再受外部的影响,形成了封闭作用域
if (true){
//封闭作用域开始
tmp = 'abc'; //ReferenceError
console.log(tmp); //ReferenceError
let tmp;
//封闭作用域结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); //123
}
在let命令声明变量tmp之前,都属于变量tmp的"死区"。
-
此处需要注意一下:
函数的作用域是其声明时所在的作用域。
来个例子思考一下:
let foo = 'outer';
function bar(func = x => foo){
let foo = 'inner';
console.log(func()); // outer
}
bar();
func 默认是一个匿名函数,返回值为foo,作为bar函数的参数。func的作用域不是bar,func声明是在外层作用域,所以bar内部的foo指向函数体外的foo的值,这段代码等同于下面这段代码:
let foo = 'outer'
let f = x => foo;
function bar(func = f){
let foo = 'inner';
console.log(func()); //outer
}
bar();
总之,暂时性死去的本质就是:只要已进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
3、新增块级作用域
ES5只有全局作用域和函数作用域,这样会产生很多不合理的场景。
第一种场景:内层变量可能会覆盖外层变量
var tmp = "hello";
(function(){
console.log(tmp);
var tmp = "world";
})(); //undefined
思考一下,为什么结果会是undefined呢?
-
第一种情况可能就是函数内的tmp变量覆盖了全局tmp,如果去掉function我们看看变量的结果:
var tmp = "hello";
if(true){
console.log(tmp);
var tmp = "world";
} // hello
-
第二种情况就是存在变量提升,这又是怎么回事呢,也就是说最初的代码等同于下面这段代码:
var tmp = "hello";
(function(){
var tmp; //变量提升
console.log(tmp);
tmp = "world";
})(); //undefined
第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello';
for (var i = 0; i < s.length; i++){
console.log(s[i]);
}
console.log(i); // 5
变量i只是用来控制循环的,循环结束它就没有用了,但是却泄露成了全局变量,这个很不好。
可以说let为js增加了块级作用域,来看下面这段代码吧:
function a(){
let s = 1;
if(true){
let s = 3;
}
console.log(s); //1
}
function a(){
var s = 1;
if(true){
var s = 3;
}
console.log(s); //3
}
let 声明的变量,使外层的代码不受内层的代码影响,而var则没有这样的效果。
ES6允许块级作用域的任意嵌套,并且外层作用域无法读取内层作用域的变量
{{{{
{let s = 'Hello World'}
console.log(s); // 报错
}}}};
内层作用域可以定义外层作用域的同名变量
{{{{
let s = 'Hello World';
{let s = 'Hello World';}
}}}};
块级作用域可以代替立即执行函数
//立即执行函数
(function(){
var tmp = ...;
...
}());
//块级作用域
{
let tmp = ...;
...
}
ES6中函数本身的作用域在其所在的块级作用域之内
function f() { console.log('I am outside!'); }
(function () {
if(false) {
function f() { console.log('I am inside!'); }
}
f();
}());
ES5中的运行结果:I am inside!
ES6中的运行结果:I am outside!
造成这样结果的原因就是:
-
在ES5中存在变量提升,不论会不会进入if代码块,函数声明都会提升到当前作用域的顶部得到执行.
-
在ES6中支持块级作用域,不论会不会进入if代码块,其内部声明的函数皆不会影响到作用域的外部
{
let a = 'hello';
function b(){
return a;
}
}
f(); //报错
因为函数的调用在块级作用域的外部,所以无法调用块级作用域内部定义的函数。
let b;
{
let a = 'world';
b = function () {
return a;
}
}
b() // "world"
如果代码进行上面这样的处理,即把函数b的声明与函数调用放在同一块级作用域就可以了。
注:如果在严格模式下,函数只能在顶层作用域和函数内声明,其他情况(比如if代码块、循环代码块)的声明都会报错。
箭头函数
语法
-
具有一个参数的简单函数
var single = a => a single('hello, world') // 'hello, world'
-
没有参数的需要用在箭头前加上小括号
var log = () => { alert('no param') }
-
多个参数需要用到小括号,参数间逗号间隔,例如两个数字相加
var add = (a, b) => a + b add(3, 8) // 11
-
函数体多条语句需要用到大括号
var add = (a, b) => { if (typeof a == 'number' && typeof b == 'number') { return a + b } else { return 0 } }
-
返回对象时需要用小括号包起来,因为大括号被占用解释为代码块了
var getHash = arr => { // ... return ({ name: 'Jack', age: 33 }) }
-
直接作为事件handler
document.addEventListener('click', ev => { console.log(ev) })
-
作为数组排序回调
var arr = [1, 9 , 2, 4, 3, 8].sort((a, b) => { if (a - b > 0 ) { return 1 } else { return -1 } }) arr // [1, 2, 3, 4, 8, 9]
特性
-
this:用function生成的函数会定义一个自己的this,而箭头函数没有自己的this,而是会和上一层的作用域共享this。
-
apply & call:由于箭头函数已经绑定了this的值,即使使用apply或者call也不能只能起到传参数的作用,并不能强行改变箭头函数里的this。
-
arguments:普通函数里arguments代表了调用时传入的参数,但是箭头函数不然,箭头函数会把arguments当成一个普通的变量,顺着作用域链由内而外地查询。
-
不能被new:箭头函数不能与new关键字一起使用,会报错。
-
typeof运算符和普通的function一样:
var func = a => a console.log(typeof func); // "function"
-
instanceof也返回true,表明也是Function的实例:
co
ECMAScript 6 的新特性
箭头(Arrow)
=>
是function的简写形式,支持expression
和 statement
两种形式。同时一点很重要的是它拥有词法作用域的this值,帮你很好的解决this的指向问题,这是一个很酷的方式,可以帮你减少一些代码的编写,先来看看它的语法。
([param] [, param]) => {
statements
}
param => expression
然后再来看看例子,以及babel 编译后的结果。
ES6:
babel编译后结果:
类(class)
ES6 引入了class(类),让javascript的面向对象编程变得更加容易清晰和容易理解。类只是基于原型的面向对象模式的语法糖。
class Animal {
// 构造方法,实例化的时候将会被调用,如果不指定,那么会有一个不带参数的默认构造函数.
constructor(name,color) {
this.name = name;
this.color = color;
}
// toString 是原型对象上的属性
toString() {
console.log('name:' + this.name + ',color:' + this.color);
}
}
var animal = new Animal('dog','white');
animal.toString();
console.log(animal.hasOwnProperty('name')); //true
console.log(animal.hasOwnProperty('toString')); // false
console.log(animal.__proto__.hasOwnProperty('toString')); // true
class Cat extends Animal {
constructor(action) {
// 子类必须要在constructor中指定super 方法,否则在新建实例的时候会报错.
// 如果没有置顶consructor,默认带super方法的constructor将会被添加、
super('cat','white');
this.action = action;
}
toString() {
console.log(super.toString());
}
}
var cat = new Cat('catch')
cat.toString();
// 实例cat 是 Cat 和 Animal 的实例,和Es5完全一致。
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true
类的 prototype
属性和 __proto__
属性
在上一篇 javascript面向对象编程 中我们已经了解到一个实例化对象会有一个 __proto__
指向构造函数的 prototype
属性。在class
中。同时具有 __proto__
和 prototype
两个属性,存在两条继承链。
-
子类的
__proto__
属性,表示构造函数的继承,总是指向父类。 -
子类的 prototype 的
__proto__
属性表示方法的继承,总是指向父类的prototype
属性。
class Cat extends Animal {}
console.log(Cat.__proto__ === Animal); // true
console.log(Cat.prototype.__proto__ === Animal.prototype); // true
我们先来看第一条 Cat.__proto__ === Animal
这条原型链。完成构造函数继承的实质如下:
class Cat extends Animal {
construcotr() {
return Animal.__proto__.call(this);
}
}
第二条对原型链 Cat.prototype.__proto__ === Animal.prototype
完成方法的继承,实质如下:
Cat.prototype.__proto__ = Animal.prototype
另外还有还有三种特殊情况。
class A extends Object {}
console.log(A.__proto__ === Object); // true
console.log(A.prototype.__proto__ === Object.prototype);
A继承Object,A的__prototype__ 指向父类Object. A的 prototype.__proto__
指向父类Object的prototype。
从上篇文章中的 函数对象的原型 中我们可以了解到,函数是一种特殊的对象,所有函数都是 Function
的实例。
class Cat {}
console.log(Cat.__proto__ === Function.prototype); //true
console.log(Cat.prototype.__proto__ === Object.prototype); //true
由于Cat不存在任何继承,就相当于一个普通函数,由于函数都是Function
的实例,所以 Cat.__proto__
指向 Function.prototype. 第二条继承链指向父类(Function.prototype) 的prototype属性,所以 Cat.prototype.__proto__ === Object.prototype
. Cat调用后会返回Object实例,所以 A.prototype.__proto__
指向构造函数(Object)的prototype。
class Cat extends null {};
console.log(Cat.__proto__ === Function.prototype); // true;
console.log(Cat.prototype.__proto__ === null); //true
Cat是一个普通函数,所以继承 Function.prototype
.第二条继承链不继承任何方法,所以 Cat.prototype.__proto__ == null
.
Module
到目前为止,javascript (ES5及以前) 还不能支持原生的模块化,大多数的解决方案都是通过引用外部的库来实现模块化。比如 遵循CMD规范的 Seajs 和AMD的 RequireJS 。在ES6中,模块将作为重要的组成部分被添加进来。模块的功能主要由 export
和 import
组成.每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export
来规定模块对外暴露的接口,通过import
来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。
export,import 命令
//test.js
export var name = 'Rainbow'
ES6将一个文件视为一个模块,上面的模块通过 export
向外输出了一个变量。一个模块也可以同时往外面输出多个变量。
//test.js
var name = 'Rainbow';
var age = '24';
export {name, age};
定义好模块的输出以后就可以在另外一个模块通过import
引用。
//index.js
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 } fro