ES6入门笔记(一)

安装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代码块、循环代码块)的声明都会报错。

箭头函数

语法

  1. 具有一个参数的简单函数

    var single = a => a
    single('hello, world') // 'hello, world'
  2. 没有参数的需要用在箭头前加上小括号

    var log = () => {
        alert('no param')
    }
  3. 多个参数需要用到小括号,参数间逗号间隔,例如两个数字相加

    var add = (a, b) => a + b
    add(3, 8) // 11
  4. 函数体多条语句需要用到大括号

    var add = (a, b) => {
        if (typeof a == 'number' && typeof b == 'number') {
            return a + b
        } else {
            return 0
        }
    }
  5. 返回对象时需要用小括号包起来,因为大括号被占用解释为代码块了

    var getHash = arr => {
        // ...
        return ({
            name: 'Jack',
            age: 33
        })
    }
  6. 直接作为事件handler

    document.addEventListener('click', ev => {
        console.log(ev)
    })
  7. 作为数组排序回调

    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]

特性

  1. this:用function生成的函数会定义一个自己的this,而箭头函数没有自己的this,而是会和上一层的作用域共享this。

  2. apply & call:由于箭头函数已经绑定了this的值,即使使用apply或者call也不能只能起到传参数的作用,并不能强行改变箭头函数里的this。

  3. arguments:普通函数里arguments代表了调用时传入的参数,但是箭头函数不然,箭头函数会把arguments当成一个普通的变量,顺着作用域链由内而外地查询。

  4. 不能被new:箭头函数不能与new关键字一起使用,会报错。

  5. typeof运算符和普通的function一样:

    var func = a => a
    console.log(typeof func); // "function"
  6. 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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值