ES6新技术之对象技术

 

Understanding ECMAScript 6 「对象」Object技术是JS开发中使用最多的技术之一。因为JS应用规模越来越大,对象越来越多(多应用角色,多结构层次的对象),对象的创建和使用应该尽可能的简洁。

ES6对对象这个工具作了多个优化改进,让它使用更加方便,和灵活。

本文部分内容择译自《Understanding ECMAScript 6 》。

对象技术的两种应用

所谓对象技术,是指Object对象作为一种计算容器的功能,它可装多个不同数据成员(键值对),以及与数据相关的函数方法成员。另外,两个对象之间的「原型关系」可以实现面向对象的程序构造任务;故对象技术既可用来:

  • 第一,处理单一的专用计算(包括无状态通用代码,少量状态的闭包,和无代码的键值对数据结构);
  • 第二,也是实现层次化的程序工程复用的基础。

对象技术的应用

对象的形式

(这部分内容较抽象,实用代码直接看 ES6节)

对象技术针对的编程任务包括对象的创建(字面量创建)、派生(对象继承)、使用(制作功能方法),而这些任务前提是我们对对象形式的理论有一定的基础。

对象属性及其两个角度

对象形式实质就是计算任务或计算功能,形式由「本质属性」组成。在制作和使用对象的活动中,对象存有两个不同角度:形式属性和结构成员。

形式属性与结构成员

对象,从一开始设计初衷,是有结构的,结构成份叫成员;成员是从质料的角度,一个对象的构造者(生产者)的角度。而相对的,对象属性,是呈现给使用者的功能,是从形式的角度,一个对象的使用者(消费者)的角度。对象的制作不但满足自己的计算需要,「通用对象」也是给项目内其它他计算任务(对象)使用,使用的就是对象的形式属性,包括「数据属性」和「方法属性」。

属性种类

从形式的角度,对象只有「数据属性」和「方法属性」,从质料角度,数据属性由「数据字段」和「字段访问器」(getter/setter)两类成员组成。在对象使用者的角度,数据字段成员和访问器成员,都是提供计算数据资源,在逻辑上可归为一类。

对象形式

数据属性的attributes

「对象」的形式属性很丰富,看以下MDN对对象属性的定义:

 an object property is a name, a value, and a set of attributes.

从编程的角度,对象属性 笼统包括了属性名、值和一些元属性( attributes )

为了提供计算功能,对象每一个属性必须有一个标识,和标识所指示的计算资源。而对象属性的 attributes 是指什么呢? 对象属性的值除了参与提供计算功能的资源,这些资源如何被使用(可读写吗?可枚举吗?可删除吗?)attributes 就是用来「规定计算资源如何被使用」的编程接口。JS开放这些attributes API的原因待究。

ES6

字面量创建 对象实例

ES6在对象字面量(object literals)上作多处改进:

第一,属性名简写

对象数据属性赋初始值的变量名可以直接用作对象数据属性名,简化代码;

const x = 4;
const y = 1;
const obj = { x, y }; // 相当于ES5: const obj = { x: x, y: y };

属性名简写很好配合新的对象解构赋值语法:

const obj = { x: 4, y: 1 };
const {x,y} = obj;
console.log(x); // 4
console.log(y); // 1

访问器属性保持原样:

const obj = {

    get foo() {
        console.log('GET foo');
        return 123;
    },
    set bar(value) {
        console.log('SET bar to ' + value);
        // return value is ignored
    }
};

第二,方法名简写

直接写「方法」名,无需再使用函数表达式,省去function和分号等字;

const obj = {

  myMethod(x, y) {
  ···
  }
};

generator函数方法:

const obj = {
  * myGeneratorMethod() {
    ···
    }
    };

第三,动态属性名

属性名可使用表达式,表达式可让属性使用非字面值,就是带变量,这个功能早在其它语言中可用;

在设置一个对象属性时,是一直可以用表达式的:

 Via an expression: obj['b'+'ar'] = 123;

但是字面量创建,ES5不行,ES6增加了一这个语法:

1` 数据属性动态:

const obj = {
  foo: true,
  \['b'+'ar'\]: 123
};

2`方法名动态:

const obj = {
  \['h'+'ello'\]() {
    return 'hi';
  }
};
console.log(obj.hello()); // hi

动态计算的属性名最典的用例,就是方便使用 symbols 作为属性名。最著名的就是Symbol.iterator 符号用作iterator的属性名。某个对象具有这个符号的属性的表明它是可迭代的(iterable)——给for-of等循环消费:

const obj = {
  * \[Symbol.iterator\]() { // (A)
    yield 'hello';
    yield 'world';
    }
    };

for (const x of obj) {
  console.log(x);
}
// Output:
// hello
// world

第四,允许属性重名

ES6 放松在严格模式下对属性重名的检查;就是放宽了对重复对象字面量属性名称的严格模式检查,这意味着具有相同名称的两个属性可以在单个对象字面量定义中,而不会引发错误。

对象形式动态改变

对象实例的形式动态改变的任务包括:查询对象形式属性、扩展计算功能、更换对象父原型等

对象形态动态改变

Object.assign()

这是Object一个新的静态方法,功能是将一个或多个通用对象(的功能)属性“赋”给一个特殊对象,扩展这对象的形式,标准化流行的mixin/extend模式。

对象混入(Mixins)是JS中非流行的对象派生技术——合并两对象产生新对象的编程模式,因为它比原型继承实现对象复用更加的灵活(EM:有点像函数对象比较一般类对象更轻便一样)。例如,很多通用代码库都有类似如下的mixin函数:

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

return receiver;
}

通过 mixin 函数,接受都对象(receiver)无需通过原型继承机制,从提供者对象(supplier)上获得新的对象属性,例如如下的一个空对象,mixin了EventTarget.prototype的所有对象属性:

function EventTarget() { /*...*/ }

EventTarget.prototype = {
  constructor: EventTarget,
  emit: function() { /*...*/ },
  on: function() { /*...*/ }
};

var myObject = {};
mixin(myObject, EventTarget.prototype);
myObject.emit("somethingChanged");

对象混入技术的需求如此之大,故ES6 提供了Object.assign() 。Object.assign() 较mixin函数更通用和灵活,它可mixin 任意个 supplier 合并给receiver。

Object.assign() only considers own (non-inherited) properties.

对象复制的意义、实现原理,和使用注意

意义是,复用代码功能(规模工程的需要),mixin模式的优点(相对于正式继承派生)是轻便;

原理是JS的动态性(可动态修改对象形式),Object.assign()是一种浅层复制(非完整克隆),例如源对象的访问器属性会被复制成数据属性。

Object.is()

ES6提供了比===更安全的等值比较操作 Object.is()。使用Object.is()的策略规则是:大部分情况下,还是可继续三等号,除非非常特殊的需要,才考虑使用Object.is()。

console.log(+0 == -0); // true
console.log(+0 === -0); // true
console.log(Object.is(+0, -0));  // false

console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN));  // true

动态更改父原型

当我们用字面量创建一个动态对象,它的父原型对象是object,功能很简单的;我们可能需替换父原型来增加功能。ES6提供了新的标准API(以前是通用访问内部属性__proto__) Object.setPrototypeOf()。

对象实例与使用

对象实例使用的任务包括:枚举自有属性,和访问父原型的属性。

规定了对象自有属性的枚举顺序

ES5没有规定对象自有属性的枚举顺序,这是浏览器自己决定,这样影响了以下等API的一致使用:

  • - Object.getOwnPropertyNames()
  • - Reflect.ownKeys
  • - Object.assign()

ECMAScript 6 清楚地定义了自有属性的枚举顺序。先是数字(以升序排列),接着是字符串(按插入顺序排列),最后符号键(按插入顺序排列):

var obj = {
  a: 1,
  0: 1,
  c: 1,
  2: 1,
  b: 1,
  1: 1
};

obj.d = 1;
console.log(Object.getOwnPropertyNames(obj).join("")); // "012acbd"

super访问父原型

此外,你可以使用 super 关键字调用父原型上的方法。使用 了 super 的方法内的 this,自动设置为 this 的当前值。

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

参考

裸男
Nakeman.cn 2023 Build by Gatsby and Tailwind, Deploy on Netlify.