Skip to content

Instantly share code, notes, and snippets.

@AmyAmy
Forked from lukescott/gist:36453a75c39c539f5c7d
Last active February 11, 2024 23:59
Show Gist options
  • Save AmyAmy/68ca8c52ec6d92da6206201e4def97d4 to your computer and use it in GitHub Desktop.
Save AmyAmy/68ca8c52ec6d92da6206201e4def97d4 to your computer and use it in GitHub Desktop.
Traits in ES6 now
import babelHelpers from "babelHelpers";
/* eslint no-underscore-dangle: ["error", { "allow": ["__traits__"] }] */
/**
* @author based on https://gist.github.com/lukescott/36453a75c39c539f5c7d
* @example
* var Trait1 = {
* method1() {}
* };
*
* var Trait2 = {
* method2() {}
* };
*
* var Trait3 = addTraits({
* method3() {}
* }, Trait2);
*
* hasTrait(Trait3, Trait2) // true
* hasTrait(Trait3, Trait1) // false
*
* @example
* class Base {}
* class Foo extends withTraits(Base, Trait1) {}
*
* hasTrait(Foo, Trait1) // true
* hasTrait(Foo, Trait2) // false
*
* @example
* class Foo2 extends Foo {}
*
* hasTrait(Foo2, Trait1) // true
* hasTrait(Foo2, Trait2) // false
*
* @example
* class Foo3 extends withTraits(Foo, Trait2) {}
*
* hasTrait(Foo3, Trait1) // true
* hasTrait(Foo3, Trait2) // true
*
* @example
* class Bar extends withTraits(Base, Trait3) {}
*
* hasTrait(Bar, Trait1) // false
* hasTrait(Bar, Trait2) // true
* hasTrait(Bar, Trait3) // true
*
* @example
* var Trait4 = {
* constructor() {
* this.prop = 4;
* },
* method4() {
* console.log(this.prop);
* }
* }
* class Baz extends withTraits(Base, Trait4) {}
* new Baz().method4(); // 4
*/
/**
* Add traits into an Object (another trait)
* @param {Object} object The object (can be another trait) to add traits to.
* @param {Array.<Object>} traits An array of traits to add.
* @returns {Object} The original object, now with the added traits.
*/
function addTraits(object, ...traits) {
const t = [];
if (object.__traits__) {
Array.prototype.push.apply(t, object.__traits__);
}
const props = {
__traits__: {
value: t,
writable: true,
configurable: true
}
};
for (let trait of traits) {
if (trait.__traits__) {
Array.prototype.push.apply(t, trait.__traits__);
}
for (let name of Object.getOwnPropertyNames(trait)) {
if (name === "constructor") {
continue;
}
if (name === "__traits__") {
continue;
}
if (object.hasOwnProperty(name)) {
continue;
}
props[name] = {
value: trait[name],
writable: true,
configurable: true
};
}
t.push(trait);
}
Object.defineProperties(object, props);
return object;
}
/**
* Adds traits to a class.
* @param {Function} baseClass The class to add the traits to.
* @param {Array.<Object>} traits One or more traits to add to the class.
* @returns {Function} A class which extends baseClass, and has all the specified traits.
* @example class Foo extends withTraits(Base, Trait1) {}
*/
function withTraits(baseClass, ...traits) {
let TraitedClass;
if (typeof baseClass === "undefined") {
TraitedClass = class {
constructor() {
for (let trait of traits) {
if (trait.constructor) {
trait.constructor.apply(this, arguments);
}
}
}
};
}
else {
TraitedClass = class extends baseClass {
constructor() {
super(...arguments);
for (let trait of traits) {
if (trait.constructor) {
trait.constructor.apply(this, arguments);
}
}
}
};
}
addTraits.apply(this, [TraitedClass.prototype, ...traits]);
return TraitedClass;
}
/**
* Checks to see if a class or instance has a trait
* @param {Function|Object} object The class or instance to check.
* @param {Object} trait The trait to check.
* @returns {Boolean} True if the object has the specified trait.
*/
function hasTrait(object, trait) {
const traits = (typeof object === "function") ? object.prototype.__traits__ : object.__traits__;
return Array.isArray(traits) && traits.indexOf(trait) >= 0;
}
/**
* Checks to see if a class or instance has all of the specified traits.
* @param {Function|Object} object The class or instance to check.
* @param {Object} trait One or more traits to check.
* @returns {Boolean} True if the object has all of the specified traits.
*/
function hasAllTraits(object, ...traits) {
return traits.every(trait => hasTrait(object, trait));
}
/**
* Checks to see if a class or instance has any of the specified traits.
* @param {Function|Object} object The class or instance to check.
* @param {Object} trait One or more traits to check.
* @returns {Boolean} True if the object has any of the specified traits.
*/
function hasAnyTraits(object, ...traits) {
return traits.some(trait => hasTrait(object, trait));
}
export default {
addTraits,
withTraits,
hasTrait,
hasAllTraits,
hasAnyTraits
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment