JavaScript: The TIBET Parts™
I want to thank the people I worked with at Electric Communities and State Software who helped me discover that deep down there was goodness in this language, especially Chip Morningstar, Randy Farmer, John La, Mark Miller, Scott Shattuck, and Bill Edney.
-- Douglas Crockford, JavaScript: The Good Parts
Wins
- Avoid problems with JS/DOM changes, browser bugs, etc. via common APIs.
- Avoid limiting boilerplate syntax for modules, types, and other declarations.
- Avoid common pitfalls due to type conversion, poor reflection, etc.
- Avoid compiler and source map overhead and leverage HTML5 browsers.
- Leverage "standard library" APIs for collections, comparisons, etc.
Contents
Concepts
Cookbook
Modules
Types
- Use
defineSubtype()
, not Function instance. - Use
defineAttribute()
, not constructor/prototype properties. - Use
defineMethod()
, not constructor/prototype properties. - Use
init()
not constructor function body. - Use
callNextMethod()
, notHardCodedType.method.call()
. - Use
TP.isType()
, nottypeof === 'function'
.
Instances
- Use
construct()
, notnew
, to create instances. - Use
get()
notthis.x
. - Use
set()
notthis.x = y
. - Use
TP.isKindOf()
, notinstanceof
- Use
TP.isMemberOf()
, notconstructor
checks. - Use
TP.tname()
, nottypeof
.
Collections
- Use
TP.hc()
, not{}
, for dictionaries. - Use
perform()
, notfor/in
, to iterate. - Use
remove\*()
, notdelete
.
Comparisons
- Use
TP.isValid()
orTP.notValid()
for boolean checks. - Use
TP.isNull()
orTP.notNull
. - Use
TP.isDefined()
orTP.notDefined
. - Use
TP.equal()
, not==
. - Consider
TP.identical()
rather than===
.
summary
Concepts
We sometimes joke that "TIBET isn't JavaScript, it's just written in it". While that sounds extreme, it's better to start with this standpoint and work our way back to reality.
In reality, TIBET is just JavaScript, there's no TIBET compiler or interpreter, no build or reload delays, no fussing with source maps. TIBET is "100% Pure JavaScript" optimized for ease of development, ease of tooling, and ease of maintenance.
To achieve its goals of easier, faster development, TIBET relies on a set of APIs that replace many of the standard JavaScript keywords with object methods. These APIs are specifically designed to avoid common issues with authoring in standard JavaScript.

Over our 20-plus years developing enterprise-class applications for the web we've found these changes to be critical to maintaining, extending, and evolving the TIBET platform and its applications with a minimum of overhead.
In return for a significant increase in power, control, and maintainability, TIBET requires you to make some simple changes in your approach to JavaScript coding.
In extreme form the quick summary of these changes is:
- Don't use functions as types.
- Don't use
typeof
orinstanceof
. - Don't use
new
. - Don't access properties directly.
- Don't use
{}
as a hash/dictionary. - Don't use
for/in
.
TIBET provides more powerful replacements, namely:
defineSubtype
andaddTraits
isType
,isKindOf
,isMemberOf
, etc.construct
(a pairing ofalloc
andinit
)get()
andset()
(method lookup and change notification)TP.core.Hash
(TP.hc
) and its collection API support- TIBET's
perform
,collect
,select
,detect
,reject
, orforEach
,map
, et. al.
By encapsulating keyword-based operations behind a method-based API TIBET is able to adjust to evolving standards, adapt to release-specific bugs, and capture critical metadata which supports advanced tooling and streamlined authoring.
Cookbook
Modules
TIBET doesn't implement a strict module construct although it does have a strong sense of load packages, groups of resources you configure to meet your application needs.
TIBET packages are not defined in JavaScript source code, they're described in package files which provide complete control over what constitutes each load package. Package definitions can be nested and automatically detect and eliminate circular or duplicate resource references. A resource can be referenced in as many packages as you like.
The TIBET Loader reads package definitions and loads the defined resources based on runtime data including whether you want minified source, concatenated source, source specific to a particular browser, language, user role, etc. You have fine-grained control over what loads for each individual user if necessary.
Don't use 'module' boilerplate in source files.
TIBET does not use constructs such as define
or require
. There's no
boilerplate that must be placed in your TIBET source files. Just enter your
code without ceremony.
// DON'T DO THIS IN TIBET
define([...], function() {
...code...
});
// DON'T DO THIS EITHER
require([...], function() {
...code...
});
// FOR SANITY'S SAKE DO NOT DO THIS:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('jquery'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
// methods
function myFunc(){};
// exposed public method
return myFunc;
}));
If you feel like using a simple closure to help scope things you can use a wrapper function, but typical TIBET source files don't contain any boilerplate whatsoever.
// DO THIS
...code...;
// OR, AT MOST, DO THIS
(function() {
...code...
}());
// BUT DO NOT DO THIS
(function() {
// NO...module.exports won't be found.
module.exports = ...code...
}());
Seriously, just type in your code.
The only dependencies in typical TIBET source are that subtypes should be loaded after their supertype. That's an easy order to maintain in your package files.
Define modules in TIBET Loader package files.
The TIBET Loader uses external package definitions which allow you to control exactly how your application resources will be rolled up and vended to the client.
Default package configurations which load the TIBET library and your application
code are provided with all TIBET project dna
. Your application resources are
typically defined in a package named after your project in ~app_cfg
, your
application config directory.
TIBET CLI commands such as the tibet tag
command (which automatically adds
your new tags to the current application's package configuration) help ease
maintenance and allow you to leverage your package configuration data to drive
linting, testing, etc.
See the tibet package
and TIBET Loader documentation for more detail.
Types
How you define your types, attributes, and methods has a huge impact on your results.
Because it's such a critical part of the coding process and has such a dramatic impact on functionality TIBET takes control of type definition via strict APIs.
Use defineSubtype()
, not Function instance.
Typical JavaScript development creates new types as instances of Function but Function-based types don't inherit behavior, restricting inheritance to instances only.
// DON'T DO THIS IN TIBET
function Demo() { ... }
// DON'T DO THIS EITHER
var Demo = function() { ... };
In TIBET you use the defineSubtype()
call. Just pick the desired supertype object
and message it. You know, like it was an object with behavior and stuff ;).
TP.lang.Object.defineSubtype('APP.demo.Demo');
TP.lang.Object
is TIBET's root Object type so it's a common starting point.
The TIBET library has a number of others specific to controllers, models, and
tags.
Use defineAttribute()
, not constructor/prototype properties.
Common JS practice defines properties on Function-based types by putting them inside the constructor function or via direct prototype assignment. Don't do that.
// DON'T DO THIS IN TIBET
function Demo() {
this.fluffy = null;
};
// DON'T DO THIS EITHER
function Demo() {};
Demo.prototype.fluffy = null;
In TIBET use the defineAttribute
method. This method creates a new attribute
with appropriate object descriptor values on a targeted object.
// DEFINE A TYPE ATTRIBUTE (INHERITED BY SUBTYPES)
APP.demo.Demo.Type.defineAttribute('fluffy');
// DEFINE A LOCAL ATTRIBUTE (NOT INHERITED)
APP.demo.Demo.defineAttribute('fluffy');
// DEFINE AN INSTANCE ATTRIBUTE
APP.demo.Demo.Inst.defineAttribute('fluffy');
While not shown above you can pass an optional default value as the second
parameter or an object property descriptor. See the documentation on
defineAttribute
for more.
Use defineMethod()
, not constructor/prototype properties.
In common JS development methods are just attributes whose values happen to be functions. As with attributes, don't define methods as properies in TIBET.
// DON'T DO THIS IN TIBET
function Demo() {
this.isFluffy = function() {
...
};
};
// DON'T DO THIS EITHER
function Demo() {};
Demo.prototype.isFluffy = function() {
...
};
In TIBET use the defineMethod
method. This approach creates a new method with
appropriate metadata and inheritance behavior.
// DEFINE A TYPE (.Type) METHOD (INHERITED BY SUBTYPES)
APP.demo.Demo.Type.defineMethod('isFluffy', function() { ... });
// DEFINE A LOCAL (No .Type or .Inst) METHOD (NOT INHERITED)
APP.demo.Demo.defineMethod('isFluffy', function() { ... });
// DEFINE AN INSTANCE (.Inst) METHOD.
APP.demo.Demo.Inst.defineMethod('isFluffy', function() { ... });
As with attribute definition note that the object being messaged determines where the method ends up and how it is inherited. TIBET's OO system supports inheritance of both type and instance behavior similar to Ruby or Smalltalk.
Use init()
not constructor function body.
If you're used to using functions as constructors you're probably used to putting constructor logic in the function body:
// NOT IN TIBET!
var NewType = function(a, b) {
...
this.fluff();
...
};
In TIBET override the instance (.Inst
) method init
instead:
TP.demo.Demo.Inst.defineMethod('init', function(a, b) {
// Invoke supertype version of 'init'.
this.callNextMethod();
// Do the local instance initialization work.
this.set('x', a);
this.set('y', b);
// Return the new instance...or nothing to default.
});
Use callNextMethod()
, not HardCodedType.method.call()
.
Many OO implementations in JavaScript require you to hard-code calls to
supertype versions of overridden methods. In TIBET you simply call
callNextMethod
.
// DON'T DO THIS
Demo.prototype.doX = function () {
// Maintenance headache ahead....
SuperDemo.prototype.doX.call(this, arguments);
}
TIBET allows you to invoke "the next method" using callNextMethod
:
TP.demo.Demo.Inst.defineMethod('doX', function(param) {
this.callNextMethod();
console.log('Y');
});
Note that:
- We don't mention a supertype at any point in the overridden method.
- We don't have to pass
arguments
to getparam
passed for us. - The "next method" might be an
.Inst
method if a local one exists. - The "next method" may traverse a variety of types due to trait resolution.
Use TP.isType()
, not typeof === 'function'
.
Using typeof === 'function' doesn't answer the real question since any function
could be used as a constructor if you put new
in front if it. While you might
combine typeof
with a name check to see if the function's name starts with an
uppercase letter there's effectively no way in JavaScript to be sure a function
instance is a type per se.
// DON'T WASTE YOUR TIME
if (typeof(NewType) === 'function') {
// Test the name for Titlecase...if it has one...useless.
}
In TIBET you should always use the TP.isType()
call:
TP.isType(obj);
This method will return true if the obj
serves as a TIBET or native type.
summary
In short, inheritance and reflection in JavaScript are either limited or broken depending on your viewpoint. Trying to use native reflection with TIBET's multi-track inheritance system only makes things worse. As you'll discover, TIBET provides Smalltalk/Ruby-level OO and reflection infrastructure, infrastructure you should use rather than native JS.
See the documentation for TIBET OO for more examples.
Instances
We've already seen some of the implications of using Function instances as types, and how they can negatively impact functionality and maintainability. When it comes to instances of those Function-based types there are similar issues that can arise.
Use construct()
, not new
, to create instances.
As mentioned earlier, JavaScript "types" are simply instances of Function. To
create a new instance of a classic JS type you use the new
keyword. That won't
work in TIBET.
// INCORRECT...throws an error.
var newInst = new App.demo.Demo();
Since TIBET types are not functions you can't use the new
operator on a
TIBET type. You have to message the type using construct()
to create new
instances.
// CORRECT...newInst is a new instance with full OO capability.
var newInst = App.demo.Demo.construct();
Parameters you pass to construct
are passed to the instance's init
method.
var newInst = NewType.construct(3, false);
As an aside, prior to calling init
on the instance, construct
calls alloc
on the type. You can override any of these methods, construct
, alloc
, and
init
, to control how your types create new instances (for example returning a
singleton instance).
Use get()
not this.x
.
For the longest time JavaScript didn't support common
getter/setter syntax. TIBET worked around this limitation by implementing
set()
and get()
functions.
Of course, to really leverage get/set
syntax, we couldn't leave them as simple
wrappers for direct property access…that would be boring ;).
TIBET's get()
function includes features like method/path lookup and access
path traversal which make it much more powerful than direct property
access.
// DO THIS
var foo = newInst.get('foo');
// NOT THIS!
var foo = newInst.foo;
TIBET's get
call first checks the property name for path characters. You might
have said get('foo.bar')
, or get('foo[1,3]')
, or any number of things which
signify an 'access path' to TIBET. Access paths give you a way to limit
boilerplate accessor creation.
Assuming you're just looking for a simple path TIBET uses reflection to see if a
getter exists. If so it forwards to it. Using our previous foo
slot as an
example, as soon as we implement getFoo()
TIBET's get()
call will begin
using it.
The get
call will also try to find an "aspect path", essentially a mapping
from a complex path to a simple name. For example, if you suddenly define foo
as a path to lastname[0]
TIBET will run that path in response to get('foo')
calls. This feature lets you use a common get()
API but remap the paths should
your underlying data structure need to change (your JSON or XML shifts format).
If you were to use get('foo.bar')
TIBET converts that into
get('foo').get('bar')
and runs the same lookup process for each step.
Each of these features is designed to improve maintenance by allowing you to remap how a particular property is accessed without changing the calling code.
Use set()
not this.x = y
.
The other half of encapsulation is protecting instance properties from being set
to inappropriate or incorrect values. The set()
call gives you a way to do
that and, like TIBET's get()
call, it performs method/path lookup and can
process access paths to accomplish its task of updating object state.
// DO THIS:
newInst.set('foo', 2);
// NOT THIS:
newInst.foo = 2; // Change notification?
As with TIBET's get()
call you can see how this is very similar on the
surface to the functionality of native setters, however since it's a method you
get inherited behavior that can be very compelling…behavior like change
notification.
Automatic Change Notification
As you may have read elsewhere, TIBET is a heavily event-driven system. One
of the key aspects of signaling in TIBET is change notification. Through the
set()
call most objects can serve as a 'model' in an MVC pattern. This is one
reason TIBET doesn't have a special 'Model' type, virtually all objects can be
models in TIBET.
When you set()
a value TIBET checks to ensure the value is actually changing.
If so it automatically creates a Change signal of the proper form and triggers
it.
The thing about Change notification is that sometimes you want to set multiple values in a sequence and only trigger Change notification when you're done with them all. You need control. You need a flag. Which means you can't use a native setter since you can't pass anything but a value to one of them. Having a real method is better.
// THIS:
// It gives us a way to have a flag to tell Change notification not to fire -
// the 'false' third parameter.
newInst.set('foo', 2, false);
// NOT THIS:
newInst.foo = 2; // Where does the other parameter go?
If you need to set a lot of values in sequence and don't want to have to pass
'false' for the third parameter each time, you can temporarily switch off change
notification from just that object via the .shouldSignalChange()
method
that all TIBET-based objects inherit:
// Turn it off.
newInst.shouldSignalChange(false);
// Do a lot of 'set's
newInst.set('foo', 2);
newInst.set('bar', 3);
newInst.set('baz', 4);
...
// Turn it back on and signal manually.
newInst.shouldSignalChange(true);
newInst.changed();
Use TP.isKindOf()
, not instanceof
.
Because TIBET types aren't instances of Function the standard JavaScript 'reflection' keywords don't work on instances of TIBET types.
// DON'T TRY THIS...it won't work
if (obj instanceof NewType) { ... };
TIBET's TP.isKindOf()
call is used for full ancestor-chain checking. The
object can be anywhere below the type:
TP.isKindOf(obj, TypeOrTypeName);
You can use TP.isKindOf()
on native objects and TIBET will do its best to
provide an accurate answer.
Use TP.isMemberOf()
, not constructor
checks.
As with instanceof
checks, constructor
checks don't work on most TIBET
objects. If you know TIBET internals you might make it work -- until we change
them.
// DON'T DO THIS...it won't work.
if (obj.constructor === NewType) { ... };
TIBET's isMemberOf()
is a 'direct instance' check meaning it returns true only
if the instance was built by invoking construct
on the type in question.
TP.isMemberOf(obj, TypeOrTypeName);
Like TP.isKindOf()
, you can use TP.isMemberOf()
on native JS objects as
well.
Use TP.tname()
, not typeof
.
The native JavaScript typeof
operator doesn't do what you want if what you
want is to know what type of object you have in OO terms. Instead it tells you
what the primitive JavaScript type is: 'object', 'number', 'string', 'boolean',
or 'function'.
That's not what we want 95% of the time and the other 5% is questionable practice.
// INCORRECT
typeof(TP.sys.getApplication()); // => 'object' => useless.
If you must know the name of the current type you can ask via TP.tname()
.
// CORRECT
TP.tname(TP.sys.getApplication()); // => 'APP.demo.Application'.
Note that if you use TP.tname()
on a TIBET type you'll get back an
answer that might surprise you. TIBET types are instances of a special set of
objects called metatypes. These metatype objects are arranged in a parallel
hierarchy to the standard type hierarchy and found under the TP.meta
and
APP.meta
namespaces.
If we use TP.tname()
on a TIBET type we'll get back the name of the metatype:
TP.tname(TP.demo.Demo); // => TP.meta.demo.Demo
All of this may seem strange/wrong until you recall that asking a native type (Array for example) what its type is will result in 'Function', a similar response that highlights that native types aren't instances of themselves either.
TP.tname(Array); // => Function
If you want to know the name of the current type just use TP.name()
instead:
TP.name(TP.demo.Demo); // => TP.demo.Demo
As with virtually all of TIBET's reflection methods you can use TP.tname()
on
native JS objects and TIBET will try to 'do the right thing'.
Collections
One of the more unfortunate oversights in the original design of JavaScript is the absence of a true Hash or Dictionary type. As a result it's common practice to use raw object instances as dictionaries, a practice that can create numerous problems.
Using a raw object as a dictionary 'works' in the same rough fashion that copying slots onto objects 'works' for inheritance. It seems ok at the time, but it severely limits you and creates a lot of landmines to dodge at scale.
Use TP.hc()
, not {}
, for dictionaries.
The TP.hc()
function creates an instance of TP.lang.Hash
, a true dictionary
type whose keys are not mixed with those of Object.prototype
. With a
TP.lang.Hash
you don't need to use hasOwnProperty
to be sure of your
key/value pairs.
var dict = TP.hc('a', 1, 'b', 2);
// OR:
var dict = TP.hc({ a: 1, b: 2 });
By using TP.hc()
you can message a true Hash type via a number of other useful
methods to get keys, values, pairs, counts, etc. Note that TP.hc()
is just a
shorthand for TP.lang.Hash.construct()
… you didn't want to do all that
typing anyway, right? ;-)
Parameter Lists
A common practice in JavaScript is to pass raw objects as parameter lists.
Unfortunately, while most JS developers have learned that you need to use
hasOwnProperty()
when iterating, few use it with parameters.
// Assume somewhere this happened...
Object.prototype.remove = function() { ... };
// OUCH:
var bar = function(params) {
if (params.remove) {
// Delete stuff...
}
};
bar({x: 1});
...stuff deletes thanks to 'remove' being a real slot...
You're certainly free to argue extending Object.prototype
is a horrible idea
but the simple fact is the language allows it. Better to write defensive code
than to spend hours tracking down some obscure bug.
// BETTER - TIBET only exposes 'remove' as a key in the hash.
var bar = function(params) {
if (params.at('remove')) {
// Delete stuff...
}
};
bar(TP.hc('x', 1));
We'll make one more argument for using TP.hc()
rather than {}
for
dictionaries. It's future-proof/future-enhancing.
If you write all your code using {}
then when new types such as Set, Map,
WeakMap, etc. arrive your existing code won't leverage them. If you use
TP.hc()
it's highly likely that when a native hash-like type arrives it will
form the underlying implementation and your TIBET hash objects will suddenly get
a performance boost.
Encapsulation rocks, whether it's at the property or object level.
Use perform()
, not for/in
, to iterate.
The for/in
construct is a great way to iterate over an object, provided you
verify all the keys are local keys. Unfortunately too many developers overlook
this step.
var obj = {};
// WORKS, but too many developers leave off the hasOwnProperty test.
for (i in obj) {
if (obj.hasOwnProperty(i)) {
// do stuff with i
}
}
Unfortunately, a problem comes up when you try to use for/in
on an Array,
something that we've seen numerous times:
var arr = [1, 2, 3];
for (i in arr) {
// NOT what most developers expect. Only the 'indexes' if Array.prototype
// hasn't been modified.
}
With the advent of native iterators such as forEach
, map
, filter
, some
,
etc. you can avoid this problem by eliminating use of for/in
on Array
instances. Unfortunately, those methods don't currently work on Object
instances.
TIBET provides iterators that work on all true collection types
(see the discussion on TP.core.Hash). These iterators come
in both internal (perform
, select
, detect
, collect
, reject
,
injectInto
) and external (collection.getIterator()
) form.
var arr,
hash;
arr = [1, 2, 3];
hash = TP.hc({a: 1, b: 2, c: 3});
// Think forEach
hash.perform(function(item, index) {
// item here is an ordered pair (['a', 1], ['b', 2], and so on)
});
// Works only on indexes, regardless of Array.prototype extensions.
arr.perform(function(item, index) {
// item here is content of slot (1, 2, 3, and so on)
}
Use remove
, not delete
.
The delete
operator shouldn't be used with TIBET collections. And you're not
going to be using raw objects as hashes, right? ;) Since TIBET's collections
manage their data independently under the covers the delete
operation will
fail.
var dict = TP.hc('age', 45, 'fname', 'Joe', ...);
// INCORRECT...dict.age won't exist anyway.
delete(dict.age);
// CORRECT
dict.removeKey('age');
In addition to handling deletion of a key, TIBET's hash type can also remove by value, remove all values, remove only particular key/value pairs, etc. It's a true collection, not an Object pressed into servitude as a poor substitute.
Comparisons
Object comparisons in JavaScript are subject to a number of unpredictable quirks. Some are a natural side-effect of how JavaScript treats certain values or performs type conversion. But the real point is you need clear semantics.
Use TP.isValid()
or TP.notValid()
for boolean checks.
Most JavaScript source is heavily sprinkled with conditionals which rely on the semantics of JavaScript Boolean type conversions. For the most part these tests work but they can often disguide hard-to-find bugs. We recommend using clear test functions instead.
// YES!
if (TP.isValid(params)) {
// Use params...
}
// NO!
if (params) {
}
The TP.isValid
call ensures the value is not null
, not undefined
, and not
NaN
. The result is your semantics are consistent and predictable.
Use TP.isNull()
or TP.notNull
.
Checking for null values, or the absence of a null, is a common operation, one common enough that we found it useful to create clear test functions.
Use TP.isDefined
or TP.notDefined()
.
As with null
, checking for undefined
(or the absence of it) is clearer when
using clearly named test functions.
Use TP.equal()
, not ==
.
JavaScript's ==
operator does type conversion and as a result is subject to a
number of quirks and unexpected results. As a result it's often recommended you
replace ==
with ===
but that's not always a semantically equivalent test,
identity isn't equality.
To perform a true "equality" check TIBET offers a the TP.equal
method which
leverages reflection and type-specialized methods to perform value comparsions.
// CORRECT
if (TP.equal(a, b)) { ... };
// INCORRECT
if (a == b) { ... };
TIBET's TP.equal()
function leverages the types of the objects as well as
knowledge of various quirks to provide a semantically clean answer to the
question of whether two objects are 'equal'. As new types are added to the
system methods invoked by 'equal' can ensure they compare appropriately.
Consider TP.identical()
rather than ===
.
For native object comparisons ===
is usually acceptable since an identity
check is pretty straightforward. For locations in your code where you may want
to spoof identity however, such as when using a proxy, the TP.identical
call
can be useful.
// CORRECT...you can spoof identity as needed for testing, proxies, etc.
if (TP.identical(a, b)) { ... };
// MOSTLY CORRECT...but not spoofable.
if (a === b) { ... };
summary
TIBET is the result of almost 20 years of hands-on experience building desktop-class applications in JavaScript; experience that led us to encapsulate many core features of the language behind APIs we can rely on to grant a higher level of control.
This document has presented the key differences you'll encounter in writing code for the TIBET platform relative to writing standard JavaScript. Almost all fall into two basic camps: always use methods and avoid using raw objects as collections.
Keywords considered harmful. Avoid if possible.
As you may have noticed, TIBET essentially removes new
, typeof
,
instanceof
, for/in
, and delete
from your JavaScript lexicon. Don't use
them in TIBET.
The first three aren't useful because they don't work well with TIBET's powerful combination of multi-track OO and composition via Traits, the latter two fall short because they don't work consistently or properly with a true dictionary type.
ES6 keywords for types (class
, static
, extends
) don't appear to add much
value to TIBET code. ES6 classes don't provide the OO + Traits power of TIBET so
while you're free to use them the resulting classes won't benefit from any of TIBET's
advances and will continue to exhibit the same bugs/limitations of prior
versions of the language.
ES6 module keywords like import
and export
seem useful on the surface but
it's not clear how well they'll work in practice. For example, things you don't
export are things you can't test. We'd prefer to test everything and let a
lint-like process check for things like violations of encapsulation.
Prefer methods over operators where possible.
The quirks inherent in JavaScript's implicit and explicit type conversions lead
to confusion and hard-to-track bugs. We recommend focusing on semantic methods
that encapsulate that logic and give you the means to adapt to newly discovered
quicks with a minimum of pain, methods like TP.isValid()
, TP.isType()
, etc.
Raw objects aren't dictionaries. Don't use them as such.
There are times when, yes, it'd be nice to have sugar for hash as nice as {}
.
Unfortunately overloading poor Object
with root-of-hierarchy and 'hash'
responsibilities turns out to be a stellar way to end up having it perform
both jobs poorly.
Since our priority is on the overall platform and not sugar for a single type we went with providing a real Hash type and suitable collection APIs.
Coding Standards
Our simple recommendation is "have one"…and enforce it with tooling.
For TIBET's version see TIBET Coding Standards.