运用ES6写更好的JavaScript

运用 ES6 写更好的 JavaScript part I:广受欢迎新特征

引见

在ES2015范例敲定而且Node.js增添了大批的函数式子集的背景下,我们终究可以拍着胸脯说:将来就在面前。

… 我早就想如许说了

但这是真的。V8引擎将很快完成范例,而且Node已增加了大批可用于临盆环境的ES2015特征。下面要列出的是一些我以为很有必要的特征,而且这些特征是不运用须要像Babel也许Traceur如许的翻译器就可以直接运用的。

这篇文章将会讲到三个相称盛行的ES2015特征,而且已在Node中支撑了了:

  • 用let和const声明块级作用域;

  • 箭头函数;

  • 简写属性和要领。

让我们立时最先。

let和const声明块级作用域

作用域是你递次中变量可见的地区。换句话说就是一系列的划定规矩,它们决议了你声明的变量在那里是可以运用的。

人人应当都听过 ,在JavaScript中只需在函数内部才会制作新的作用域。然则你建立的98%的作用域事实上都是函数作用域,实在在JavaScript中有三种建立新作用域的要领。你可以如许:

  1. 建立一个函数。你应当已晓得这类体式格局。

  2. 建立一个catch块。 我相对没哟开顽笑.

  3. 建立一个代码块。假如你用的是ES2015,在一段代码块顶用let也许const声明的变量会限定它们只在这个块中可见。这叫做块级作用域。

一个代码块就是你用花括号包起来的部份。 { 像如许 }。在if/else声明和try/catch/finally块中经常涌现。假如你想运用块作用域的上风,你可以用花括号包裹恣意的代码来建立一个代码块

斟酌下面的代码片断。

// 在 Node 中你须要运用 strict 情势尝试这个
"use strict";

var foo = "foo";
function baz() {
    if (foo) {
        var bar = "bar";
        let foobar = foo + bar;
    }
    // foo 和 bar 这里都可见 
    console.log("This situation is " + foo + bar + ". I'm going home.");

    try {
        console.log("This log statement is " + foobar + "! It threw a ReferenceError at me!");
    } catch (err) {
        console.log("You got a " + err + "; no dice.");
    }

    try {
        console.log("Just to prove to you that " + err + " doesn't exit outside of the above `catch` block.");
    } catch (err) {
        console.log("Told you so.");
    }
}

baz();

try {
    console.log(invisible);
} catch (err) {
    console.log("invisible hasn't been declared, yet, so we get a " + err);
}
let invisible = "You can't see me, yet"; // let 声明的变量在声明前是不可接见的

另有些要强调的

  • 注重foobar在if块以外是不可见的,因为我们没有用let声明;

  • 我们可以在任何处所运用foo ,因为我们用var定义它为全局作用域可见;

  • 我们可以在baz内部任何处所运用bar, 因为var-声明的变量是在定义的悉数作用域内都可见。

  • 用let or const声明的变量不能在定义前挪用。换句话说,它不会像var变量一样被编译器提升到作用域的最先处。

const 与 let 类似,但有两点差别。

  1. 必需给声明为const的变量在声明时赋值。不可以先声明后赋值。

  2. 不能转变const变量的值,只需在建立它时可以给它赋值。假如你试图转变它的值,会取得一个TyepError。

let & const: Who Cares?

我们已用var迁就了二十多年了,你可以在想我们真的须要新的范例声明关键字吗?(这里作者应当是想表达这个意义)

问的好,简朴的回复就是–不, 并不真正须要。但在可以用let和const的处所运用它们很有长处的。

  • let和const声明变量时都不会被提升到作用域最先的处所,如许可以使代码可读性更强,制作尽量少的疑惑。

  • 它会尽量的束缚变量的作用域,有助于削减使人疑惑的定名争执。

  • 如许可以让递次只需在必需从新分配变量的状况下从新分配变量。 const 可以增强常量的援用。

另一个例子就是 let 在 for 轮回中的运用:

"use strict";

var languages = ['Danish', 'Norwegian', 'Swedish'];

//会污染全局变量!
for (var i = 0; i < languages.length; i += 1) {
    console.log(`${languages[i]} is a Scandinavian language.`);
}

console.log(i); // 4

for (let j = 0; j < languages.length; j += 1) {
    console.log(`${languages[j]} is a Scandinavian language.`);
}

try {
    console.log(j); // Reference error
} catch (err) {
    console.log(`You got a ${err}; no dice.`);
}

在for轮回中运用var声明的计数器并不会真正把计数器的值限定在本次轮回中。 而let可以。

let在每次迭代时从新绑定轮回变量有很大的上风,如许每一个轮回中拷贝自身 , 而不是同享全局范围内的变量。

"use strict";

// 简约明了
for (let i = 1; i < 6; i += 1) {
    setTimeout(function() {
        console.log("I've waited " + i + " seconds!");
    }, 1000 * i);
}

// 功用完全杂沓
for (var j = 0; j < 6; j += 1) {
        setTimeout(function() {
        console.log("I've waited " + j + " seconds for this!");
    }, 1000 * j);
}

第一层轮回会和你设想的一样事变。而下面的会每秒输出 “I’ve waited 6 seconds!”。

好吧,我挑选狗带。

动态this关键字的奇特

JavaScript的this关键字因为老是不按套路出牌而臭名远扬。

事实上,它的划定规矩相称简朴。不论怎样说,this在有些状况下会致使新颖的用法

"use strict";

const polyglot = {
    name : "Michel Thomas",
    languages : ["Spanish", "French", "Italian", "German", "Polish"],
    introduce : function () {
        // this.name is "Michel Thomas"
        const self = this;
        this.languages.forEach(function(language) {
            // this.name is undefined, so we have to use our saved "self" variable 
            console.log("My name is " + self.name + ", and I speak " + language + ".");
        });
    }
}

polyglot.introduce();

在introduce里, this.name是undefined。在回调函数表面,也就是forEach中, 它指向了polyglot对象。在这类状况下我们老是愿望在函数内部this和函数外部的this指向同一个对象。

题目是在JavaScript中函数会依据确定性四原则在挪用时定义自身的this变量。这就是有名的动态this 机制。

这些划定规矩中没有一个是关于查找this所形貌的“四周作用域”的;也就是说并没有一个确切的要领可以让JavaScript引擎可以基于包裹作用域来定义this的寄义。

这就意味着当引擎查找this的值时,可以找到值,但却和回调函数以外的不是同一个值。有两种传统的计划可以处置惩罚这个题目。

  • 在函数表面把this保存到一个变量中,平常取名self,并在内部函数中运用;

  • 也许在内部函数中挪用bind阻挠对this的赋值。

以上两种方法都可见效,但会发作副作用。

另一方面,假如内部函数没有设置它自身的this值,JavaScript会像查找别的变量那样查找this的值:经由历程遍历父作用域直到找到同名的变量。如许会让我们运用四周作用域代码中的this值,这就是有名的词法this。

假如有样的特征,我们的代码将会越发的清晰,不是吗?

箭头函数中的词法this

在 ES2015 中,我们有了这一特征。箭头函数不会绑定this值,许可我们运用词法绑定this关键字。如许我们就可以像如许重构上面的代码了:

"use strict";

let polyglot = {
    name : "Michel Thomas",
    languages : ["Spanish", "French", "Italian", "German", "Polish"],
    introduce : function () {
        this.languages.forEach((language) => {
            console.log("My name is " + this.name + ", and I speak " + language + ".");
        });
    }
}

… 如许就会根据我们想的那样事变了。

箭头函数有一些新的语法。

"use strict";

let languages = ["Spanish", "French", "Italian", "German", "Polish"];

// 多行箭头函数必需运用花括号, 
// 必需明白包含返回值语句
    let languages_lower = languages.map((language) => {
    return language.toLowerCase()
});

// 单行箭头函数,花括号是可省的,
// 函数默许返回末了一个表达式的值
// 你可以指明返回语句,这是可选的。
let languages_lower = languages.map((language) => language.toLowerCase());

// 假如你的箭头函数只需一个参数,可以省略括号
let languages_lower = languages.map(language => language.toLowerCase());

// 假如箭头函数有多个参数,必需用圆括号包裹
let languages_lower = languages.map((language, unused_param) => language.toLowerCase());

console.log(languages_lower); // ["spanish", "french", "italian", "german", "polish"]

// 末了,假如你的函数没有参数,你必需在箭头前加上空的括号。
(() => alert("Hello!"))();

MDN关于箭头函数的文档诠释的很好。

简写属性和要领

ES2015供应了在对象上定义属性和要领的一些新体式格局。

简写要领

在 JavaScript 中, method 是对象的一个有函数值的属性:

"use strict";

const myObject = {
    const foo = function () {
        console.log('bar');
    },
}

在ES2015中,我们可以如许简写:

"use strict";

const myObject = {
    foo () {
        console.log('bar');
    },
    * range (from, to) {
        while (from < to) {
            if (from === to)
                return ++from;
            else
                yield from ++;
        }
    }
}

注重你也可以运用生成器去定义要领。只须要在函数名前面加一个星号(*)。

这些叫做 要领定义 。和传统的函数作为属性很像,但有一些差别:

  • 只能在要领定义处挪用super;

  • 不许可用new挪用要领定义。

我会在随后的几篇文章中讲到super关键字。假如你等不及了, Exploring ES6中有关于它的干货。

简写和推导属性

ES6还引入了简写和推导属性 。

假如对象的键值和变量名是一致的,那末你可以仅用变量名来初始化你的对象,而不是定义冗余的键值对。

"use strict";

const foo = 'foo';
const bar = 'bar';

// 旧语法
const myObject = {
    foo : foo,
    bar : bar
};

// 新语法
const myObject = { foo, bar }

两中语法都以foo和bar键值指向foo and bar变量。背面的体式格局语义上越发一致;这只是个语法糖。

当用展现模块情势来定义一些简约的大众 API 的定义,我经常运用简写属性的上风。

"use strict";

function Module () {
    function foo () {
        return 'foo';
    }
    
    function bar () {
        return 'bar';
    }
    
    // 如许写:
    const publicAPI = { foo, bar }
    
    /* 不要如许写:
    const publicAPI =  {
       foo : foo,
       bar : bar
    } */ 
    
    return publicAPI;
};

这里我们建立并返回了一个publicAPI对象,键值foo指向foo要领,键值bar指向bar要领。

推导属性名

这是不稀有的例子,但ES6许可你用表达式做属性名。

"use strict";

const myObj = {
  // 设置属性名为 foo 函数的返回值
    [foo ()] () {
      return 'foo';
    }
};

function foo () {
    return 'foo';
}

console.log(myObj.foo() ); // 'foo'

依据Dr. Raushmayer在Exploring ES6中讲的,这类特征最重要的用处是设置属性名与Symbol值一样。

Getter 和 Setter 要领

末了,我想提一下get和set要领,它们在ES5中就已支撑了。

"use strict";

// 例子采纳的是 MDN's 上关于 getter 的内容
//   https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
const speakingObj = {
    // 纪录 “speak” 要领挪用过若干次
    words : [],
    
    speak (word) {
        this.words.push(word);
        console.log('speakingObj says ' + word + '!');
    },
    
    get called () {
        // 返回最新的单词
        const words = this.words;
        if (!words.length)
            return 'speakingObj hasn\'t spoken, yet.';
        else
            return words[words.length - 1];
    }
};

console.log(speakingObj.called); // 'speakingObj hasn't spoken, yet.'

speakingObj.speak('blargh'); // 'speakingObj says blargh!'

console.log(speakingObj.called); // 'blargh'

运用getters时要记得下面这些:

  • Getters不接收参数;

  • 属性名不可以和getter函数重名;

  • 可以用Object.defineProperty(OBJECT, “property name”, { get : function () { . . . } }) 动态建立 getter

作为末了这点的例子,我们可以如许定义上面的 getter 要领:

"use strict";

const speakingObj = {
    // 纪录 “speak” 要领挪用过若干次
    words : [],
    
    speak (word) {
        this.words.push(word);
        console.log('speakingObj says ' + word + '!');
    }
};

// 这只是为了证实看法。我是相对不会如许写的
function called () {
    // 返回新的单词
    const words = this.words;
    if (!words.length)
        return 'speakingObj hasn\'t spoken, yet.';
    else
        return words[words.length - 1];
};

Object.defineProperty(speakingObj, "called", get : getCalled ) 
除了 getters,另有 setters。像寻常一样,它们经由历程自定义的逻辑给对象设置属性。

"use strict";

// 建立一个新的 globetrotter(全球者)!
const globetrotter = {
    // globetrotter 如今所处国度所说的言语 
    const current_lang = undefined,
    
    // globetrotter 已近环游过的国度
    let countries = 0,
    
    // 检察环游过哪些国度了
    get countryCount () {
        return this.countries;
    }, 
    
    // 不论 globe trotter 飞到那里,都从新设置他的言语
    set languages (language) {
        // 增添环游过的城市数
        countries += 1;
    
        // 重置当前言语
        this.current_lang = language; 
    };
};

globetrotter.language = 'Japanese';
globetrotter.countryCount(); // 1

globetrotter.language = 'Spanish';
globetrotter.countryCount(); // 2

上面讲的关于getters的也一样实用于setters,但有一点差别:

  • getter不接收参数,setters必需接收恰好一个参数。

损坏这些划定规矩中的恣意一个都邑抛出一个毛病。

既然 Angular 2 正在引入TypeCript而且把class带到了台前,我愿望get and set可以盛行起来… 但另有点愿望它们不要盛行起来。

结论

将来的JavaScript正在变成实际,是时刻把它供应的东西都用起来了。这篇文章里,我们阅读了 ES2015的三个很盛行的特征:

  • let和const带来的块级作用域;

  • 箭头函数带来的this的词法作用域;

  • 简写属性和要领,以及getter和setter函数的回忆。

运用 ES6 编写更好的 JavaScript Part II:深切探讨 [类]

辞旧迎新

在本文的最先,我们要申明一件事:

从实质上说,ES6的classes主如果给建立老式组织函数供应了一种越发轻易的语法,并不是什么新魔法 —— Axel Rauschmayer,Exploring ES6作者

从功用上来讲,class声明就是一个语法糖,它只是比我们之前一向运用的基于原型的行动托付功用更壮大一点。本文将从新语法与原型的关联入手,细致研讨ES2015的class关键字。文中将提及以下内容:

  • 定义与实例化类;

  • 运用extends建立子类;

  • 子类中super语句的挪用;

  • 以及重要的标记要领(symbol method)的例子。

在此历程当中,我们将迥殊注重 class 声明语法从实质上是怎样映射到基于原型代码的。

让我们从头最先提及。

退一步说:Classes不是什么

JavaScript的『类』与Java、Python也许其他你可以用过的面向对象言语中的类差别。实在后者可以称作面向『类』的言语更加正确一些。

在传统的面向类的言语中,我们建立的类是对象的模板。须要一个新对象时,我们实例化这个类,这一步操纵通知言语引擎将这个类的要领和属性复制到一个新实体上,这个实体称作实例。实例是我们自身的对象,且在实例化今后与父类毫无内在联系。

而JavaScript没有如许的复制机制。在JavaScript中『实例化』一个类建立了一个新对象,但这个新对象却不独立于它的父类。

正相反,它建立了一个与原型相衔接的对象。纵然是在实例化今后,关于原型的修正也会通报到实例化的新对象去。

原型自身就是一个异常壮大的设想情势。有很多运用了原型的手艺模仿了传统类的机制,class便为这些手艺供应了简约的语法。

总而言之:

  • JavaScript不存在Java和其他面向对象言语中的类观点;

  • JavaScript 的class很大程度上只是原型继承的语法糖,与传统的类继承有很大的差别。

搞清晰这些今后,让我们先看一下class。

类基础:声明与表达式

我们运用class 关键字建立类,关键字今后是变量标识符,末了是一个称作类主体的代码块。这类写法称作类的声明。没有运用extends关键字的类声明被称作基类:

"use strict";

// Food 是一个基类
class Food {

    constructor (name, protein, carbs, fat) {
        this.name = name;
        this.protein = protein;
        this.carbs = carbs;
        this.fat = fat;
    }

    toString () {
        return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`
    }

    print () {
        console.log( this.toString() );
    }
}

const chicken_breast = new Food('Chicken Breast', 26, 0, 3.5);

chicken_breast.print(); // 'Chicken Breast | 26g P :: 0g C :: 3.5g F'
console.log(chicken_breast.protein); // 26 (LINE A)

须要注重到以下事变:

  • 类只能包含要领定义,不能有数据属性;

  • 定义要领时,可以运用简写要领定义;

  • 与建立对象差别,我们不能在类主体中运用逗号分开要领定义;

  • 我们可以在实例化对象上直接援用类的属性(如 LINE A)。

类有一个独占的特征,就是 contructor 组织要领。在组织要领中我们可以初始化对象的属性。

组织要领的定义并不是必需的。假如不写组织要领,引擎会为我们插进去一个空的组织要领:

"use strict";

class NoConstructor {
    /* JavaScript 会插进去如许的代码:
     constructor () { }
    */
}

const nemo = new NoConstructor(); // 能事变,但没啥意义

将一个类赋值给一个变量的情势叫类表达式,这类写法可以替换上面的语法情势:

"use strict";

// 这是一个匿名类表达式,在类主体中我们不能经由历程称号援用它
const Food = class {
    // 和上面一样的类定义……
}

// 这是一个定名类表达式,在类主体中我们可以经由历程称号援用它
const Food = class FoodClass {
    // 和上面一样的类定义……

    //  增加一个新要领,证实我们可以经由历程内部称号援用 FoodClass……        
    printMacronutrients () {
        console.log(`${FoodClass.name} | ${FoodClass.protein} g P :: ${FoodClass.carbs} g C :: ${FoodClass.fat} g F`)
    }
}

const chicken_breast = new Food('Chicken Breast', 26, 0, 3.5);
chicken_breast.printMacronutrients(); // 'Chicken Breast | 26g P :: 0g C :: 3.5g F'

// 然则不能在外部援用
try {
    console.log(FoodClass.protein); // 援用毛病
} catch (err) {
    // pass
}

这一行动与匿名函数与定名函数表达式很类似。

运用extends建立子类以及运用super挪用

运用extends建立的类被称作子类,或派生类。这一用法简朴明了,我们直接在上面的例子中构建:

"use strict";

// FatFreeFood 是一个派生类
class FatFreeFood extends Food {

    constructor (name, protein, carbs) {
        super(name, protein, carbs, 0);
    }

    print () {
        super.print();
        console.log(`Would you look at that -- ${this.name} has no fat!`);
    }

}

const fat_free_yogurt = new FatFreeFood('Greek Yogurt', 16, 12);
fat_free_yogurt.print(); // 'Greek Yogurt | 26g P :: 16g C :: 0g F  /  Would you look at that -- Greek Yogurt has no fat!'

派生类具有我们上文议论的统统有关基类的特征,别的另有以下几点新特点:

  • 子类运用class关键字声明,今后紧跟一个标识符,然后运用extend关键字,末了写一个恣意表达式。这个表达式平常来讲就是个标识符,但理论上也可所以函数。

  • 假如你的派生类须要援用它的父类,可以运用super关键字。

  • 一个派生类不能有一个空的组织函数。纵然这个组织函数就是挪用了一下super(),你也得把它显式的写出来。但派生类却可以没有组织函数。

  • 在派生类的组织函数中,必需先挪用super,才运用this关键字(译者注:仅在组织函数中是如许,在其他要领中可以直接运用this)。

在JavaScript中唯一两个super关键字的运用场景:

  1. 在子类组织函数中挪用。假如初始化派生类是须要运用父类的组织函数,我们可以在子类的组织函数中挪用super(parentConstructorParams),通报恣意须要的参数。

  2. 援用父类的要领。在通例要领定义中,派生类可以运用点运算符来援用父类的要领:super.methodName。

我们的 FatFreeFood 演示了这两种状况:

  1. 在组织函数中,我们简朴的挪用了super,并将脂肪的量传入为0。

  2. 在我们的print要领中,我们先挪用了super.print,今后才增加了其他的逻辑。

不论你信不信,我反恰是信了以上说的已涵盖了有关class的基础语法,这就是你最先试验须要控制的悉数内容。

深切进修原型

如今我们最先关注class是怎样映射到JavaScript内部的原型机制的。我们会关注以下几点:

  • 运用组织挪用建立对象;

  • 原型衔接的实质;

  • 属性和要领托付;

  • 运用原型模仿类。

  • 运用组织挪用建立对象

组织函数不是什么新颖玩艺儿。运用new关键字挪用恣意函数会使其返回一个对象 —— 这一步称作建立了一个组织挪用,这类函数平常被称作组织器:

"use strict";

function Food (name, protein, carbs, fat) {
    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

// 运用 'new' 关键字挪用 Food 要领,就是组织挪用,该操纵会返回一个对象
const chicken_breast = new Food('Chicken Breast', 26, 0, 3.5);
console.log(chicken_breast.protein) // 26

// 不必 'new' 挪用 Food 要领,会返回 'undefined'
const fish = Food('Halibut', 26, 0, 2);
console.log(fish); // 'undefined'

当我们运用new关键字挪用函数时,JS内部实行了下面四个步骤:

  1. 建立一个新对象(这里称它为O);

  2. 给O给予一个衔接到其他对象的链接,称为原型;

  3. 将函数的this援用指向O;

  4. 函数隐式返回O。

在第三步和第四步之间,引擎会实行你函数中的详细逻辑。

晓得了这一点,我们就可以重写Food要领,使之不必new关键字也能事变:

"use strict";

// 演示示例:消弭对 'new' 关键字的依靠
function Food (name, protein, carbs, fat) {
    // 第一步:建立新对象
    const obj = { };

    // 第二步:链接原型——我们在下文会越发详细地探讨原型的观点
    Object.setPrototypeOf(obj, Food.prototype);

    // 第三步:设置 'this' 指向我们的新对象
    // 尽然我们不能再运转的实行高低文中重置 `this`
    // 我们在运用 'obj' 庖代 'this' 来模仿第三步
    obj.name    = name;
    obj.protein = protein;
    obj.carbs   = carbs;
    obj.fat     = fat;

    // 第四步:返回新建立的对象
    return obj;
}

const fish = Food('Halibut', 26, 0, 2);
console.log(fish.protein); // 26

四步中的三步都是简朴明了的。建立一个对象、赋值属性、然后写一个return声明,这些操纵对大多数开发者来讲没有明白上的题目——然则这就是难倒世人的黑魔法原型。

直观明白原型链

在平常状况下,JavaScript中的包含函数在内的一切对象都邑链接到另一个对象上,这就是原型。

假如我们接见一个对象自身没有的属性,JavaScript就会在对象的原型上搜检该属性。换句话说,假如你对一个对象要求它没有的属性,它会对你说:『这个我不晓得,问我的原型吧』。

在另一个对象上查找不存在属性的历程称作托付。

"use strict";

// joe 没有 toString 要领……
const joe    = { name : 'Joe' },
    sara   = { name : 'Sara' };

Object.hasOwnProperty(joe, toString); // false
Object.hasOwnProperty(sara, toString); // false

// ……但我们照样可以挪用它!
joe.toString(); // '[object Object]',而不是援用毛病!
sara.toString(); // '[object Object]',而不是援用毛病!

只管我们的 toString 的输出完全没啥用,但请注重:这段代码没有引发任何的ReferenceError!这是因为只管joe和sara没有toString的属性,但他们的原型有啊。

当我们寻觅sara.toString()要领时,sara说:『我没有toString属性,找我的原型吧』。正如上文所说,JavaScript会亲热的讯问Object.prototype 是不是含有toString属性。因为原型上有这一属性,JS 就会把Object.prototype上的toString返回给我们递次并实行。

sara自身没有属性没紧要——我们会把查找操纵托付到原型上。

换言之,我们就可以接见到对象上并不存在的属性,只需其的原型上有这些属性。我们可以运用这一点将属性和要领赋值到对象的原型上,然后我们就可以挪用这些属性,彷佛它们真的存在在谁人对象上一样。

更给力的是,假如几个对象同享雷同的原型——正如上面的joe和sara的例子一样——当我们给原型赋值属性今后,它们就都可以接见了,无需将这些属性零丁拷贝到每一个对象上。

这就是为什么人人把它称作原型继承——假如我的对象没有,但对象的原型有,那我的对象也能继承这个属性。

事实上,这里并没有发作什么『继承』。在面向类的言语里,继承指从父类复制属性到子类的行动。在JavaScript里,没发作这类复制的操纵,事实上这就是原型继承与类继承比拟的一个重要上风。

在我们探讨原型究竟是怎样来的之前,我们先做一个扼要回忆:

  • joe和sara没有『继承』一个toString的属性;

  • joe和sara实际上基础没有从Object.prototype上『继承』;

  • joe和sara是链接到了Object.prototype上;

  • joe和sara链接到了同一个Object.prototype上。

  • 假如想找到一个对象的(我们称它作O)原型,我们可以运用 Object.getPrototypeof(O)。

然后我们再强调一遍:对象没有『继承自』他们的原型。他们只是托付到原型上。

以上。

接下来让我们深切一下。

设置对象的原型

我们已相识到基础上每一个对象(下文以O指代)都有原型(下文以P指代),然后当我们查找O上没有的属性,JavaScript引擎就会在P上寻觅这个属性。

至此我们有两个题目:

  • 以上状况函数怎样玩?

  • 这些原型是从那里来的?

名为Object的函数

在JavaScript引擎实行递次之前,它会建立一个环境让递次在内部实行,在实行环境中会建立一个函数,叫做Object, 以及一个关联对象,叫做Object.prototype。

换句话说,Object和Object.prototype在恣意实行中的JavaScript递次中永久存在。

这个Object乍一看彷佛和其他函数没什么区分,但迥殊之处在于它是一个组织器——在挪用它时返回一个新对象:

"use strict";

typeof new Object(); // "object"
typeof Object();     // 这个 Object 函数的特点是不须要运用 new 关键字挪用

这个Object.prototype对象是个……对象。正如其他对象一样,它有属性。

《运用ES6写更好的JavaScript》

关于Object和Object.prototype你须要晓得以下几点:

  1. Object函数有一个叫做.prototype的属性,指向一个对象(Object.prototype);

  2. Object.prototype对象有一个叫做.constructor的属性,指向一个函数(Object)。

实际上,这个总体计划关于JavaScript中的一切函数都是实用的。当我们建立一个函数——下文称作 someFunction——这个函数就会有一个属性.prototype,指向一个叫做someFunction.prototype 的对象。

与之相反,someFunction.prototype对象会有一个叫做.contructor的属性,它的援用指回函数someFunction。

"use strict";

function foo () {  console.log('Foo!');  }

console.log(foo.prototype); // 指向一个叫 'foo' 的对象
console.log(foo.prototype.constructor); // 指向 'foo' 函数

foo.prototype.constructor(); // 输出 'Foo!' —— 仅为证实白实有 'foo.prototype.constructor' 这么个要领且指向原函数

须要记着以下几个要点:

  1. 一切的函数都有一个属性,叫做 .prototype,它指向这个函数的关联对象。

  2. 一切函数的原型都有一个属性,叫做 .constructor,它指向这个函数自身。

  3. 一个函数原型的 .constructor 并不是必需指向建立这个函数原型的函数……有点绕,我们等下会深切探讨一下。

设置函数的原型有一些划定规矩,在最先之前,我们先归纳综合设置对象原型的三个划定规矩:

  1. 『默许』划定规矩;

  2. 运用new隐式设置原型;

  3. 运用Object.create显式设置原型。

默许划定规矩

斟酌下这段代码:

"use strict";

const foo = { status : 'foobar' };

异常简朴,我们做的事儿就是建立一个叫foo的对象,然后给他一个叫status的属性。

然后JavaScript在幕后多做了点事变。当我们在字面上建立一个对象时,JavaScript将对象的原型指向Object.prototype并设置其原型的.constructor指向Object:

"use strict";

const foo = { status : 'foobar' };

Object.getPrototypeOf(foo) === Object.prototype; // true
foo.constructor === Object; // true

运用new隐式设置原型

让我们再看下之前调整过的 Food 例子。

"use strict";

function Food (name, protein, carbs, fat) {
    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

如今我们晓得函数Food将会与一个叫做Food.prototype的对象关联。

当我们运用new关键字建立一个对象,JavaScript将会:

  1. 设置这个对象的原型指向我们运用new挪用的函数的.prototype属性;

  2. 设置这个对象的.constructor指向我们运用new挪用到的组织函数。

const tootsie_roll = new Food('Tootsie Roll', 0, 26, 0);

Object.getPrototypeOf(tootsie_roll) === Food.prototype; // true
tootsie_roll.constructor === Food; // true

这就可以让我们搞出下面如许的黑魔法:

"use strict";

Food.prototype.cook = function cook () {
    console.log(`${this.name} is cooking!`);
};

const dinner = new Food('Lamb Chops', 52, 8, 32);
dinner.cook(); // 'Lamb Chops are cooking!'

运用Object.create显式设置原型

末了我们可以运用Object.create要领手工设置对象的原型援用。

"use strict";

const foo = {
    speak () {
        console.log('Foo!');
    }
};

const bar = Object.create(foo);

bar.speak(); // 'Foo!'
Object.getPrototypeOf(bar) === foo; // true

还记得运用new挪用函数的时刻,JavaScript在幕后干了哪四件事儿吗?Object.create就干了这三件事儿:

  1. 建立一个新对象;

  2. 设置它的原型援用;

  3. 返回这个新对象。

你可以自身去看下MDN上写的谁人polyfill。
(译者注:polyfill就是给老代码完成现有新功用的补丁代码,这里就是指老版本JS没有Object.create函数,MDN上有手工撸的一个替换计划)

模仿 class 行动

直接运用原型来模仿面向类的行动须要一些技能。

"use strict";

function Food (name, protein, carbs, fat) {
    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

Food.prototype.toString = function () {
    return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`;
};

function FatFreeFood (name, protein, carbs) {
    Food.call(this, name, protein, carbs, 0);
}

// 设置 "subclass" 关联
// =====================
// LINE A :: 运用 Object.create 手动设置 FatFreeFood's 『父类』.
FatFreeFood.prototype = Object.create(Food.prototype);

// LINE B :: 手工重置 constructor 的援用
Object.defineProperty(FatFreeFood.constructor, "constructor", {
    enumerable : false,
    writeable  : true,
    value      : FatFreeFood
});

在Line A,我们须要设置FatFreeFood.prototype使之即是一个新对象,这个新对象的原型援用是Food.prototype。假如没这么搞,我们的子类就不能接见『超类』的要领。

不幸的是,这个致使了相称诡异的效果:FatFreeFood.constructor是Function,而不是FatFreeFood。为了保证统统一般,我们须要在Line B手工设置FatFreeFood.constructor。

让开发者从运用原型对类行动愚笨的模仿中脱离苦海是class关键字的发作效果之一。它确切也供应了防备原型语法稀有圈套的处置惩罚计划。

如今我们已探讨了太多关于JavaScript的原型机制,你应当更轻易明白class关键字让统统变得何等简朴了吧!

深切探讨下要领

如今我们已相识到JavaScript原型体系的必要性,我们将深切探讨一下类支撑的三种要领,以及一种迥殊状况,以完毕本文的议论。

  • 组织器;

  • 静态要领;

  • 原型要领;

  • 一种原型要领的迥殊状况:『标记要领』。

并不是我提出的这三组要领,这要归功于Rauschmayer博士在探究ES6一书中的定义。

类组织器

一个类的constructor要领用于关注我们的初始化逻辑,constructor要领有以下几个迥殊点:

  1. 只需在组织要领里,我们才可以挪用父类的组织器;

  2. 它在背地处置惩罚了一切设置原型链的事变;

  3. 它被用作类的定义。

第二点就是在JavaScript中运用class的一个重要长处,我们来援用一下《探究 ES6》书里的15.2.3.1 的题目:

子类的原型就是超类

正如我们所见,手工设置异常烦琐且轻易失足。假如我们运用class关键字,JavaScript在内部会担任搞定这些设置,这一点也是运用class的上风。

第三点有点意义。在JavaScript中类仅仅是个函数——它等同于与类中的constructor要领。

"use strict";

class Food {
    // 和之前一样的类定义……
}

typeof Food; // 'function'

与平常把函数作为组织器的体式格局差别,我们不能不必new关键字而直接挪用类组织器:

const burrito = Food('Heaven', 100, 100, 25); // 范例毛病

这就引发了另一个题目:当我们不必new挪用函数组织器的时刻发作了什么?

简短的回复是:关于任何没有显式返回的函数来讲都是返回undefined。我们只须要相信用我们组织函数的用户都邑运用组织挪用。这就是社区为什么商定组织要领的首字母大写:提示运用者要用new来挪用。

"use strict";

function Food (name, protein, carbs, fat) {
    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

const fish = Food('Halibut', 26, 0, 2); // D'oh . . .
console.log(fish); // 'undefined'

长一点的回复是:返回undefined,除非你手工检测是不是运用被new挪用,然后举行自身的处置惩罚。

ES2015引入了一个属性使得这类检测变得简朴: new.target.

new.target是一个定义在一切运用new挪用的函数上的属性,包含类组织器。 当我们运用new关键字挪用函数时,函数体内的new.target的值就是这个函数自身。假如函数没有被new挪用,这个值就是undefined。

"use strict";

// 强行组织挪用
function Food (name, protein, carbs, fat) {
    // 假如用户忘了手工挪用一下
    if (!new.target)
        return new Food(name, protein, carbs, fat);

    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

const fish = Food('Halibut', 26, 0, 2); // 糟了,不过没紧要!
fish; // 'Food {name: "Halibut", protein: 20, carbs: 5, fat: 0}'

在ES5里用起来也还行:

"use strict";

function Food (name, protein, carbs, fat) {

    if (!(this instanceof Food))
        return new Food(name, protein, carbs, fat);

    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

MDN文档报告了new.target的更多细节,而且给有兴致者配上了ES2015范例作为参考。范例里有关 [[Construct]] 的形貌很有启发性。

静态要领

静态要领是组织要领自身的要领,不能被类的实例化对象挪用。我们运用static关键字定义静态要领。

"use strict";

class Food {
     // 和之前一样……

     // 增加静态要领
     static describe () {
         console.log('"Food" 是一种存储了养分信息的数据范例');
     }
}

Food.describe(); // '"Food" 是一种存储了养分信息的数据范例'

静态要领与老式组织函数中直接属性赋值类似:

"use strict";

function Food (name, protein, carbs, fat) {
    Food.count += 1;

    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

Food.count = 0;
Food.describe = function count () {
    console.log(`你建立了 ${Food.count} 个 food`);
};

const dummy = new Food();
Food.describe(); // "你建立了 1 个 food"

原型要领

任何不是组织要领和静态要领的要领都是原型要领。之所以叫原型要领,是因为我们之前经由历程给组织函数的原型上附加要领的体式格局来完成这一功用。

"use strict";

// 运用 ES6:
class Food {

    constructor (name, protein, carbs, fat) {
        this.name = name;
        this.protein = protein;
        this.carbs = carbs;
        this.fat = fat;
    }

    toString () {  
        return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`;
    }

    print () {  
        console.log( this.toString() );  
    }
}

// 在 ES5 里:
function Food  (name, protein, carbs, fat) {
    this.name = name;
    this.protein = protein;
    this.carbs = carbs;
    this.fat = fat;
}

// 『原型要领』的定名也许来自我们之前经由历程给组织函数的原型上附加要领的体式格局来完成这一功用。
Food.prototype.toString = function toString () {
    return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`;
};

Food.prototype.print = function print () {
    console.log( this.toString() );
};

应当申明,在要领定义时完全可以运用生成器。

"use strict";

class Range {

    constructor(from, to) {
        this.from = from;
        this.to   = to;
    }

    * generate () {
        let counter = this.from,
            to      = this.to;

        while (counter < to) {
            if (counter == to)
                return counter++;
            else
                yield counter++;
        }
    }
}

const range = new Range(0, 3);
const gen = range.generate();
for (let val of range.generate()) {
    console.log(`Generator 的值是 ${ val }. `);
    //  Prints:
    //    Generator 的值是 0.
    //    Generator 的值是 1.
    //    Generator 的值是 2.
}

标志要领

末了我们说说标志要领。这是一些名为Symbol值的要领,当我们在自定义对象中运用内置组织器时,JavaScript引擎可以辨认并运用这些要领。

MDN文档供应了一个Symbol是什么的扼要概览:

Symbol是一个唯一且稳定的数据范例,可以作为一个对象的属性标示符。

建立一个新的symbol,会给我们供应一个被以为是递次里的唯一标识的值。这一点关于定名对象的属性异常有用:我们可以确保不会不小心掩盖任何属性。运用Symbol做键值也不是无数的,所以他们很大程度上对外界是不可见的(也不完全是,可以经由历程Reflect.ownKeys取得)

"use strict";

const secureObject = {
    // 这个键可以看做是唯一的
    [new Symbol("name")] : 'Dr. Secure A. F.'
};

console.log( Object.getKeys(superSecureObject) ); // [] -- 标志属性不太好猎取    
console.log( Reflect.ownKeys(secureObject) ); // [Symbol("name")] -- 但也不是完全隐蔽的

对我们来讲更有意义的是,这给我们供应了一种体式格局来通知 JavaScript 引擎运用特定要领来到达特定的目标。

所谓的『尽人皆知的Symbol』是一些特定对象的键,当你在定义对象中运用时他们时,JavaScript引擎会触发一些特定要领。

这关于JavaScript来讲有点奇特,我们照样看个例子吧:

"use strict";

// 继承 Array 可以让我们直观的运用 'length'
// 同时可以让我们接见到内置要领,如
// map、filter、reduce、push、pop 等
class FoodSet extends Array {

    // foods 把通报的恣意参数网络为一个数组
    // 拜见:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
    constructor(...foods) {
        super();
        this.foods = [];
        foods.forEach((food) => this.foods.push(food))
    }

     // 自定义迭代器行动,请注重,这不是何等好用的迭代器,然则个不错的例子
     // 键名前必需写星号
     * [Symbol.iterator] () {
        let position = 0;
        while (position < this.foods.length) {
          if (position === this.foods.length) {
              return "Done!"
          } else {
              yield `${this.foods[ position++ ]} is the food item at position ${position}`;
          }
         }
     }

     // 当我们的用户运用内置的数组要领,返回一个数组范例对象
     // 而不是 FoodSet 范例的。这使得我们的 FoodSet 可以被一些
     // 希冀操纵数组的代码操纵
     static get [Symbol.species] () {
         return Array;
     }
}

const foodset = new FoodSet(new Food('Fish', 26, 0, 16), new Food('Hamburger', 26, 48, 24));

// 当我们运用 for ... of 操纵 FoodSet 时,JavaScript 将会运用
// 我们之前用 [Symbol.iterator] 做键值的要领
for (let food of foodset) {
    // 打印悉数 food
    console.log( food );
}

// 当我们实行数组的 `filter` 要领时,JavaScript 建立并返回一个新对象
// 我们在什么对象上实行 `filter` 要领,新对象就运用这个对象作为默许组织器来建立
// 然则大部份代码都愿望 filter 返回一个数组,因而我们经由历程重写 [Symbol.species]
// 的体式格局通知 JavaScript 运用数组的组织器
const healthy_foods = foodset.filter((food) => food.name !== 'Hamburger');

console.log( healthy_foods instanceof FoodSet ); //
console.log( healthy_foods instanceof Array );

当你运用for…of遍历一个对象时,JavaScript将会尝试实行对象的迭代器要领,这一要领就是该对象 Symbol.iterator属性上关联的要领。假如我们供应了自身的要领定义,JavaScript就会运用我们自定义的。假如没有自身制订的话,假如有默许的完成就用默许的,没有的话就不实行。

Symbo.species更奇特了。在自定义的类中,默许的Symbol.species函数就是类的组织函数。当我们的子类有内置的鸠合(比方Array和Set)时,我们平常愿望在运用父类的实例时也能运用子类。

经由历程要领返回父类的实例而不是派生类的实例,使我们更能确保我们子类在大多数代码里的可用性。而Symbol.species可以完成这一功用。

假如不怎样须要这个功用就别辛苦去搞了。Symbol的这类用法——也许说有关Symbol的悉数用法——都还比较稀有。这些例子只是为了演示:

  1. 我们可以在自定义类中运用JavaScript内置的特定组织器;

  2. 用两个一般的例子展现了怎样完成这一点。

结论

ES2015的class关键字没有带给我们 Java 里或是SmallTalk里那种『真正的类』。宁肯说它只是供应了一种越发轻易的语法来建立经由历程原型关联的对象,实质上没有什么新东西。

运用ES6写更好的JavaScript part III:好用的鸠合和反引号

简介

ES2015发作了一些严重革新,像promises和generators. 但并不是新标准的统统都高不可攀。 – 相称一部份新特征可以疾速上手。

在这篇文章里,我们来看下新特征带来的长处:

  • 新的鸠合: map,weakmap,set, weakset

  • 大部份的new String methods

  • 模板字符串。

我们最先这个系列的末了一章吧。

模板字符串

模板字符串 处置惩罚了三个痛点,许可你做以下操纵:

  1. 定义在字符串内部的表达式,称为 字符串插值。

  2. 写多行字符串无须用换行符 (n) 拼接。

  3. 运用“raw”字符串 – 在反斜杠内的字符串不会被转义,视为常量。

“use strict”;

/* 三个模板字符串的例子:

字符串插值,多行字符串,raw 字符串。
================================= */
// ================================== 
// 1. 字符串插值 :: 剖析任何一个字符串中的表达式。 
console.log(1 + 1 = ${1 + 1});

// ================================== 
// 2. 多行字符串 :: 如许写: 
let childe_roland = 
I saw them and I knew them all. And yet <br> Dauntless the slug-horn to my lips I set, <br> And blew “Childe Roland to the Dark Tower came.”

// … 替代下面的写法: 
child_roland = 
‘I saw them and I knew them all. And yet\n’ + 
‘Dauntless the slug-horn to my lips I set,\n’ + 
‘And blew “Childe Roland to the Dark Tower came.”’;

// ================================== 
// 3. raw 字符串 :: 在字符串前加 raw 前缀,javascript 会忽视转义字符。 
// 依旧会剖析包在 ${} 的表达式 
const unescaped = String.rawThis ${string()} doesn't contain a newline!\n

function string () { return “string”; }

console.log(unescaped); // ‘This string doesn’t contain a newline!\n’ – 注重 \n 会被原样输出

// 你可以像 React 运用 JSX 一样,用模板字符串建立 HTML 模板 
const template = 
`

Example

I’m a pure JS & HTML template!

`

function getClass () { 
// Check application state, calculate a class based on that state 
return “some-stateful-class”; 
}

console.log(template); // 如许运用略显笨,自身尝尝吧!

// 另一个经常使用的例子是打印变量名: 
const user = { name : ‘Joe’ };

console.log(“User’s name is ” + user.name + “.”); // 有点冗杂 
console.log(User's name is ${user.name}.); // 如许稍好一些
  1. 运用字符串插值,用反引号替代引号包裹字符串,并把我们想要的表达式嵌入在${}中。

  2. 关于多行字符串,只须要把你要写的字符串包裹在反引号里,在要换行的处所直接换行。 JavaScript 会在换行处插进去新行。

  3. 运用原生字符串,在模板字符串前加前缀String.raw,依然运用反引号包裹字符串。

模板字符串也许只不过是一种语法糖 … 但它比语法糖略胜一筹。

新的字符串要领

ES2015也给String新增了一些要领。他们重要归为两类:

  1. 通用的便利要领

  2. 扩大 Unicode 支撑的要领。

在本文里我们只讲第一类,同时unicode特定要领也有相称好的用例 。假如你感兴致的话,这是地点在MDN的文档里,有一个关于字符串新要领的完全列表。

startsWith & endsWith

对新手而言,我们有String.prototype.startsWith。 它对任何字符串都有用,它须要两个参数:

  1. 一个是 search string 另有

  2. 整形的位置参数 n。这是可选的。

String.prototype.startsWith要领会搜检以nth位起的字符串是不是以search string最先。假如没有位置参数,则默许从头最先。

假如字符串以要搜刮的字符串开首返回 true,不然返回 false。

"use strict";

const contrived_example = "This is one impressively contrived example!";

// 这个字符串是以 "This is one" 开首吗?
console.log(contrived_example.startsWith("This is one")); // true

// 这个字符串的第四个字符以 "is" 开首?
console.log(contrived_example.startsWith("is", 4)); // false

// 这个字符串的第五个字符以 "is" 最先?
console.log(contrived_example.startsWith("is", 5)); // true

endsWith

String.prototype.endsWith和startswith类似: 它也须要两个参数:一个是要搜刮的字符串,一个是位置。

然则String.prototype.endsWith位置参数会通知函数要搜刮的字符串在原始字符串中被当作末端处置惩罚。

换句话说,它会切掉nth后的一切字符串,并搜检是不是以要搜刮的字符末端。

"use strict";

const contrived_example = "This is one impressively contrived example!";

console.log(contrived_example.endsWith("contrived example!")); // true

console.log(contrived_example.slice(0, 11)); // "This is one"
console.log(contrived_example.endsWith("one", 11)); // true

// 平常状况下,传一个位置参数向下面如许:
function substringEndsWith (string, search_string, position) {
    // Chop off the end of the string
    const substring = string.slice(0, position);

    // 搜检被截取的字符串是不是已 search_string 末端
    return substring.endsWith(search_string);
}

includes

ES2015也增加了String.prototype.includes。 你须要用字符串挪用它,而且要通报一个搜刮项。假如字符串包含搜刮项会返回true,反之返回false。

"use strict";

const contrived_example = "This is one impressively contrived example!";

// 这个字符串是不是包含单词 impressively ?
contrived_example.includes("impressively"); // true

ES2015之前,我们只能如许:

"use strict";
contrived_example.indexOf("impressively") !== -1 // true

不算太坏。然则,String.prototype.includes是 一个改良,它屏障了恣意整数返回值为true的破绽。

repeat

另有String.prototype.repeat。可以对恣意字符串运用,像includes一样,它会或多或少地完成函数名指导的事变。

它只须要一个参数: 一个整型的count。运用案例申明统统,上代码:

const na = "na";

console.log(na.repeat(5) + ", Batman!"); // 'nanananana, Batman!'

raw

末了,我们有String.raw,我们在上面简朴引见过。

一个模板字符串以 String.raw 为前缀,它将不会在字符串中转义:

/* 单右斜线要转义,我们须要双右斜线才打印一个右斜线,\n 在一般字符串里会被剖析为换行
  *   */
console.log('This string \\ has fewer \\ backslashes \\ and \n breaks the line.');

// 不想如许写的话用 raw 字符串
String.raw`This string \\ has too many \\ backslashes \\ and \n doesn't break the line.`

Unicode要领

虽然我们不触及盈余的 string 要领,然则假如我不通知你去这个主题的必读部份就会显得我忽视。

  • Dr Rauschmayer关于Unicode in JavaScript的引见

  • 他关于ES2015’s Unicode Support in Exploring ES6和The Absolute Minimum Every Software Developer Needs to Know About Unicode 的议论。

无论怎样我不能不跳过它的末了一部份。虽然有些老然则照样有长处的。

这里是文档中缺失的字符串要领,如许你会晓得缺哪些东西了。

  • String.fromCodePoint & String.prototype.codePointAt;

  • String.prototype.normalize;

  • Unicode point escapes.

鸠合

ES2015新增了一些鸠合范例:

  1. Map和WeakMap

  2. Set和WeakSet。

适宜的Map和Set范例异常轻易运用,另有弱变量是一个使人兴奋的修改,虽然它对Javascript来讲像进口货一样。

Map

map就是简朴的键值对。最简朴的明白体式格局就是和object类似,一个键对应一个值。

"use strict";

// 我们可以把 foo 当键,bar 当值
const obj = { foo : 'bar' };

// 对象键为 foo 的值为 bar
obj.foo === 'bar'; // true

新的Map范例在观点上是类似的,然则可以运用恣意的数据范例作为键 – 不止strings和symbols–另有除了pitfalls associated with trying to use an objects a map的一些东西。

下面的片断例举了 Map 的 API.

"use strict";

// 组织器
let scotch_inventory = new Map();

// BASIC API METHODS
// Map.prototype.set (K, V) :: 建立一个键 K,并设置它的值为 V。
scotch_inventory.set('Lagavulin 18', 2);
scotch_inventory.set('The Dalmore', 1);

// 你可以建立一个 map 内里包含一个有两个元素的数组
scotch_inventory = new Map([['Lagavulin 18', 2], ['The Dalmore', 1]]);

// 一切的 map 都有 size 属性,这个属性会通知你 map 里有若干个键值对。
// 用 Map 或 Set 的时刻,肯定要运用 size ,不能运用 length
console.log(scotch_inventory.size); // 2

// Map.prototype.get(K) :: 返回键相干的值。假如键不存在返回 undefined
console.log(scotch_inventory.get('The Dalmore')); // 1
console.log(scotch_inventory.get('Glenfiddich 18')); // undefined

// Map.prototype.has(K) :: 假如 map 里包含键 K 返回true,不然返回 false
console.log(scotch_inventory.has('The Dalmore')); // true
console.log(scotch_inventory.has('Glenfiddich 18')); // false

// Map.prototype.delete(K) :: 从 map 里删除键 K。胜利返回true,不存在返回 false
console.log(scotch_inventory.delete('The Dalmore')); // true -- breaks my heart

// Map.prototype.clear() :: 清晰 map 中的一切键值对
scotch_inventory.clear();
console.log( scotch_inventory ); // Map {} -- long night

// 遍历要领
// Map 供应了多种要领遍历键值。 
//  重置值,继承探究
scotch_inventory.set('Lagavulin 18', 1);
scotch_inventory.set('Glenfiddich 18', 1);

/* Map.prototype.forEach(callback[, thisArg]) :: 对 map 里的每一个键值对实行一个回调函数 
  *   你可以在回调函数内部设置 'this' 的值,经由历程通报一个 thisArg 参数,那是可选的而且没有太大必要那样做
  *   末了,注重回调函数已被传了键和值 */
scotch_inventory.forEach(function (quantity, scotch) {
    console.log(`Excuse me while I sip this ${scotch}.`);
});

// Map.prototype.keys() :: 返回一个 map 中的一切键
const scotch_names = scotch_inventory.keys();
for (let name of scotch_names) {
    console.log(`We've got ${name} in the cellar.`);
}

// Map.prototype.values() :: 返回 map 中的一切值
const quantities = scotch_inventory.values();
for (let quantity of quantities) {
    console.log(`I just drank ${quantity} of . . . Uh . . . I forget`);
}

// Map.prototype.entries() :: 返回 map 的一切键值对,供应一个包含两个元素的数组 
//   今后会经常看到 map 里的键值对和 "entries" 关联 
const entries = scotch_inventory.entries();
for (let entry of entries) {
    console.log(`I remember! I drank ${entry[1]} bottle of ${entry[0]}!`);
}

然则Object在保存键值对的时刻依然有用。 假如相符下面的悉数前提,你可以照样想用Object:

  1. 当你写代码的时刻,你晓得你的键值对。

  2. 你晓得你可以不会去增添或删除你的键值对。

  3. 你运用的键全都是 string 或 symbol。

另一方面,假如相符以下恣意前提,你可以会想运用一个 map。

  1. 你须要遍历悉数map – 然则这对 object 来讲是难以置信的.

  2. 当你写代码的时刻不须要晓得键的名字或数目。

  3. 你须要庞杂的键,像 Object 或 别的 Map (!).

像遍历一个map一样遍历一个object是可行的,但巧妙的是–还会有一些坑潜伏在暗处。 Map更轻易运用,而且增添了一些可集成的上风。然则object是以随机递次遍历的,map是以插进去的递次遍历的。

增加随便动态键名的键值对给一个object是可行的。但巧妙的是: 比如说假如你曾遍历过一个伪 map,你须要记着手动更新条目数。

末了一条,假如你要设置的键名不是string或symbol,你除了挑选Map别无挑选。

上面的这些只是一些指导性的看法,并不是最好的划定规矩。

WeakMap

你可以听说过一个迥殊棒的特征渣滓接纳器,它会按期地搜检不再运用的对象并消灭。

To quote Dr Rauschmayer:

WeakMap 不会阻挠它的键值被渣滓接纳。那意味着你可以把数据和对象关联起来不必忧郁内存走漏。

换句换说,就是你的递次丢掉了WeakMap键的一切外部援用,他能自动渣滓接纳他们的值。

只管大大简化了用例,斟酌到SPA(单页面运用) 就是用来展现用户愿望展现的东西,像一些物品形貌和一张图片,我们可以明白为API返回的JSON。

理论上来讲我们可以经由历程缓存相应效果来削减要求服务器的次数。我们可以如许用Map :

"use strict";

const cache = new Map();

function put (element, result) {
    cache.set(element, result);
}

function retrieve (element) {
    return cache.get(element);
}

… 这是行得通的,然则有内存走漏的风险。

因为这是一个SPA,用户也许想脱离这个视图,如许的话我们的 “视图”object就会失效,会被渣滓接纳。

不幸的是,假如你运用的是一般的Map ,当这些object不运用时,你必需自行消灭。

运用WeakMap替换就可以处置惩罚上面的题目:

"use strict";

const cache = new WeakMap(); // 不会再有内存泄露了

// 剩下的都一样

如许当运用落空不须要的元素的援用时,渣滓接纳体系可以自动重用那些元素。

WeakMap的API和Map类似,但有以下几点差别:

  1. 在WeakMap里你可以运用object作为键。 这意味着不能以String和Symbol做键。

  2. WeakMap只需set,get,has,和delete要领 – 那意味着你不能遍历weak map.

  3. WeakMaps没有size属性。

不能遍历或搜检WeakMap的长度的原因是,在遍历历程当中可以会碰到渣滓接纳体系的运转: 这一霎时是满的,下一秒就没了。

这类不可展望的行动须要郑重看待,TC39(ECMA第39届手艺委员会)曾试图防备制止WeakMap的遍历和长度检测。

其他的案例,可以在这里找到Use Cases for WeakMap,来自Exploring ES6.

Set

Set就是只包含一个值的鸠合。换句换说,每一个set的元素只会涌现一次。

这是一个有用的数据范例,假如你要追踪唯一而且牢固的object ,比如说聊天室的当前用户。

Set和Map有完全雷同的API。重要的差别是Set没有set要领,因为它不能存储键值对。剩下的险些雷同。

"use strict";

// 组织器
let scotch_collection = new Set();

// 基础的 API 要领
// Set.prototype.add (O) :: 和 set 一样,增加一个对象
scotch_collection.add('Lagavulin 18');
scotch_collection.add('The Dalmore');

// 你也可以用数组组织一个 set
scotch_collection = new Set(['Lagavulin 18', 'The Dalmore']);

// 一切的 set 都有一个 length 属性。这个属性会通知你 set 里有若干对象
//   用 set 或 map 的时刻,肯定记着用 size,不必 length
console.log(scotch_collection.size); // 2

// Set.prototype.has(O) :: 包含对象 O 返回 true 不然返回 false
console.log(scotch_collection.has('The Dalmore')); // true
console.log(scotch_collection.has('Glenfiddich 18')); // false

// Set.prototype.delete(O) :: 删除 set 中的 O 对象,胜利返回 true,不存在返回 false
scotch_collection.delete('The Dalmore'); // true -- break my heart

// Set.prototype.clear() :: 删除 set 中的一切对象
scotch_collection.clear();
console.log( scotch_collection ); // Set {} -- long night.

/* 迭代要领
 * Set 供应了多种要领遍历
 *  从新设置值,继承探究 */
scotch_collection.add('Lagavulin 18');
scotch_collection.add('Glenfiddich 18');

/* Set.prototype.forEach(callback[, thisArg]) :: 实行一个函数,回调函数
 *  set 里在每一个的键值对。 You can set the value of 'this' inside 
 *  the callback by passing a thisArg, but that's optional and seldom necessary. */
scotch_collection.forEach(function (scotch) {
    console.log(`Excuse me while I sip this ${scotch}.`);
});

// Set.prototype.values() :: 返回 set 中的一切值
let scotch_names = scotch_collection.values();
for (let name of scotch_names) {
    console.log(`I just drank ${name} . . . I think.`);
}

// Set.prototype.keys() ::  对 set 来讲,和 Set.prototype.values() 要领一致
scotch_names = scotch_collection.keys();
for (let name of scotch_names) {
    console.log(`I just drank ${name} . . . I think.`);
}

/* Set.prototype.entries() :: 返回 map 的一切键值对,供应一个包含两个元素的数组 
 *   这有点过剩,然则这类要领可以保存 map API 的可操纵性
 *    */
const entries = scotch_collection.entries();
for (let entry of entries) {
    console.log(`I got some ${entry[0]} in my cup and more ${entry[1]} in my flask!`);
}

WeakSet

WeakSet相干于Set就像WeakMap相干于 Map :

  1. 在WeakSet里object的援用是弱范例的。

  2. WeakSet没有property属性。

  3. 不能遍历WeakSet。

Weak set的用例并不多,然则这儿有一些Domenic Denicola称谓它们为“perfect for branding” – 意义就是标记一个对象以满足其他需求。

这儿是他给的例子:

/* 下面这个例子来自 Weakset 运用案例的邮件议论 
  *    邮件的内容和议论的其余部份在这儿:
  *      https://mail.mozilla.org/pipermail/es-discuss/2015-June/043027.html
  */

const foos = new WeakSet();

class Foo {
  constructor() {
    foos.add(this);
  }

  method() {
    if (!foos.has(this)) {
      throw new TypeError("Foo.prototype.method called on an incompatible object!");
    }
  }
}

这是一个轻量科学的要领防备人人在一个没有被Foo组织出的object上运用method。

运用的WeakSet的上风是许可foo里的object运用完后被渣滓接纳。

总结

这篇文章里,我们已相识了ES2015带来的一些长处,从string的便利要领和模板变量到恰当的Map和Set完成。

String要领和模板字符串易于上手。同时你很快也就不必随处用weak set了,我以为你很快就会喜好上Set和Map。

整顿转载:https://github.com/xitu/gold-miner

    原文作者:大猛
    原文地址: https://segmentfault.com/a/1190000005593043
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞