可写的吸气剂

2020-12-26 05:13:35

在我的代码中出现过几次的模式如下:对象具有一个属性,除非明确设置,否则该属性默认基于其其他属性为表达式,在这种情况下,其功能类似于普通属性。本质上,表达式用作默认值。

根据名称或标题生成默认ID的对象,但也可以具有自定义ID。

具有关于人类的信息的对象,其中名称可以显式指定,或者如果未指定,则从firstName和lastName生成。

一个具有用于绘制椭圆的参数的对象,如果未明确设置,则ry缺省为rx。

具有日期信息的对象文字和具有日期格式的可读属性,但可以用人类可读的自定义格式覆盖。

一个对象,该对象代表具有apiCall属性的Github URL的一部分(例如,用户名,仓库,分支),该属性可以自定义,也可以从这些部分生成(这实际上是提示此博客文章的示例)

好的,既然我已经使您相信了这种模式的实用性,那么我们如何在JS中实现它呢?我们的第一次尝试可能看起来像这样:

注意:为简单起见,我们将在本文中使用对象文字,但是相同的逻辑适用于使用Object.create()或lea是实例的Person类的变体。

为什么会这样?原因是,getter的存在将属性变成了访问器,因此它也无法保存数据。如果没有设置器,则设置后什么也不会发生。

但是,我们可以有一个setter,当调用该setter时,它将删除该访问器并将其替换为data属性:

let lea = {name:" Lea Verou&#34 ;, get id(){返回this.name.toLowerCase()。replace(/ \ W + / g,"-"); },设置id(v){删除this.id;返回this.id = v; }}

如果我们发现自己在代码库中的多个地方都需要这种模式,则可以将其抽象为一个助手:

函数writableGetter(o,property,getter,options = {}){Object.defineProperty(o,property,{get:getter,set(v){删除this [property];返回this [property] = v;},可枚举:true,可配置:true,... options});}

请注意,这里我们使用Object.defineProperty()代替了简洁的get / set语法。前者不仅对于增加现有对象更加方便,而且还允许我们自定义可枚举性,而后者仅默认为可枚举:true。

当我们要使用静态值覆盖时,此方法有效,但如果要使用其他getter覆盖,该怎么办?例如,考虑一下日期用例:如果我们想为日期组件维护一个单一的真实来源,并且仅覆盖格式作为函数,以便当日期组件发生更改时,格式化的日期会相应地更新怎么办?

如果我们确信将属性设置为实际的函数值没有意义,则可以专门处理这种情况,然后创建一个新的getter而不是data属性:

函数writableGetter(o,property,getter,options = {}){return Object.defineProperty(o,property,{get(){return getter.call(this);),设置(v){if(typeof v == =" function"){getter = v;} else {删除this [property];返回this [property] = v;}},可枚举:true,可配置:true,... options}); }

请注意,如果我们将属性设置为静态值,然后再尝试将其设置为函数,则它将只是创建函数的数据属性,因为我们已经删除了专门处理函数的访问器。如果这是一个重大问题,我们可以维护访问器并仅更新getter:

函数writableGetter(o,property,getter,options = {}){return Object.defineProperty(o,property,{get(){return getter.call(this);),设置(v){if(typeof v == =" function"){getter = v;} else {getter =()=> v;}},可枚举:true,可配置:true,... options});}

虽然这是定义助手的最直接方法,但使用起来并不自然。我们的对象定义现在分散在多个地方,可读性很差。当我们在设计UI之前开始实施时,通常就是这种情况。在这种情况下,编写帮助程序是实现,而其调用代码实际上是UI。

通过编写对函数的调用来开始设计函数始终是一个好习惯,就好像为我们工作的不倦的小精灵已经编写了我们的梦想的实现。

那么,我们将如何编写对象呢?实际上,我更喜欢使用可读性更高的get()语法,并将所有内容放在一个地方,然后以某种方式将该getter转换为可写getter。像这样:

let lea = {name:" Lea Verou&#34 ;, get id(){返回this.name.toLowerCase()。replace(/ \ W + / g,"-"); }} makeGetterWritable(lea," id&#34 ;, {enumerable:true});

我们可以实现这样的事情吗?当然。这是JS,我们可以做任何事!

主要思想是,我们读回我们创建的语法的描述符,进行修饰,然后将其填充为新属性:

函数makeGetterWritable(o,property,options){let d = Object.getOwnPropertyDescriptor(o,property);让getter = d.get; d.get = function(){return getter.call(this); }; d.set = function(v){if(typeof v ===" function"){getter = v; } else {删除此[属性];返回this [property] = v; }; //应用任何替代,例如可枚举的Object.assign(d,options); //使用新的描述符Object.defineProperty(o,property,d)}重新定义属性

尽管JS在区分访问者属性和数据属性方面非常坚定,但现实是我们经常需要以不同的方式将两者结合起来,并且从概念上讲,它比两个不同的类别更多地是数据访问者。以下是一些其他示例,其中data属性和accessor属性之间的界限有些……模糊:

“实时”数据属性:在获取或设置代码时执行代码以产生副作用的属性,但仍像常规数据属性一样保留数据。可以通过使用创建隐藏数据属性的助手来伪造此信息。这个想法是Bliss.live()的核心。

惰性评估:第一次读取(通过getter)然后将其自身替换为常规data属性时进行评估的属性。 如果在读取之前设置了它们,则它们的功能完全类似于可写的吸气剂。 这个想法是Bliss.lazy()的核心。 MDN也提到了这种模式。 注意:请不要实际使用name.toLowerCase()。replace(/ \ W + / g,"-")来实现id / slug生成。 为了简化示例,这非常简单。 与其他语言和书写系统相比,它具有英语/ ASCII特权,因此应避免使用。