dcl学习_dcl简介

dcl学习

I'm incredibly honored to have Eugene Lazutkin author for David Walsh Blog. Eugene has written much of the vector graphic code for the Dojo Toolkit's dojox/gfx (and subsequent charting and drawing resources) library, a library I consider to be mind-blowingly awesome. Eugene chose to write about dcl, an ultra-flexible, tiny OOP JS library.

能够为David Walsh Blog的作者Eugene Lazutkin感到非常荣幸。 尤金(Eugene)为Dojo Toolkit的dojox / gfx(以及随后的制图和绘图资源)库编写了许多矢量图形代码,该库我认为令人赞叹不已。 Eugene选择撰写有关dcldcl是一种超灵活的小型OOP JS库。

dcl is a minimalistic yet complete JavaScript package for node.js and modern browsers. It implements OOP with mixins + AOP at both "class" and object level, and works in strict and non-strict modes.

dcl是适用于node.js和现代浏览器的简约但完整JavaScript程序包。 它在“类”和对象级别上用mixins + AOP实现OOP,并在严格和非严格模式下工作。

The simplest way to learn something is to dive right in. Let's implement a simple widget based on reactive templating: when we change parameters of a widget, they are immeditely reflected in a web page.

学习某些东西的最简单方法是直接学习。让我们实现一个基于React性模板的简单小部件:当我们更改小部件的参数时,它们会立即反映在网页中。

Assuming that we run our code using AMD format in browser, our "code shell" will look like that:

假设我们在浏览器中使用AMD格式运行代码,则“代码外壳”将如下所示:


require(
  ["dcl", "dcl/bases/Mixer", "dcl/mixins/Cleanup", "dcl/advices/memoize"],
  function(dcl, Mixer, Cleanup, memoize){
    // our code goes here
  }
);


As the first step let's code our data model:

第一步,让我们对数据模型进行编码:


var Data = dcl(Mixer, {
  declaredClass: "Data",
  updateData: function(data){
    dcl.mix(this, data);
  }
});


We derived our class using single inheritance from Mixer, which comes with dcl. Mixer is a very simple base. All it does is it copies properties of the first constructor argument to an instance.

我们使用dcl随附的Mixer的单继承来派生我们的类。 Mixer是一个非常简单的基础。 它所做的只是将第一个构造函数参数的属性复制到实例。

Obviously in this simple example we could just call updateData() from our constructor, but let's assume that a constructor and an updater can do (slightly) different things and we want to keep them separately.

显然,在这个简单的示例中,我们可以从构造函数中调用updateData() ,但让我们假设构造函数和更新程序可以(略)做不同的事情,并且我们希望将它们分开保存。

declaredClass is completely optional, yet recommended to be specified (any unique human-readable name is fine), because it is used by debugging helpers included with `dcl`.

declaredClass是完全可选的,但建议指定它(任何人类可读的唯一名称都是可以的),因为它由dcl附带的调试助手使用。

Now let's code our nano-sized template engine, which substitutes strings like this: ${abc} with properties taken directly from an instance (this.abc in this case). Something like that:

现在,让我们编写纳米级模板引擎的代码,该引擎将这样的字符串替换为: ${abc} ,其属性直接取自实例(在本例中为this.abc )。 像这样:


var Template = dcl(null, {
  declaredClass: "Template",
  render: function(templateName){
    var self = this;
    return this[templateName].replace(/\$\{([^\}]+)\}/g, function(_, prop){
      return self[prop];
    });
  }
});


We specify what template to use by name, which is a property name on an object instance, and it fills out a template string using properties specified on an object.

我们通过名称指定要使用的模板,这是对象实例上的属性名称,它使用对象上指定的属性填充模板字符串。

This is another demonstration of single inheritance: our Template is based on a plain vanilla Object, like any JavaScript's object, which is indicated by using null as a base.

这是单继承的另一示例:我们的Template基于普通的香草Object ,就像任何JavaScript的对象一样,使用null作为基础来表示。

What else do we need? We need a way to manage our DOM node:

我们还需要什么? 我们需要一种管理DOM节点的方法:


var Node = dcl([Mixer, Cleanup], {
  show: function(text){
    if(this.node){
      this.node.innerHTML = text;
    }
  },
  destroy: function(){
    if(this.node){
      this.node.innerHTML = "";
    }
  }
});


The code above provides a way to show some HTML, and clears out its presentation when we destroy() a widget.

上面的代码提供了一种显示HTML的方法,并在我们destroy()小部件时清除了其表示。

It uses two bases: already mentioned Mixer is used to get a property in during initialization (node in this case), and Cleanup, which again comes with dcl. The latter chains all destroy() methods together and provides a simple foundation for clean up management, so all resources can be properly disposed of.

它有两个基础:已经提到的Mixer用于在初始化过程中获取属性(在本例中为node ),以及Cleanup ,它也是dcl附带的。 后者将所有destroy()方法链接在一起,并为清理管理提供了简单的基础,因此可以正确处理所有资源。

What we did up to this point is we came up with very small manageable orthogonal components, which reflect different sides of our widget, and can be combined together in different configurations. Let's put them all together now:

到目前为止,我们要做的是,我们提出了非常小的可管理的正交组件,这些组件反映了小部件的不同面,并且可以在不同的配置中组合在一起。 现在将它们放在一起:


var NameWidget0 = dcl([Data, Template, Node], {
  declaredClass: "NameWidget0",
  template: "Hello, ${firstName} ${lastName}!"
});

var x = new NameWidget0({
  node:      document.getElementById("name"),
  firstName: "Bob",
  lastName:  "Smith"
});

x.show(x.render("template")); // Hello, Bob Smith!
x.updateData({firstName: "Jill"});
x.show(x.render("template")); // Hello, Jill Smith!


It works, but it is not very coherent, and way too verbose. Don't worry, we will fix it soon.

它可以工作,但不是很协调,而且太冗长。 不用担心,我们会尽快修复。

Some readers probably noticed that we have three bases now: Data, Template, and Node, and two of them (Data, and Node) are based on Mixer. How does it work? It works fine, because underneath dcl uses C3 superclass linearization algorithm (the same one used by Python), which removes duplicates, and sorts bases to ensure that their requested order is correct. In this case a single copy of Mixin should go before both Data and Node. Read more on that topic in dcl() documentation.

一些读者可能已经注意到我们现在有三个基础: DataTemplateNode ,其中两个( DataNode )基于Mixer 。 它是如何工作的? 之所以能正常工作,是因为dcl之下使用了C3超类线性化算法 (Python使用了相同的算法 ),该算法删除了重复项,并对基数进行排序以确保其请求的顺序正确。 在这种情况下,应在DataNode之前添加一个Mixin副本。 在dcl()文档中阅读有关该主题的更多信息

Now let's address deficiencies of our implementation #0:

现在让我们解决实现#0的缺陷:

  • As soon as a widget is constructed, we should show text.

    构造小部件后,我们应该显示文本。
  • As soon as data is updated, we should show text.

    数据更新后,我们应该显示文本。

Both requirements are simple and seem to call for good old-fashioned supercalls:

这两个要求都很简单,似乎要求良好的老式超级调用:


var NameWidget1 = dcl([Data, Template, Node], {
  declaredClass: "NameWidget1",
  template: "Hello, ${firstName} ${lastName}!",
  constructor: function(){
    this.showData();
  },
  updateData: dcl.superCall(function(sup){
    return function(){
      sup.apply(this, arguments);
      this.showData();
    };
  }),
  showData: function(){
    var text = this.render("template");
    this.show(text);
  }
});

var x = new NameWidget1({
  node:      document.getElementById("name"),
  firstName: "Bob",
  lastName:  "Smith"
});
// Hello, Bob Smith!

x.updateData({firstName: "Jill"}); // Hello, Jill Smith!


Much better!

好多了!

Let's take a look at two new things: constructor and a supercall. Both are supposed to be supercalls, yet look differently. For example, constructor doesn't call its super method. Why? Because dcl chains constructors automatically.

让我们看一下两个新事物:构造函数和超级调用。 两者都应该是超级通话,但外观有所不同。 例如,构造函数不调用其super方法。 为什么? 因为dcl自动链接构造函数。

updateData() is straightforward: it calls a super first, then a method to update a visual. But it is declared using a double function pattern. Why? For two reasons: run-time efficience, and ease of debugging. Read all about it in dcl.superCall() documentation, and Supercalls in JS.

updateData()很简单:首先调用一个super,然后调用一个更新视觉效果的方法。 但这是使用双重功能模式声明的。 为什么? 出于两个原因:运行时效率高,调试方便。 在dcl.superCall()文档中阅读所有相关内容,并在JS中阅读Supercalls

While this implementation looks fine, it is far from "fine". Let's be smart and look forward: in real life our implementation will be modified and augmented by generations of developers. Some will try to build on top of it.

尽管此实现看起来不错,但远非“优良”。 让我们变得聪明并期待:在现实生活中,我们的实现将被几代开发人员修改和增强。 有些人会尝试在此基础上构建。

  • Our call to showData() in construct is not going to be the last code executed, as we expected. Constructors of derived classes will be called after it.

    正如我们所期望的那样,在构造函数中对showData()调用不会成为最后执行的代码。 派生类的构造函数将在其后被调用。

  • updateData() will be overwritten, and some programmers may forget to call a super. Again, they may update data in their code after our code called showData() resulting in stale data shown.

    updateData()将被覆盖,某些程序员可能会忘记调用super。 同样,在我们的代码showData()导致显示过时的数据之后,他们可能会更新其代码中的数据。

Obviously we can write lengthy comments documenting our "implementation decisions", and suggesting future programmers ways to do it right, but who reads docs and comments especially when writing "industrial" code in a crunch time?

显然,我们可以写一些冗长的注释来记录我们的“实现决策”,并建议未来的程序员正确地做这些事情,但是谁能读文档和注释,特别是在紧要关头编写“工业”代码时呢?

It would be nice to solve those problems in a clean elegant way. Is it even possible? Of course. That's why we have AOP.

干净利落地解决这些问题将是很好的。 可能吗 当然。 这就是为什么我们有AOP。

Let's rewrite our attempt #1:

让我们重写尝试1:


var NameWidget2 = dcl([Data, Template, Node], {
  declaredClass: "NameWidget2",
  template: "Hello, ${firstName} ${lastName}!",
  constructor: dcl.after(function(){
    this.showData();
  }),
  updateData: dcl.after(function(){
    this.showData();
  }),
  showData: function(){
    var text = this.render("template");
    this.show(text);
  }
});

var x = new NameWidget2({
  node:      document.getElementById("name"),
  firstName: "Bob",
  lastName:  "Smith"
});
// Hello, Bob Smith!

x.updateData({firstName: "Jill"}); // Hello, Jill Smith!


Not only we got a (slightly) smaller code, now we are guaranteed, that showData() is called after all possible constructors, and after every invokation of updateData(), which can be completely replaced with code that may use supercalls. We don't really care --- we just specified code, which will be executed *after* whatever was put there by other programmers.

不仅我们得到了一个(稍微)小的代码,而且现在可以保证,在所有可能的构造函数之后以及每次调用updateData()之后都会调用showData() updateData() ,可以完全将其替换为可以使用超级调用的代码。 我们并不在乎-我们只是指定了代码,这些代码将在其他程序员放置的所有内容之后*执行。

Now imagine that our user wants to click on a name, and get a pop-up with more detailed information, e.g., an HR record of that person. It would make sense to keep the information in one place, yet render it differently. And we already have a provision for that: we can add another template property, and call render() with its name:

现在,假设我们的用户想要单击一个名称,然后弹出一个包含更详细信息的弹出窗口,例如该人的HR记录。 将信息保留在一个地方,而以不同的方式呈现,这是有道理的。 并且我们已经有一个规定:我们可以添加另一个模板属性,并使用其名称调用render()


var PersonWidget1 = dcl(NameWidget2, {
  declaredClass: "PersonWidget1",
  detailedTemplate: "..."
});

var x = new PersonWidget1({
  node:      document.getElementById("name"),
  firstName: "Bob",
  lastName:  "Smith",
  position:  "Programmer",
  hired:     new Date(2012, 0, 1) // 1/1/2012
});
// Hello, Bob Smith!

var detailed = x.render("detailedTemplate");


In the example above I skipped the definition of a detailed template for brevity. But you can see that we can add more information about person, and we can define different templates when a need arises.

在上面的示例中,为简洁起见,我跳过了详细模板的定义。 但是您可以看到我们可以添加有关人员的更多信息,并且可以在需要时定义不同的模板。

Imagine that we profiled our new implementation and it turned out that we call render() method directly and indirectly very frequently, and it introduces some measurable delays. We can pre-render a template eagerly on every data update, yet it sounds like a lot of work for several complex templates, and some of them are not even going to be used. Better solution is to implement some kind of lazy caching: we will invalidate cache on every update, yet we will build a string only when requested.

想象一下,我们介绍了新的实现,结果发现我们非常频繁地直接和间接调用render()方法,并且引入了一些可测量的延迟。 我们可以在每次数据更新时急切地预渲染模板,但是对于多个复杂的模板来说,这似乎需要大量工作,并且其中一些甚至将不再使用。 更好的解决方案是实现某种惰性缓存:我们将在每次更新时使缓存无效,但仅在请求时才构建字符串。

Obviously such changes involve both Data and Template. Or it can be done downstream in NameWidget or PersonWidget. Now look above and please refrain from doing those changes: so far we tried to keep our "classes" orthogonal, and caching is clearly an orthogonal business.

显然,此类更改涉及到DataTemplate 。 或者可以在NameWidgetPersonWidget下游完成。 现在看上面,请不要进行这些更改:到目前为止,我们试图使“类”保持正交,并且缓存显然是正交的。

dcl already provides a simple solution: memoize advice. Let's use it in our example:

dcl已经提供了一个简单的解决方案: 记住建议 。 在我们的示例中使用它:


var PersonWidget2 = dcl(NameWidget2, {
  declaredClass: "PersonWidget2",
  detailedTemplate: "...",
  // memoization section:
  render:     dcl.advise(memoize.advice("render")),
  updateData: dcl.advise(memoize.guard ("render"))
});


With these two lines added our render() result is cached for every first parameter value ("template" or "detailedTemplate" in our case), and the cache will be invalidated every time we call updateData().

添加这两行后,将为每个第一个参数值(在本例中为“ template”或“ detailedTemplate” render()缓存render()结果,并且每次调用updateData()时,缓存都会失效。

In this article we presented dcl package. If you plan to use it in your Node.js project install it like this:

在本文中,我们介绍了dcl软件包。 如果您打算在Node.js项目中使用它,请像这样安装它:


npm install dcl


For your browser-based projects I suggest to use volo.js:

对于基于浏览器的项目,我建议使用volo.js


volo install uhop/dcl


The code is an open source on github.com/uhop/dcl under New BSD and AFL v2 licenses.

该代码是github.com/uhop/dcl上的新BSD和AFL v2许可下的开源代码。

This article didn't cover a lot of other things provided by dcl:

本文未涵盖dcl提供的许多其他内容:

  • Avoid the double function pattern in your legacy projects using inherited() supercalls.

    在您的旧项目中,请使用inherited()超级调用避免使用双重功能模式。

  • Use AOP on object-level --- add and remove advices dynamically in any order.

    在对象级别上使用AOP ---以任意顺序动态添加和删除建议。
  • Specify "before" and "after" automatic chaining for any method.

    为任何方法指定自动链接的“之前”和“之后”。
  • Use debug helpers that come with dcl.

    使用dcl随附的调试助手。

  • Leverage a small library of canned advices and mixins provided by dcl.

    利用dcl提供的小型罐装建议和mixin库。

If you want to learn more about it, or just curious, you can find a lot of information in the documentation.

如果您想了解更多或只是好奇,可以在文档中找到很多信息。

Happy DRY coding!

DRY编码愉快!

翻译自: https://davidwalsh.name/intro-dcl

dcl学习

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值