美文网首页
ES6基础内容概览

ES6基础内容概览

作者: 2Youngg | 来源:发表于2017-04-02 17:02 被阅读0次

    最近在学习React,示例代码都由ES6所书写,所以对于ES6,不得不好好研究一下新的语法。
    这篇文章就对自己现在经常遇到的一些ES6语法进行了一个梳理。

    let, const

    这两个的用途与var类似,都是用来声明变量的,但在实际运用中他俩都有各自的特殊用途。
    首先来看下面这个例子:

    var name = 'zach'

    while (true) {
        var name = 'obama'
        console.log(name)  //obama
        break
    }
    
    console.log(name)  //obama
    

    使用var两次输出都是obama,这是因为ES5只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。第一种场景就是你现在看到的内层变量覆盖外层变量。而let则实际上为JavaScript新增了块级作用域。用它所声明的变量,只在let命令所在的代码块内有效。

    let name = 'zach'
    
    while (true) {
        let name = 'obama'
        console.log(name)  //obama
        break
    }
    
    console.log(name)  //zach
    

    另外一个var带来的不合理场景就是用来计数的循环变量泄露为全局变量,看下面的例子:

    var a = [];
    for (var i = 0; i < 10; i++) {
      a[i] = function () {
        console.log(i);
      };
    }
    a[6](); // 10
    

    上面代码中,变量i是var声明的,在全局范围内都有效。所以每一次循环,新的i值都会覆盖旧值,导致最后输出的是最后一轮的i的值。而使用let则不会出现这个问题。

    var a = [];
    for (let i = 0; i < 10; i++) {
      a[i] = function () {
        console.log(i);
      };
    }
    a[6](); // 6
    

    再来看一个更常见的例子,了解下如果不用ES6,而用闭包如何解决这个问题。

    var clickBoxs = document.querySelectorAll('.clickBox')
    for (var i = 0; i < clickBoxs.length; i++){
        clickBoxs[i].onclick = function(){
            console.log(i)
        }
    }
    

    我们本来希望的是点击不同的clickBox,显示不同的i,但事实是无论我们点击哪个clickBox,输出的都是5。下面我们来看下,如何用闭包搞定它。

    function iteratorFactory(i){
        var onclick = function(e){
            console.log(i)
        }
        return onclick;
    }
    var clickBoxs = document.querySelectorAll('.clickBox')
    for (var i = 0; i < clickBoxs.length; i++){
        clickBoxs[i].onclick = iteratorFactory(i)
    }
    

    const也用来声明变量,但是声明的是常量。一旦声明,常量的值就不能改变。

    const PI = Math.PI
    
    PI = 23 //Module build failed: SyntaxError: /es6/app.js: "PI" is read-only
    

    当我们尝试去改变用const声明的常量时,浏览器就会报错。
    const有一个很好的应用场景,就是当我们引用第三方库的时声明的变量,用const来声明可以避免未来不小心重命名而导致出现bug:

    const monent = require('moment')
    

    class, extends, super,constructor

    这三个特性涉及了ES5中最令人头疼的的几个部分:原型、构造函数,继承...你还在为它们复杂难懂的语法而烦恼吗?你还在为指针到底指向哪里而纠结万分吗?

    有了ES6我们不再烦恼!

    ES6提供了更接近传统语言的写法,引入了Class(类)这个概念。新的class写法让对象原型的写法更加清晰、更像面向对象编程的语法,也更加通俗易懂。

    class Animal {
        constructor(){
            this.type = 'animal'
        }
        says(say){
            console.log(this.type + ' says ' + say)
        }
    }
    
    let animal = new Animal()
    animal.says('hello') //animal says hello
    
    class Cat extends Animal {
        constructor(){
            super()
            this.type = 'cat'
        }
    }
    
    let cat = new Cat()
    cat.says('hello') //cat says hello
    

    上面代码首先用class定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。简单地说,constructor内定义的方法和属性是实例对象自己的,而constructor外定义的方法和属性则是所有实力对象可以共享的。

    Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。上面定义了一个Cat类,该类通过extends关键字,继承了Animal类的所有属性和方法。

    super关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

    ES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

    P.S 如果你写react的话,就会发现以上三个东西在最新版React中出现得很多。创建的每个component都是一个继承React.Component的类。

    arrow function

    这个恐怕是ES6最最常用的一个新特性了,用它来写function比原来的写法要简洁清晰很多。

    function(i){ return i + 1; } //ES5
    (i) => i + 1 //ES6
    

    简直是简单的不像话对吧...
    如果方程比较复杂,则需要用{}把代码包起来:

    function(x, y) { 
        x++;
        y--;
        return x + y;
    }
    (x, y) => {x++; y--; return x+y}
    

    除了看上去更简洁以外,arrow function还有一项超级无敌的功能!
    长期以来,JavaScript语言的this对象一直是一个令人头痛的问题,在对象方法中使用this,必须非常小心。例如:

    class Animal {
        constructor(){
            this.type = 'animal'
        }
        says(say){
            setTimeout(function(){
                console.log(this.type + ' says ' + say)
            }, 1000)
        }
    }
    
     var animal = new Animal()
     animal.says('hi')  //undefined says hi
    

    运行上面的代码会报错,这是因为setTimeout中的this指向的是全局对象。所以为了让它能够正确的运行,传统的解决方法有两种:

    1.第一种是将this传给self,再用self来指代this

     says(say){
         var self = this;
         setTimeout(function(){
             console.log(self.type + ' says ' + say)
         }, 1000)
    

    2.第二种方法是用bind(this),即

    says(say){
         setTimeout(function(){
             console.log(self.type + ' says ' + say)
         }.bind(this), 1000)
    

    但现在我们有了箭头函数,就不需要这么麻烦了:

    class Animal {
        constructor(){
            this.type = 'animal'
        }
        says(say){
            setTimeout( () => {
                console.log(this.type + ' says ' + say)
            }, 1000)
        }
    }
     var animal = new Animal()
     animal.says('hi')  //animal says hi
    

    当我们使用箭头函数时,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
    并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this。

    template string

    这个东西也是非常有用,当我们要插入大段的html内容到文档中时,传统的写法非常麻烦,所以之前我们通常会引用一些模板工具库,比如mustache等等。

    大家可以先看下面一段代码:

    $("#result").append(
      "There are <b>" + basket.count + "</b> " +
      "items in your basket, " +
      "<em>" + basket.onSale +
      "</em> are on sale!"
    );
    

    我们要用一堆的'+'号来连接文本与变量,而使用ES6的新特性模板字符串``后,我们可以直接这么来写:

    $("#result").append(`
      There are <b>${basket.count}</b> items
       in your basket, <em>${basket.onSale}</em>
      are on sale!
    `);
    

    用反引号(`)来标识起始,用${}来引用变量,而且所有的空格和缩进都会被保留在输出之中,是不是非常爽?!

    React Router从第1.0.3版开始也使用ES6语法了,比如这个例子:
    <Link to={/taco/${taco.name}}>{taco.name}</Link>

    destructuring

    ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

    看下面的例子:

    let cat = 'ken'
    let dog = 'lili'
    let zoo = {cat: cat, dog: dog}
    console.log(zoo)  //Object {cat: "ken", dog: "lili"}
    

    用ES6完全可以像下面这么写:

    let cat = 'ken'
    let dog = 'lili'
    let zoo = {cat, dog}
    console.log(zoo)  //Object {cat: "ken", dog: "lili"}
    

    反过来可以这么写:

    let dog = {type: 'animal', many: 2}
    let { type, many} = dog
    console.log(type, many)   //animal 2
    

    Set

    ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

    Set本身是一个构造函数,用来生成Set数据结构。

    一个Set是一群值的集合。它是可变的,能够增删元素。

    set 它和数组不同是不会包含相同元素。试图再次加入一个已有元素不会产生任何效果。

    new Set:创建一个新的、空的Set。

    new Set(iterable):从任何可遍历数据中提取元素,构造出一个新的集合。

    set.size:获取集合的大小,即其中元素的个数。

    set.has(value):判定集合中是否含有指定元素,返回一个布尔值。

    set.add(value):添加元素。如果与已有重复,则不产生效果。

    set.delete(value):删除元素。如果并不存在,则不产生效果。.add()和.delete()都会返回集合自身,所以我们可以用链式语法。

    _ setSymbol.iterator:返回一个新的遍历整个集合的迭代器。一般这个方法不会被直接调用,因为实际上就是它使集合能够被遍历,也就是说,我们可以直接写for (v of set) {...}等等。

    set.forEach(f):类似于数组的.forEach()方法。 直接上代码。

    for (let value of set) { f(value, value, set); }
    

    set.clear():清空集合。

    set.keys()、set.values()和set.entries()返回各种迭代器,它们是为了兼容Map而提供的.

    var s = new Set();
    
    [2,3,5,4,5,2,2].map(x => s.add(x))
    
    for (i of s) {console.log(i)}
    // 2 3 5 4
    

    上面代码通过add方法向Set结构加入成员,结果表明Set结构不会添加重复的值。

    > arrayOfWords[15000] 
        "anapanapa" 
    > setOfWords[15000]    
        undefined
    

    Set不支持索引

    arr.indexOf('a') !== -1 //慢
    //true
    setOfWords.has('a') //快 
    //true
    

    Set的数据存储结构专门为一种操作作了速度优化:包含性检测。

    Map

    一个Map对象由若干键值对组成,支持:

    new Map:返回一个新的、空的Map。

    new Map(pairs):根据所含元素形如[key,value]的数组pairs来创建一个新的Map。这里提供的 pairs可以是一个已有的Map对象,可以是一个由二元数组组成的数组,也可以是逐个生成二元数组的一个生成器,等等。

    map.size:返回Map中项目的个数。

    map.has(key):测试一个键名是否存在,类似key in obj。

    map.get(key):返回一个键名对应的值,若键名不存在则返回undefined,类似obj[key]。

    map.set(key, value):添加一对新的键值对,如果键名已存在就覆盖。

    map.delete(key):按键名删除一项,类似delete obj[key]。

    map.clear():清空Map。

    mapSymbol.iterator:返回遍历所有项的迭代器,每项用一个键和值组成的二元数组表示。

    map.forEach(f) 类似 for (let [key, value] of map) { f(value, key, map); } 。 这 里 诡 异 的 参 数 顺 序 , 和 Set 中 一 样 , 是 对 应 着数组的forEach()。

    map.keys():返回遍历所有键的迭代器。

    map.values():返回遍历所有值的迭代器。

    map.entries():返回遍历所有项的迭代器,就像mapSymbol.iterator。实际上,它们就是同一个方法,不同名字。
    先从书上把map的api记下来,

    Map数据结构类似于对象,同样是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。

    var m = new Map();
    var o = {p: "Hello World"};
    m.set(o, "content")
    m.get(o) // "content"
    m.has(o) // true
    m.delete(o) // true
    m.has(o) // false
    

    作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。(感觉真强大)

    var map = new Map([["name", "张三"], ["title", "Author"]]);
    map.size // 2
    map.has("name") // true
    map.get("name") // "张三"
    map.has("title") // true
    map.get("title") // "Author"
    

    上面代码在新建Map实例时,就指定了两个键name
    和title。

    * 注意,只有对同一个对象的引用,Map结构才将其视为同一个键。这一点要非常小心。

    var map = new Map();
    map.set(['a'], 555);
    map.get(['a']) // undefined
    
    let b = ['b'];
    map.set(b, 555);
    map.get(b) // 555
    

    上面代码的set和get方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此get方法无法读取该键,返回undefined。
    这个也比较好理解,因为这两个['a']是两个不同的数组对象。

    有一个坏处。 Map和Set都为内部的每个键或值保持了强引用,也就是说,如果一个 DOM 元素被移除了,回收机制无法取回它占用的内存,除非 movingSet中也删除了它。在最理想的情况下,库在善后工作上对使用者都有复杂的要求,所以,这很可能引发内存泄露。

    所已有了 WeakSet

    WeakSet与Set有两个区别:

    WeakSet的成员只能是对象,而不能是其他类型的值。
    WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。

    var ws = new WeakSet();
    ws.add(1)
    // TypeError: Invalid value used in weak set
    ws.add(Symbol())
    // TypeError: invalid value used in weak set
    

    上面代码试图向WeakSet添加一个数值和Symbol值,结果报错。

    WeakSet结构有以下三个方法。

    WeakSet.prototype.add(value):向WeakSet实例添加一个新成员。

    WeakSet.prototype.delete(value):清除WeakSet实例的指定成员。

    WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在WeakSet实例之中。

    下面是一个例子。

    var ws = new WeakSet();
    var obj = {};
    var foo = {};
    
    ws.add(window);
    ws.add(obj);
    
    ws.has(window); // true
    ws.has(foo);    // false
    
    ws.delete(window);
    ws.has(window);    // false
    WeakSet没有size属性,没有办法遍历它的成员。
    ws.size // undefined
    ws.forEach // undefined
    
    ws.forEach(function(item){ console.log('WeakSet has ' + item)})
    // TypeError: undefined is not a function
    

    上面代码试图获取size和forEach属性,结果都不能成功。

    WeakSet不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet的一个用处,是储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏。

    著作权声明

    本文参考资料 ES6学习笔记 30分钟掌握ES6/ES2015核心内容
    著者 2Youngg,转载请保留以上链接

    相关文章

      网友评论

          本文标题:ES6基础内容概览

          本文链接:https://www.haomeiwen.com/subject/qgpgottx.html