【译】《Understanding ECMAScript6》- 第二章-Object

目录

ES陆针对Object的核查,目的在于使JavaScript语言尤其类似“万物皆对象”的见地。随着越多地使用Object类型进行支付,开荒者们进一步不满意于Object相对低下的付出作用。

ES陆透过各个渠道对Object进行了革新,包涵语法的调节、以及新的操作和交互方式等。

Object分类

JavaScript中的Object有过多例外的连串,举例自定义的目的和语言内置的目标,很轻便生出模糊。为了越来越准确地区分分化品类的对象,ES6引进了多少个新的术语,这个术语将Object的项目具体为以下三种;

  • 常备对象(Ordinary
    objects)是指具备JavaScript对象具有暗许行为的靶子;
  • 诡异对象(Exotic objects)是指有个别行为与默许行为不一样的目的;
  • 专门的学问对象(Standard
    objects)是指由ES六语言标准定义的靶子,比方Array、Date等。规范对象足以是平时对象,也可以是古怪对象
  • 内置对象(Built-in
    objects)是指JavaScript运转条件定义的目的。全部的正经对象都是放到对象

本书将要持续的剧情中详尽描述每一个对象的切实可行细节。

Object字面量扩展

Object字面量表明式被普及采取于JavaScript程序中,差不离全数的JavaScript应用程序中都可以找到这种情势。Object字面量有着类似JSON的简洁语法,那是它因而那样流行的主要缘由。ES六对Object字面量语法实行了扩展,保持语法简洁的前提下,也增进了作用性。

属性开头化的缩写形式

在ES伍及其从前的版本中,Object字面量必须写成键值对的格式,那就象征,在好几场景下会爆发部分重新的讲话,如下:

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

上述代码中的createPerson()函数成立了一个对象,这几个目标的属性值与createPerson()的实参值一样。这会令众多开拓者误感到对象的值是createPerson()实参的副本。

ES陆中新添了Object字面量的洗练注明语法,能够一定程度上解除以上误解。借使目的的某部属性与四个本地变量同名,就能够在宣称对象时只写那特性子的key,省略冒号和value。如下:

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

若果Object字面量的某部属性唯有key没有value,JavaScript引擎便会在近期效劳域内寻觅是不是留存与key同名的变量。假若存在,则将同名变量的值赋值为key对应的value。上述代码中的name品质对应的value纵使地点变量name的值。

ES陆新扩大那种体制的目的是令Object字面量语法越发简洁化。使用本地变量的值作为目标属性的value是一种很宽泛的情势,开头化属性的缩写格局能够令代码特别简明。

函数伊始化的缩写情势

ES陆等同精简了目标内函数的申明语法。在ES陆从前,开拓者必须遵循键值对的格式证明对象内的函数,如下:

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

ES陆中,能够简简单单冒号和function关键字,如下:

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

应用上述代码中的缩写格局表明的函数与上例中的功能完全同样。

计量属性名

JavaScript允许使用方括号计算对象的属性名,1方面令对象属性的操作特别动态化,另壹方面防止了不可能动用.一向访问的性质名引起的语法错误。如下:

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都含有空格,不可能直接行使.访问,只好通过方括号访问。方括号内能够归纳字符串和变量。

ES第55中学能够行使字符串作为靶子的属性名:

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

应用字符串作为目的属性名的前提是在宣称以前务必旗帜鲜明知晓此字符串的值。假诺此字符串并非固定值,比方须要依靠变量值动态总括,那种光景下便不可能采用上述的宣示方式了。

为满意以上供给,ES陆将方括号总结属性名的建制引进了Object字面量,如下:

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

上述代码中,Object字面量内的方括号的功用是测算对象的属性名,在那之中间为字符串运算。你同1能够运用以下形式:

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()对此ES陆以来,并不是一个探究性的遵守,不过它规范了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有1个囤积器属性name。使用Object.assign()之后,receiver被予以一个值为"filter.js"的例行属性receiver.name。那是出于
supplier.name的演算结果为"filter.js"Object.assign()将运算结果克隆为receiver的一个健康属性。

重复属性

ES伍严俊情势下不允许Object字面量存在key值重复的属性,举例:

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

上述代码在ES伍严格格局下会抛出语法错误。

ES陆移除了重新属性的语法错误。不论是在非严苛格局或然严厉方式下,上例中的代码都不会抛错,而且后边的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()创办的对象,它们的原型在创立的时候就被钦命了。在ES陆在此之前,并从未正儿8经的秘技改动目的的原型。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“

2个目的的原型积攒在内部隐藏属性[[Prototype]]中。Object.getPrototypeOf()函数重返[[Prototype]]的值,Object.setPrototypeOf()则是将[[Prototype]]的值改换为钦赐的value。除了上述多少个函数以外,还有壹部分别样路径对[[Prototype]]举行操作。

在ES5事先就已经有少数JavaScript引擎达成了通过操作__proto__属性来赢得和改造对象原型的法子。能够说__proto__Object.getPrototypeOf()Object.setPrototypeOf()的先锋。不过不用全数的JavaScript引擎都扶助__proto__,所以ES陆对此实行了专门的学问。

在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

上述代码与前例的功能雷同。唯1的分别是用Object字面量合作__proto__品质替代了Object.create()

__proto__属性有以下特点:

  1. 利用Object字面量证明时,__proto__属性只可以被赋值一遍。重复赋值会挑起错误。__proto__是ES陆中Object字面量中唯一有次限制的习性。

  2. 使用["__proto__"]访问对象属性时,方括号内的字符串只好当作1个符合规律的习性key,并不能够操作__proto__脾气。它是不适用于方括号总计对象属性key规则的个别天性之一。

操作__proto__品质时要时刻谨记上述规则。

super引用

如前文所述,原型是JavaScript中国和欧洲常首要的环节,ES陆针对原型新扩大了累累强化职能,super引用便是中间之1。比如,在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能够了然为3个针对当前目标原型的指针,等价于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(),形成了Infiniti递归的死循环,直到仓库溢出。使用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只幸亏目的方法中利用,不能够在常规函数和大局意义域内使用,不然会抛出语法错误

方法

在ES陆在此以前的本子中,方法并不曾精确的定义。经常以为方法是1种函数类型的靶子属性。ES陆正式规范了方法的定义,作为方法的函数有三个之中属性[[HomeObject]]标明方法的名下对象:

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

上述代码中,person目的有三个格局getGreeting()getGreeting()的内部属性[[HomeObject]]值为person,表明它的归属对象是personshareGreeting()是1个函数,它未有[[HomeObject]]品质,因为它不是其余对象的办法。大多数意况下,方法与函数的区分并不是很关键,然而选择super时索要严格管理两岸的异议。

super引用是依靠[[HomeObject]]品质来调控其针对性的。其里面机制如下:

  1. 率先依据被调用方法的[[HomeObject]]属性值(即当前方式的名下对象),通过Object.getPrototypeOf()获取原型;
  2. 取获得原型后,检索同名方法;
  3. 绑定this指向并施行办法函数。

假若1个函数未有[[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字面量方面,属性开头化的缩写形式能够进一步简明地经过当前功用域的同名变量进行赋值;总计属性名字为对象扩展属性提供更加多的动态化帮衬;函数开首化的缩写方式简化了对象方法的注解语法;属性重复注脚在ES六严苛和非严谨情势下都不会报错。

Object.assign()函数能够开始展览对象多种属性的仿制,统1mixin情势的操作流程。

Object.setPrototypeOf()函数能够变动对象的原型。ES六正经了__proto__
属性,作为二个仓库储存器属性,它的get方法为Object.getPrototypeOf(),set方法为Object.setPrototypeOf()

super引用永世指向当前目的的原型。super能够当做函数使用,举个例子super(),也能够看成指针使用,举例super.getGreeting()。不论哪个种类格局,super调用的在这之中this永久指向当前的成效域。