es6 初步深刻学习

es6初步深刻学习

es6前言(只做了解就好)

ECMAScript和JavaScript的关系

ECMAScript和JavaScript的关系是,前者是后者的规格,后者是前者的一种实现(另外的ECMAScript方言还有Jscript和ActionScript)。vue

检测node环境对ES6的支持状况

阮一峰写了一个ES-Checker模块,用来检查各类运行环境对ES6的支持状况。运行下面的命令,能够查看你正在使用的Node环境对ES6的支持程度。node

sudo npm i -g es-checker
    es-checker

babel转码器

ES6转码器,能够将ES6代码转为ES5代码,从而在现有环境执行。es6在线编辑器/在线转码器react

babel stage

在es6标准出来以前,你们都会参与讨论,把各自以为好的语法加进去,便有了不少阶段(准标准、草案、提案..)。stage-0是一个提案,里面基本包括你们讨论的全部内容,stage-1 -- stage-3以此类推,stage-3基本肯定要进入es6了。因此使用webpack的同窗会看到,安装的依赖基本都是babel-preset-stage-0。webpack

babel-polyfill

本人以前开发中遇到过一个问题,array的includes方法(string也有),当时运行在chrome47环境下,整个页面打不开,报错(babel includes can not a function),在chrome52环境下支持。解决办法--安装babel-polyfill。最后在你的文件中import 'babel-polyfill'。es6

Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,好比Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(好比Object.assign)都不会转码。举例来讲,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。若是想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。web

let和const

let用于声明变量,const用于声明常量。可是聊这个以前,咱们仍是简单提提老生常谈的问题吧---scope。vuex

块级做用域

在es5中是不存在块级做用域的,通常咱们听得比较多的是函数做用域(定义在函数中的参数和变量在函数外部是不可见的)。chrome

function getVal(boo) {
        if (boo) {
            var val = 'red'
            // ...
            return val
        } else {
            // 这里能够访问 val
            return null
        }
        // 这里也能够访问 val
    }

那么在es5中如何使用块级做用域呢?js中在一个函数中定义的变量,当这个函数调用完后,变量会被销毁,咱们能够利用这种特性(闭包,the most important feature!!)。npm

function caniuse() { 
      for(var i=0;i<10;i++){} 
      console.log(i); //10
    } 
    caniuse();
    
    function caniuse2() {
      (function() {
        for(var i=0;i<10;i++){}
      })()
      console.log(i) //i is not defined
    }
    caniuse2()

在es6中,任何一对花括号中(for,if)的语句集都属于一个块,在这之中定义的全部变量在代码块外都是不可见的,咱们称之为块级做用域。redux

function getVal(boo) {
        if (boo) {
            let val = 'red'
            // ...
            return val
        } else {
            // 这里访问不到 val
            return null
        }
        // 这里也访问不到 val
    }

有了es6,上面闭包的写法咱们用es6能够不须要用到当即执行函数。

for(let i = 0; i < 10; i++){}

let

let声明的变量只在他的代码块内有效

{
      let i = 9;
      console.log(i) //9
    }
    console.log(i) //ReferenceError: i is not defined
function test1() {
      for(var i = 0; i < 10; i++) {
        setTimeout(function() {
          console.log(i) //10次10
        }, 1000)
      }
    }
    
    function test2() {
      for(let i = 0; i < 10; i++) {
        setTimeout(function() {
          console.log(i) //0-9
        }, 1000)
      }
    }
    
    function test3() {
      for(var i = 0; i < 10; i++) {
        (function(i) {
          setTimeout(function() {
            console.log(i) //0-9
          }, 100)
        })(i)
      }
    }
    
    
    test1()
    test2()
    test3()

不存在变量提高

脚本开始运行时,变量i已经存在了,可是没有值,因此会输出undefined。变量ii用let命令声明,不会发生变量提高。这表示在声明它以前,变量ii是不存在的,这时若是用到它,就会抛出一个错误。

console.log(i) //undefined
    console.log(ii) //ReferenceError: ii is not defined
    var i;
    let ii;

还有一些比较不容易发现的暂时性死区

function test1(y = 1, x = y) {
      console.log(x, y) // 1 1
    }
    
    function test2(x = y, y = 1) {
      console.log(x, y) //error
    }
    
    test1()
    test2()

不容许重复声明

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

const

  • 用const声明,常量的值就不能改变。

  • const一旦声明变量,就必须当即初始化,不能留到之后赋值。

  • 只所在的块级做用域内有效。(见let)

  • 不能变量提高,存在暂时性死区,只能在声明的位置后面使用。(见let)

  • 在一个scope中不可重复declare。

const foo; // SyntaxError: Missing initializer in const declaration
  • 对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,因此将一个对象声明为常量必须很是当心。

const foo = {};
    foo.prop = 123;
    
    foo.prop
    // 123
    
    foo = {}; // TypeError: "foo" is read-only
    
    const a = [];
    a.push('Hello'); // 可执行
    a.length = 0;    // 可执行
    a = ['Dave'];    // 报错
    
    const foo = Object.freeze({});

    // 常规模式时,下面一行不起做用;
    // 严格模式时,该行会报错
    foo.prop = 123;

小结

ES5只有两种声明变量的方法:var命令和function命令。ES6除了添加let和const命令,另外两种声明变量的方法:import命令和class命令。因此,ES6一共有6种声明变量的方法。

Iterator(简单说下,下次再仔细补充)

有三类数据结构原生具有Iterator接口:数组、某些相似数组的对象、Set和Map结构。Iterator是一种机制,为不一样的数据结构提供可遍历操做。可遍历结构,个人理解是能够执行for...of。

Iterator做用:

  • 为各类数据结构,提供一个统一的、简便的访问接口

  • 使得数据结构的成员可以按某种次序排列

  • ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。

Iterator的遍历过程

var it = makeIterator(['a', 'b']);
    
    it.next() // { value: "a", done: false }
    it.next() // { value: "b", done: false }
    it.next() // { value: undefined, done: true }
    
    function makeIterator(array) {
      var nextIndex = 0;
      return {
        next: function() {
          return nextIndex < array.length ?
            {value: array[nextIndex++]} :
            {done: true};
        }
      };
    }

for...of

let arr = [2,3,4,,7,8]
    for(let val of arr) {
      console.log(val) //2,3,4,undefined,7,8
    }
    for(let k in arr) {
      console.log(k) //"0" "1" "2" "4" "5",下标都是字符串哦
    }
    console.log(arr["2"]) //4
    
    let arr2 = {0: 'a', 1: 'b', 3: 'c'}
    for(let key of arr2) {
      console.log(key, arr2[key]) //arr2[Symbol.iterator] is not a function
    }
    for(let key in arr2) {
      console.log(key, arr2[key]) //"0" "a"
                                  //"1" "b"
                                  //"3" "c"
    }

Iterator的使用场合

  • 解构赋值(下一章)

  • 扩展运算符(spread, 方便,使用频率很高。。。spread arguments in function)。

let [a, ...c] = [3, 5, 6, 7, 7, 9]
    console.log(a) //3
    console.log(c) //[5, 6, 7, 7, 9]
    
    let [e, ...f, g] = [3, 5, 6, 7, 7, 9]
    console.log(e) //3
    console.log(f) //[5, 6, 7, 7, 9]
    console.log(g) //error
    
    var str = 'hello';
    [...str] //  ['h','e','l','l','o']
    
    let arr = ['b', 'c'];
    ['a', ...arr, 'd'] // ['a', 'b', 'c', 'd']
  • yield*

let generator = function* () {
      yield 1;
      yield* [2, 4];
      yield 5
    }
    let iterator = generator()
    
    console.log(iterator.next()) //{"done": false, "value": 1}
    console.log(iterator.next()) //{"done": false, "value": 2}
    console.log(iterator.next()) //{"done": false, "value": 4}
    console.log(iterator.next()) //{"done": true, "value": 5}
  • 其余(for...of, Array.form(), Map(), Set())

变量的解构赋值(Destructuring)

解构赋值——个人字面理解是先分解结构,再匹配等号两边相对应的结构,若是解构成功给每一个相对应地变量赋值,通常状况下如若不成功,就给对应的变量赋值undefined。

模式匹配

只要等号两边的模式相同,解构彻底匹配。左边的变量就会被赋予对应的值。

let [a, ...c] = [3, 5, 6, 7, 7, 9]
    console.log(a) //3
    console.log(c) //[5, 6, 7, 7, 9]
    
    let [x, y, ...z] = ['a'];
    x // "a"
    y // undefined
    z // []

不彻底解构

等号右边的解构匹配左边的,可是还有多余的值。若是右边不具有Iterator接口,解构的过程当中会报错。

let [a, [b], d] = [1, [2, 3], 4];
    a // 1
    b // 2
    d // 4
    
    let [foo] = 1;
    let [foo] = false;
    let [foo] = NaN;
    let [foo] = undefined;
    let [foo] = null;
    let [foo] = {};

默认值

以前写redux的时候,常常会用到默认值。

export function receiveCustomers({source=null, origin=null, start_at=null, end_at=null, keyword=null}) {
      return {
        type: types.CUSTOMERS_QUERY_CHANGE,
        source,
        origin,
        start_at,
        end_at,
        keyword
      }
    }
  • null/undefined

var [x = 1] = [undefined];
    x // 1
    
    var [x = 1] = [null];
    x // null
  • function

function aaa() {console.log(1)}
    let [a = aaa()] = [1] //a=1
  • 变量不能提高

let [x = y, y = 1] = [];     // ReferenceError

对象解构

这个灰经常用。通常用法就行,不须要很怪异的写法,可读性差。总结几点注意事项:

  • 若是要将一个已经声明的变量用于解构赋值,必须很是当心。

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

// 错误的写法
    var x;
    {x} = {x: 1};
    // SyntaxError: syntax error
    
    var {x = 3} = {x: undefined};
    x // 3
    
    var {x = 3} = {x: null};
    x // null

对象的扩展

属性的简洁表示

这个比较经常使用,以前写vuex的时候有使用到。用过vuex/redux的同窗应该不会陌生

import {fn1, fn2} from 'action'
    //action.js里面有两个方法fn1,fn2,最后 export {fn1,fn2}
    //等同于export{fn1: fn1, fn2: fn2}
    
    const { dispatch } = this.props
    //等同于const dispatch = this.props.dispatch
    
    vuex: {
        actions: {
            fn1,
            fn2
        }
    }
    //等同于
    vuex: {
        actions: {
            fn1: fn1,
            fn2: fn2
        }
    }
function f(x, y) {
      return {x, y};
    }
    
    // 等同于
    
    function f(x, y) {
      return {x: x, y: y};
    }
    
    f(1, 2) // Object {x: 1, y: 2}

方法的简写

handleText = e => {
        this.setState({
          inputValue: e.target.value
        })
    };
    
    //等同于
    handleText: function(e) {
        ...
    }

Object.assign

deep clone and shallow clone

JavaScript存储对象都是存地址的,因此浅复制会致使 obj 和 obj1 指向同一块内存地址,大概的示意图以下。而深复制通常都是开辟一块新的内存地址,将原对象的各个属性逐个复制出去。

let obj = {0: "a", 1: "b", 2: "c"}

let deepObj = JSON.parse(JSON.stringify(obj))//is not a really deep clone, but it works.
let shallowObj = obj
obj[3] = 'd'

console.log(deepObj) //{0: "a", 1: "b", 2: "c"}
console.log(shallowObj) //{0: "a", 1: "b", 2: "c", 3: "d"}
console.log(obj) //{0: "a", 1: "b", 2: "c", 3: "d"}

JSON.parse(JSON.stringify(obj))这样的使用方式并非真正的深拷贝,由于它会丢失一些东西,一些obj的内在property之类的,
好比

obj_test1 = {
        a: 1,
        b: 2,
        get_a() {
          return this.a
        }
    }

这样的一个obj,你用上面的方式会丢掉get_a,若是你定义一些prototype,也会丢失掉,或者若是包含一些dom元素之类的。可是咱们多数时候只是用它来复制数据,不关心对象的方法之类的东西,这样的话是够用的。

常见做用

  • 对象拷贝(copy)

Object.assign方法实行的是浅拷贝,而不是深拷贝,若是源对象某个属性的值是对象,那么目标对象拷贝获得的是这个对象的引用。以前本身的一个疑惑

let obj = {0: "a", 1: "b", 2: "c"}

let objClone = Object.assign({}, obj)
obj[3] = 'd'
console.log(objClone) // {0: "a", 1: "b", 2: "c"} why the same, but not change

Javascript has five primitive data types:

  1. Number

  2. String

  3. Boolean

  4. Undefined

  5. Null

Anything that doesn’t belong to any of these five primitive types is considered an object.

primitive types are passed by value, while objects passed by reference.

a = {a: 1, b: 2, c: 3, d: {aa: 11, bb: 22}}
a1 = Object.assign({}, a, {a: 2})
a1.d.aa = 'i am shllow copy'
第二行是浅拷贝,a拷贝到a1了,而且把property a的值改为2了 (你以为a.a会变成2么,为何?)
第三行把a1.d这个object理得aa这个property改了 (你以为a.d.aa也会改么,为何?)

  • 合并对象 (merge)

Object.keys && Object.value

ES5引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)全部可遍历(enumerable)属性的key。Object.values返回value。

Class

不得不说,这是我从没仔细看过的一部分,写react的时候都是三板斧套路。。。
特地留意一下super()。ES5的继承,实质是先创造子类的实例对象this,而后再将父类的方法添加到this上面(Parent.apply(this))。es6中先创造父类的实例对象this(因此必须先调用super方法),而后再用子类的构造函数修改this。

class Teachers extends React.Component {
        constructor(props, context) {
            super(props, context)
        }
        componentDidMount(){
            const { siteId, dispatch } = this.props
            if (siteId) {
              dispatch(fetchTeachersList(siteId))
            }
        }
        
        componentWillReceiveProps(nextProps) {
            const { siteId, dispatch } = this.props
            if (nextProps.siteId !== siteId) {
              dispatch(fetchTeachersList(nextProps.siteId))
            }
        }
    }
    
    export default Teachers

搭配Object.assign()

class Point {
      constructor(){
        // ...
      }
    }
    
    Object.assign(Point.prototype, {
      toString(){},
      toValue(){}
    });

不存在变量提高

new Foo(); // ReferenceError
    class Foo {}

class表达式

Class表达式,能够写出当即执行的Class。

let person = new class {
      constructor(name) {
        this.name = name;
      }
    
      sayName() {
        console.log(this.name);
      }
    }('张三');
    
    person.sayName(); // "张三"

this指向和箭头函数

箭头函数很方便,解决了以前es5遗留下来的问题(继承,this做用域)。箭头函数没有它本身的this值,箭头函数内的this值继承自外围做用域。

//es5中, var self = this;这样的代码你确定常见,es6箭头函数中不用鸟
    var obj = {
      field: 'hello',
      getField: () => {
        console.log(this.field)
      }
    }
    obj.getField() // undefined,这时this是window,
    
    var obj = {
      field: 'hello',
      getField(){
        console.log(this.field)
      }
    }
    obj.getField() // hello,

本身经常使用的拓展

let arr = [1,2,3,4,5,6]
    let arr2 = arr.reduce( (init, prev, index) => {init.push(prev*prev); return init}, [] )
    console.log(arr2) //[1,4,9,16,25,36]

想起以前写的数组转对象的方法
clipboard.png

警告 yield 关键字一般不能在箭头函数中使用(except when permitted within functions further nested within it)。所以,箭头函数不能用做Generator函数。

Module

js历史上没有标准的模块化。在ES6以前,社区制定了一些模块加载方案,最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。可是这些都只能在运行时起做用,es6的模块化能够在编译时就能肯定模块的依赖关系,以及输入和输出的变量。

模块功能主要由两个命令构成:export和import。先抛出一个问题:如下两行代码有什么区别?

import {message, menu} from 'antd'
import message from 'antd/lib/message'

export

对外的接口名与模块内部变量的关系

export对外的接口名与模块内部变量之间,创建了一一对应的关系。当export一个function时,语句输出的接口,与其对应的值是动态绑定关系,即经过该接口,能够取到模块内部实时的值。

export 1; //error
export var m = 1 //right

var m = 1;
export m//error
export {m} //right

import

注意 import命令具备提高效果,会提高到整个模块的头部,首先执行。因此在文件中的任意位置,exort也同样,不过利于可读性,仍是import写在文件开头,export写在文件结尾好点。

注意 import命令接受一个对象(用大括号表示),里面指定要从其余模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。

import {message, menu} from 'antd'
import message from 'antd/lib/message'

因此这之间的区别就是前者中括号里的变量名要与export出的接口名一致,后者message是随意起的名字,能够不与export出来的变量名保持一致,它表明export default出来的接口。export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,所以export deault命令只能使用一次。因此,import命令后面才不用加大括号,由于只可能对应一个方法。本质上,export default就是输出一个叫作default的变量或方法,而后系统容许你为它取任意名字。

模块的总体加载

import * as circle from './circle';
    
    console.log('圆面积:' + circle.area(4));
    console.log('圆周长:' + circle.circumference(14));
相关文章
相关标签/搜索