The Surprisingly Elegant Javascript Type Model

The Surprisingly Elegant Javascript Type Model

转自:http://vijayan.ca/blog/2012/02/21/javascript-type-model/

By now, Javascript programmers at large have generally gotten a handle on the prototype-based inheritance schemes that are possible within the language.  Still, it seems that many people feel that JS’s idea of types is somewhat lacking. What I want to talk about is the reality that once you dust around the corners a little bit, it’s possible to see that Javascript contains within it the outlines of a reasonably well-defined, somewhat elegant (if not strictly enforced) type model.

I feel that a clear understanding of the nature of the existing informal system would be useful in informing further discussion on the nature of what a more formalized notion of types in Javascript might look like.

Let’s get started.

The Type Model

In idiomatic JS, we can consider three general categories of objects as composing the structural basis of types:

  • Type objects – These are just the constructor functions used to instantiate objects.
  • Traits objects – This is what I’ll call the objects referred to by the prototype field of a constructor function. They provide the method bindings for instances of the type they are associated with. Traits objects are basically the behaviour specification for the instances a type.
  • Instance objects – These are the actual instances. They are created with their prototype (i.e. delegation target) pointing to the trait object of their type.

Note that these categories are neither exclusive, nor complete. A single Javascript object can belong to more than one category, and there can exist Javascript objects that fall into none of the above categories. But I’ll get to that later. Here’s a diagram showing how these objects are structured:

As noted earlier, Type (in blue) is the constructor function, Type.prototype points to the traits object (in green), and new Type() (in gray) is an instance of Type.  The light-gray dotted line points out the instance-of relationship between new Type() and Type.  This structure is pretty simple: Types have a field named prototype that points to its traits object.  The traits object has a field named constructor that points back to the type it is associated with.  The instance belongs to the type by virtue of the fact that its prototype (i.e. the object that it delegates to) refers to the traits object for the type.

Pretty straightforward so far, no?

The Subtyping Model

Let’s keep going with that diagram, and model what subtyping looks like in Javascript:

Here, the light-blue dotted line shows the implicit subtype-of relationship between Subtype andType.  The key point to notice here is the following: Subtype is a subtype of Type by virtue of the fact that Subtype.prototype (the traits object for Subtype) delegates to Type.prototype (the traits object for Type). This is how the delegation-based inheritance in Javascript is leveraged by the type model to enable the behaviour sharing necessary to represent subtype relationships.

This set of relationships is not my construction, but the structure that Javascript already uses, albeit in an informal way, to express type and subtype relationships. To prove this to ourselves, we just need to take a look at how the core Javascript constructor functions are organized. The following diagram shows the actual relationship between three of the core Javascript constructor functions (ObjectFunction, and Array) and their instances:

Well that got complicated fast, didn’t it?  Well, not really.  It’s the same simple structure that we were looking at above, except we are also diagramming the fact that functions (and thus constructor functions, and thus the built-in constructor functions ObjectFunction, and Array themselves) are also instances of Function.

Yes, this means that Function is an instance of itself (naturally, since it’s a function, and thus an instance of Function).  This is something we’ve all been dealing with, knowingly or not, for a long time now – all constructor functions are regular functions and thus instances of Function, and Function itself is just the constructor function for constructing other functions, so it too is an instance of Function.

The Pullback

Let’s step back from that rat’s nest of a relationship diagram above. It looks hairy, but in reality it reflects a very simple truth about how Javascript already structures its type relationships for all of its built-in objects. Let’s throw away the instances, the traits objects and all of that stuff, and just look at the types and subtypes. The following is what we see already going on inside the world of Javascript types:

That right there is the type model that’s been present in Javascript for a while now. Pretty neat, huh?

Let’s think about what that picture above tells us. It first tells us that Javascript’s type system and subtyping model is built around two core types: Object and Function. The root of all subtype-ofrelationships is Object, and Function is the only meta-type in the system. Every type in this system has Function for its own type, including Function itself. Function is basically a universal meta-type, hiding in plain view. Neat, huh?

The Caveats

As cool as observing the above may be, this detailing is more than a bit idealized. While these relationships are there, embedded right into the core Javascript types we use every day, they are notstrict, and they don’t completely cover the language.

Consider, for example, Object.prototype (or, for that matter, Function.prototype, orString.prototype, or any such traits object). In the context of the type system described above, those objects don’t actually HAVE a type of their own. They’re purely structural – they exist only to model the inheritance chain, and provide the method bindings for instances. Despite this, we can easily access them, and pass them around to functions, or return them from functions, or store them in arrays, or anything else you can do with other regular objects which have well-defined types.

For another example, consider the result of the expression Object.create(null). This creates a new object that has no prototype that it delegates to. This newly created object is completely un-represented and un-captured by the type-system above.

Furthermore, none of the ‘primitive’ values are covered at all by this system. Primitive strings, numbers, booleans, and the null value – none are really represented. However, this is ameliorated somewhat by the fact that primitive values (except for null and undefined) are auto-boxed when methods are called on them. So, if you squint at them the right way (and refrain from doing things liketypeof on them), you can basically get away with looking at them as instances of their respective object-constructors (i.e. a primitive string as an “instance” of String).

All that is a bit.. well.. discomforting. The type system above has some amount of elegance, and it’s already “present” in some sense in the language, but it’s voluntary and does not cover all of the objects we can touch from Javascript code. Is that a good thing or a bad thing? Well, that all depends on your temperament.

Javascript has always been a language with warts, but those warts are a reflection of its history. It’s the ugly duckling that inadvertantly took over the world. It’s the language the dynamic web was built on. It is what it is. However, I understand if you disagree, if this eating-french-fries-off-the-fine-china really gets under your skin. I can feel it a bit too.. tickling the back of my neck.

The Takeaway

I believe that the simple structure used within Javascript to model types contains within it the sketches of a truly powerful system for organizing the behaviour of complex object relationships. Javascript already represents classes, subclasses, and at least one notional metaclass.

Moving on, I think we can use this realization and understanding to start thinking about simple ways in which the existing capabilities can be extended (loosened in certain places, tightened up in others) to allow JS programmers to tap their full power.

More on that when I get a chance to write it up…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值