ECMAScript【译】《Understanding ECMAScript6》- 第三段-Object

目录

  • Object分类
  • Object字面量扩展
  • Object.assign()
  • 又属性
  • 反原型
  • super引用
  • 方法
  • 总结

ES6针对Object的改善,旨在使JavaScript语言更加类似“万物皆对象”的观点。随着越来越多地采用Object类型进行开,开发者们越来越不满足吃Object相对低下的开销效率。

ES6透过多种路子对Object进行了改进,包括语法的调动、以及新的操作与交互方式等。

Object分类

JavaScript中的Object有众多异之项目,比如从定义的对象同言语内置的目标,很轻生出模糊。为了重新准确地区瓜分不同品类的对象,ES6引入了几乎独新的术语,这些术语将Object的路具体为以下几种植;

  • 便对象(Ordinary
    objects)是依靠具备JavaScript对象有默认行为之对象;
  • 离奇对象(Exotic objects)是因某些行为以及默认行为不同的对象;
  • 规范对象(Standard
    objects)是指由于ES6语言规范定义之对象,比如Array、Date等。专业对象好是普普通通对象,也可是千奇百怪对象
  • 置于对象(Built-in
    objects)是指JavaScript运行条件定义的靶子。具有的正儿八经对象都是停放对象

本书用于连续之情遭详细讲述每种对象的具体细节。

Object字面量扩展

Object字面量表达式被大面积采取于JavaScript程序中,几乎所有的JavaScript应用程序中还可以找到这种模式。Object字面量有着近乎JSON的简练语法,这是其之所以如此流行的首要由。ES6针对性Object字面量语法进行了扩大,保持语法简洁之前提下,也提高了功能性。

性初始化的缩写模式

每当ES5及其前的本子中,Object字面量必须写成键值对的格式,这虽表示,在少数场景下会出有重新的言辞,如下:

function createPerson(name, age) {
    return {
        name: name,
        age: age
    };
}

上述代码中之createPerson()函数创建了一个目标,这个目标的属性值与createPerson()的实参值相同。这会使众多开发者误认为对象的值是createPerson()实参的副本。

ES6遭到新增了Object字面量的简单声明语法,可以肯定水平达到去掉以上误解。如果目标的某某属性和一个当地变量同名,就好以声明对象时仅写这特性的key,省略冒号和value。如下:

function createPerson(name, age) {
    return {
        name,
        age
    };
}

一旦Object字面量的某个属性只有key没有value,JavaScript引擎便会在手上打算域内搜索是否留存与key同名的变量。如果有,则将同名变量的价值赋值为key对应的value。上述代码中之name属性对应之value尽管当地变量name的值。

ES6初添这种体制的目的是令Object字面量语法更加简洁化。使用当地变量的价值作为对象属性的value举凡平栽死广泛的模式,初始化属性之缩写模式可让代码更加简明。

函数初始化的缩写模式

ES6平精简了目标内函数的声明语法。在ES6之前,开发者必须按键值对之格式声明对象内的函数,如下:

var person = {
    name: "Nicholas",
    sayName: function() {
        console.log(this.name);
    }
};

ES6负,可以简单冒号和function关键字,如下:

var person = {
    name: "Nicholas",
    sayName() {
        console.log(this.name);
    }
};

运上述代码中之缩写模式声明的函数和齐例被之打算完全相同。

测算属性名

JavaScript允许以方括号计算对象的属于性名,一方面让对象属性之操作逾动态化,另一方面避免了无可知利用.直白看的性能名引起的语法错误。如下:

var person = {},
    lastName = "last name";
person["first name"] = "Nicholas";
person[lastName] = "Zakas";
console.log(person["first name"]);      // "Nicholas"
console.log(person[lastName]);          // "Zakas"

上述代码中之person对象的有数独属于性名first namelast name且富含空格,无法直接动用.访问,只能通过方括号访问。方括号内可以概括字符串和变量。

ES5被得采取字符串作为对象的属于性名:

var person = {
    "first name": "Nicholas"
};
console.log(person["first name"]);      // "Nicholas"

下字符串作为对象属性名的前提是在宣称前必须明确了解者字符串的值。如果这字符串并非固定值,比如要根据变量值动态计算,这种状况下就算不可知下上述的声明方式了。

为满足以上需,ES6将方括号计算属性名的体制引入了Object字面量,如下:

var lastName = "last name";
var person = {
    "first name": "Nicholas",
    [lastName]: "Zakas"
};
console.log(person["first name"]);      // "Nicholas"
console.log(person[lastName]);          // "Zakas"

上述代码中,Object字面量内之方括号的作用是精打细算对象的属于性名,其里面也字符串运算。你同可行使以下模式:

var suffix = " name";
var person = {
    ["first" + suffix]: "Nicholas",
    ["last" + suffix]: "Zakas"
};
console.log(person["first name"]);      // "Nicholas"
console.log(person["last name"]);       // "Zakas"

ES6被,方括号不仅仅可以在访问对象属性时算属性名,同样可以于目标声明时算属性名。

Object.assign()

mixin是结合对象常用的模式之一,本质是用一个靶的特性键值对克隆给另外一个对象。很多JavaScript类库有近似如下的mixin函数:

function mixin(receiver, supplier) {
    Object.keys(supplier).forEach(function(key) {
        receiver[key] = supplier[key];
    });
    return receiver;
}

上述代码中之mixin()函数将supplier对象的由生总体性(不包括原型链属性)克隆赋值给receiver目标。这种方法可免经连续实现receiver属性之壮大。请看如下示例:

function EventTarget() { /*...*/ }
EventTarget.prototype = {
    constructor: EventTarget,
    emit: function() { /*...*/ },
    on: function() { /*...*/ }
};
var myObject = {};
mixin(myObject, EventTarget.prototype);
myObject.emit("somethingChanged");

上述代码中,myObject靶通过仿制EventTarget.prototype的性质获取到了打印事件与下emit()on()函数的效能。

ES6新增的Object.assign()更是提高了这种模式,并且进一步语义化。上文提到的mixin()函数使用赋值运算符=进行性能克隆,这样的弱项是力不从心处理目标的囤器属性(后续章节详细描述)。Object.assign()釜底抽薪了立即无异题目。

今非昔比之JavaScript类库实现mixin模式之函数取名迥异,其中extend()mix()是使对大宽泛的函数誉为。ES6头除了Object.assign()外界,还就引入了Object.mixin()方法。Object.mixin()可以仿造对象的贮存器属性,但是出于super的引入(后续章节详细描述),最终取消了Object.mixin()的使用。

Object.assign()足替上文提到的mixin()函数:

function EventTarget() { /*...*/ }
EventTarget.prototype = {
    constructor: EventTarget,
    emit: function() { /*...*/ },
    on: function() { /*...*/ }
}
var myObject = {}
Object.assign(myObject, EventTarget.prototype);
myObject.emit("somethingChanged");

Object.assign()得承受任意数目的克隆源对象,克隆目标靶仍克隆源对象的依次依次克隆。也就是说,队列后面的源于对象属性会覆盖其前面的起源对象同名属性。如下:

var receiver = {};
Object.assign(receiver, {
        type: "js",
        name: "file.js"
    }, {
        type: "css"
    }
);
console.log(receiver.type);     // "css"
console.log(receiver.name);     // "file.js"

上述代码最终的receiver.type值为css,因为第二只来自对象覆盖了第一个源对象的同名属性。

Object.assign()对于ES6的话,并无是一个探索性的作用,但是其规范了mixin模式,而无需借助于让第三正值类库。

储存器属性的拍卖

mixin模式下存储器属性是不可知被统统仿制的,Object.assign()实为上是由此赋值运算符克隆属性,在处理存储器属性时,将源对象的积存器属性的运算结果克隆至目标靶。如下:

var receiver = {},
    supplier = {
        get name() {
            return "file.js"
        }
    };
Object.assign(receiver, supplier);
var descriptor = Object.getOwnPropertyDescriptor(receiver, "name");
console.log(descriptor.value);      // "file.js"
console.log(descriptor.get);        // undefined

上述代码中的源于对象supplier发生一个存储器属性name。使用Object.assign()之后,receiver叫给予一个价为"filter.js"的例行性receiver.name。这是由于
supplier.name的演算结果吧"filter.js"Object.assign()用运算结果克隆为receiver的一个例行性。

更属性

ES5严模式下未容许Object字面量存在key值重复的性质,比如:

var person = {
    name: "Nicholas",
    name: "Greg"        // syntax error in ES5 strict mode
};

上述代码在ES5严厉模式下会抛来语法错误。

ES6转换除了重新属性之语法错误。不论是以非严格模式要严格模式下,上例被之代码都非会见抛错,而且后面的name属性值将蒙前的价值。

var person = {
    name: "Nicholas",
    name: "Greg"        // not an error in ES6
};
console.log(person.name);       // "Greg"

上述代码person.name的取值为"Greg",因为name属于性取的是终极一软赋值。

变更原型

原型是JavaScript实现持续的根底,ES6尤其提高了原型的作用。ES5遇提供Object.getPrototypeOf()函数用来博取指定对象的原型。ES6引入了那逆向操作函数Object.setPrototypeOf()故而来改指定对象的原型。

无是行使构造函数还是经过Object.create()创造的靶子,它们的原型在开创的时刻就给指定了。在ES6之前,并不曾正式的方式改变目标的原型。Object.setPrototypeOf()打破了目标为创造后无可知更改原型的业内,在斯意义上,可以说Object.setPrototypeOf()举凡革命性的。

Object.setPrototypeOf()接纳两个参数,第一单参数是深受更改原型的对象,第二个参数是第一单参数为改后的原型。如下:

let person = {
    getGreeting() {
        return "Hello";
    }
};
let dog = {
    getGreeting() {
        return "Woof";
    }
};
// prototype is person
let friend = Object.create(person);
console.log(friend.getGreeting());                      // "Hello"
console.log(Object.getPrototypeOf(friend) === person);  // true
// set prototype to dog
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting());                      // "Woof"
console.log(Object.getPrototypeOf(friend) === dog);     // true

上述代码中定义了少数只基础对象:persondog。两者都发生一个getGreeting()函数。对象friend创造时继续自person,此时friend.getGreeting()的运算结果也”Hello“。然后经Object.setPrototypeOf()函数将friend对象的原型更改为dog,此时friend.getGreeting()的演算结果吗”woof“

一个目标的原型储存在内部隐藏属性[[Prototype]]中。Object.getPrototypeOf()函数返回[[Prototype]]的值,Object.setPrototypeOf()则是将[[Prototype]]的价改变也指定的value。除了上述两单函数以外,还有一对旁途径对[[Prototype]]展开操作。

在ES5前就曾经来个别JavaScript引擎实现了经操作__proto__性来得到与更改对象原型的点子。可以说__proto__Object.getPrototypeOf()Object.setPrototypeOf()的先锋。但是并非有的JavaScript引擎都支持__proto__,所以ES6针对这个展开了正式。

在ES6中,Object.prototype.__proto__作一个仓储器属性,它的get方法为Object.getPrototypeOf()set方法为Object.setPrototypeOf()。所以,使用Object.getPrototypeOf()Object.setPrototypeOf()函数和直接操作__proto__本质上是平等的。如下:

let person = {
    getGreeting() {
        return "Hello";
    }
};
let dog = {
    getGreeting() {
        return "Woof";
    }
};
// prototype is person
let friend = {
    __proto__: person
};
console.log(friend.getGreeting());                      // "Hello"
console.log(Object.getPrototypeOf(friend) === person);  // true
console.log(friend.__proto__ === person);               // true
// set prototype to dog
friend.__proto__ = dog;
console.log(friend.getGreeting());                      // "Woof"
console.log(friend.__proto__ === dog);                  // true
console.log(Object.getPrototypeOf(friend) === dog);     // true

上述代码和前例的法力雷同。唯一的界别是用Object字面量配合__proto__性取代了Object.create()

__proto__特性有以下特点:

  1. 采用Object字面量声明时,__proto__性只能被赋值一坏。重复赋值会唤起错误。__proto__大凡ES6遭遇Object字面量中唯一有不行克的性。

  2. 使用["__proto__"]顾对象属性时,方括号内的字符串只能当做一个健康的属性key,并无克操作__proto__性。它是勿适用于方括号计算对象属性key规则的个别性质之一。

操作__proto__特性时若时时谨记上述规则。

super引用

如前文所述,原型是JavaScript中好重大的环,ES6针对性原型新增了森强化功能,super引用不怕是其中某。举个例子,在ES5条件下,如果想复写一个对象原型的同名函数,你也许会见挑选类似下述代码的方:

let person = {
    getGreeting() {
        return "Hello";
    }
};
let dog = {
    getGreeting() {
        return "Woof";
    }
};
// prototype is person
let friend = {
    __proto__: person,
    getGreeting() {
        // same as this.__proto__.getGreeting.call(this)
        return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
    }
};
console.log(friend.getGreeting());                      // "Hello, hi!"
console.log(Object.getPrototypeOf(friend) === person);  // true
console.log(friend.__proto__ === person);               // true
// set prototype to dog
friend.__proto__ = dog;
console.log(friend.getGreeting());                      // "Woof, hi!"
console.log(friend.__proto__ === dog);                  // true
console.log(Object.getPrototypeOf(friend) === dog);     // true

上述代码中,friend出一个暨原型链中重名的getGreeting()。通过Object.getPrototypeOf()调用其原型的同名函数后追加字符串", hi!".call(this)保证原型函数中之作用域为friend

需要留意的是,Object.getPrototypeOf().call(this)不能不配合以。遗漏任何一方都可能勾问题。因此,ES6引入了super为简化这种操作。

简单易行来讲,super可知晓也一个针对性当前目标原型的指针,等价于Object.getPrototypeOf(this)。所以,前例中的代码可以改写为以下形式:

let friend = {
    __proto__: person,
    getGreeting() {
        // in the previous example, this is the same as:
        // 1. Object.getPrototypeOf(this).getGreeting.call(this)
        // 2. this.__proto__.getGreeting.call(this)
        return super.getGreeting() + ", hi!";
    }
};

上述代码中的super.getGreeting()等价于Object.getPrototypeOf(this).getGreeting.call(this)
或者this.__proto__.getGreeting.call(this)

super的效力并不仅仅限于此。比如当差不多重继承的状况下,Object.getPrototypeOf()连无克满足急需,如下:

let person = {
    getGreeting() {
        return "Hello";
    }
};

// prototype is person
let friend = {
    __proto__: person,
    getGreeting() {
        return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
    }
};

// prototype is friend
let relative = {
    __proto__: friend
};

console.log(person.getGreeting());                  // "Hello"
console.log(friend.getGreeting());                  // "Hello, hi!"
console.log(relative.getGreeting());                // error!

上述代码中,执行relative.getGreeting()Object.getPrototypeOf()会面报错。因为这时候之this对的是relativerelative的原型为friend。所以当friend.getGreeting().call()this受指定为relative每每一定给履行relative.getGreeting(),形成了无限递归的死循环,直到堆栈溢起。使用super好老自由之解决这种题材。如下:

let person = {
    getGreeting() {
        return "Hello";
    }
};
// prototype is person
let friend = {
    __proto__: person,
    getGreeting() {
        return super.getGreeting() + ", hi!";
    }
};
// prototype is friend
let relative = {
    __proto__: friend
};
console.log(person.getGreeting());                  // "Hello"
console.log(friend.getGreeting());                  // "Hello, hi!"
console.log(relative.getGreeting());                // "Hello, hi!"

super的针对是定位的。不论发生略层继承关系,super.getGreeting()永对person.getGreeting()

super偏偏能够以对象方法受到使用,不可知于常规函数及大局意图域内使用,否则会废弃来语法错误

方法

在ES6之前的本被,方法连不曾标准之概念。通常认为方法凡一律种植函数类型的目标属性。ES6正式规范了方法的定义,作为方法的函数有一个里属性[[HomeObject]]表明方法的落对象:

let person = {
    // 方法
    getGreeting() {
        return "Hello";
    }
};
// 不是方法
function shareGreeting() {
    return "Hi!";
}

上述代码中,person对象有一个办法getGreeting()getGreeting()的里边属性[[HomeObject]]值为person,表明它们的归对象是personshareGreeting()凡是一个函数,它并未[[HomeObject]]属性,因为其不是任何对象的艺术。大多数场面下,方法及函数的分并无是特别要紧,但是采取super时用严谨处理双边的异议。

super引用是依据[[HomeObject]]性能来决定其针对性的。其中间机制如下:

  1. 率先冲被调用方法的[[HomeObject]]属于性值(即当前方式的落对象),通过Object.getPrototypeOf()收获原型;
  2. 收获到原型后,检索同名方法;
  3. 绑定this指于连执行办法函数。

而一个函数没有[[HomeObject]]性能或者属于性值是谬误的,以上之机制就无法运转。如下:

let person = {
    getGreeting() {
        return "Hello";
    }
};
// prototype is person
let friend = {
    __proto__: person,
    getGreeting() {
        return super() + ", hi!";
    }
};
function getGlobalGreeting() {
    return super.getGreeting() + ", yo!";
}
console.log(friend.getGreeting());  // "Hello, hi!"
getGlobalGreeting();                      // throws error

上述代码中之friend.getGreeting()回来了无可非议结果,而getGlobalGreeting()运作产生了不当,因为super连无克在常规函数内使。由于getGlobalGreeting()函数不在[[HomeObject]]性能,所以不能够由此super开拓进取检索。即便getGlobalGreeting()函数被动态的赋值给目标的法子,它还不克使super。如下:

// prototype is person
let friend = {
    __proto__: person,
    getGreeting() {
        return super() + ", hi!";
    }
};
function getGlobalGreeting() {
    return super.getGreeting() + ", yo!";
}
console.log(friend.getGreeting());  // "Hello, hi!"
// assign getGreeting to the global function
friend.getGreeting = getGlobalGreeting;
friend.getGreeting();               // throws error

上述代码中,全局函数getGlobalGreeting()被动态地赋值给friendgetGreeting()方式。随后调用friend.getGreeting()生和前例相同之左。这是以[[HomeObject]]的属性值在函数/方法给创造的当儿便固定了,随后不可知被更改。

总结

Object是JavaScript语言中最主要的模块,ES6每当简化操作以及加剧功能方面进行了成千上万改善。

每当Object字面量方面,属性初始化的缩写模式可进一步从简地经过时作用域的同名变量进行赋值;计算属性名为对象扩大属性提供更多的动态化支持;函数初始化的缩写模式简化了对象方法的扬言语法;属性重复声明在ES6严酷和非严格模式下都未会见报错。

Object.assign()函数可以开展对象多重属性的克隆,统一mixin模式的操作流程。

Object.setPrototypeOf()函数可以变动对象的原型。ES6规范了__proto__
属性,作为一个囤器属性,它的get方法吧Object.getPrototypeOf(),set方法为Object.setPrototypeOf()

super援永远对当前目标的原型。super足当做函数使用,比如super(),也足以作为指针动,比如super.getGreeting()。不论哪种形式,super调用的里this永远对当前底作用域。