|
'use strict'; |
|
|
|
function instanceType(obj) { |
|
const typeOf = typeof obj; |
|
switch (typeOf) { |
|
case 'undefined': |
|
case 'number': // ironically also NaN |
|
case 'string': |
|
case 'boolean': |
|
case 'function': |
|
case 'symbol': |
|
return typeOf; |
|
} |
|
if (null === obj) { |
|
return 'null'; // FIXME: Is this correct? |
|
} |
|
// Symbol.toStringTag |
|
const stringified = Object.prototype.toString |
|
.call(obj) |
|
.match(/^\[object (.+)\]$/)[1]; // [object Object] // Object |
|
if ('Object' !== stringified) { |
|
return stringified; |
|
} |
|
|
|
if (obj.constructor && obj.constructor.name) { |
|
return obj.constructor.name; |
|
} |
|
// Note: `Object.create(null)` will not have a `constructor` property |
|
return stringified; |
|
} |
|
|
|
function top(stack) { |
|
return stack.split('\n')[1].match(/^\s+at ([^\s/]+)/)[1].split('.')[0]; // all stack frames // just the `Constructor.method` // just the `Constructor` |
|
} |
|
|
|
// A. Overwrite prototype |
|
function AnimalA() {} |
|
function DogA() {} |
|
DogA.prototype = /* ⬅︎ Don’t do this */ Object.create(AnimalA.prototype); // Will inherit from AnimalA.prototype |
|
DogA.prototype.speak = function speak() { |
|
return 'A: bark'; |
|
}; |
|
DogA.prototype.fetch = function fetch() { |
|
Error.captureStackTrace(this); |
|
return top(this.stack); |
|
}; |
|
const dogA = new DogA(); |
|
|
|
// A1: Same as A, but sets [Symbol.toStringTag] property |
|
function AnimalA1() {} |
|
function DogA1() {} |
|
DogA1.prototype = /* ⬅︎ Don’t do this */ Object.create(AnimalA1.prototype); |
|
DogA1.prototype.speak = function speak() { |
|
return 'A1: bark'; |
|
}; |
|
DogA1.prototype.fetch = function fetch() { |
|
Error.captureStackTrace(this); |
|
return top(this.stack); |
|
}; |
|
DogA1.prototype[Symbol.toStringTag] = 'DogA1'; // This overrides the behavior of Object.prototype.toString.call(dog) |
|
const dogA1 = new DogA1(); |
|
|
|
// B. Overwrite prototype, set subclass constructor |
|
function AnimalB() {} |
|
function DogB() {} |
|
DogB.prototype = Object.create(AnimalB.prototype); |
|
DogB.prototype.constructor = DogB; |
|
DogB.prototype.speak = function speak() { |
|
return 'B: bark'; |
|
}; |
|
DogB.prototype.fetch = function fetch() { |
|
Error.captureStackTrace(this); |
|
return top(this.stack); |
|
}; |
|
const dogB = new DogB(); |
|
|
|
// C. Extend prototype |
|
function AnimalC() {} |
|
function DogC() {} |
|
Object.assign(DogC.prototype, Object.create(AnimalC.prototype), { |
|
speak() { |
|
return 'C: bark'; |
|
}, |
|
fetch() { |
|
Error.captureStackTrace(this); |
|
return top(this.stack); |
|
} |
|
}); |
|
const dogC = new DogC(); |
|
|
|
// C1. Same as C, but instantiates instance with `Object.create()` instead of `new` |
|
function AnimalC1() {} |
|
function DogC1() {} |
|
Object.assign(DogC1.prototype, Object.create(AnimalC1.prototype), { |
|
speak() { |
|
return 'C: bark'; |
|
}, |
|
fetch() { |
|
Error.captureStackTrace(this); |
|
return top(this.stack); |
|
} |
|
}); |
|
const dogC1 = Object.create(DogC1.prototype); |
|
|
|
// D. Class |
|
class AnimalD {} |
|
class DogD extends AnimalD { |
|
speak() { |
|
return 'D: bark'; |
|
} |
|
fetch() { |
|
Error.captureStackTrace(this); |
|
return top(this.stack); |
|
} |
|
} |
|
const dogD = new DogD(); |
|
|
|
const instances = [dogA, dogA1, dogB, dogC, dogC1, dogD]; |
|
({ |
|
instanceType: instances.map(instanceType), |
|
stackLabel: instances.map(dog => dog.fetch()), |
|
optSc: instances.map(dog => Object.prototype.toString.call(dog)) |
|
}); |
|
|
|
/* |
|
{ |
|
"instanceType": [ |
|
"AnimalA", // This is because `DogA.prototype` doesn’t set the `constructor` property so it’s inheritied from `AnimalA` |
|
"DogA1", |
|
"DogB", |
|
"DogC", |
|
"DogC1", |
|
"DogD" |
|
], |
|
"stackLabel": [ |
|
"AnimalA", |
|
"AnimalA1", |
|
"DogB", |
|
"DogC", |
|
"DogC1", |
|
"DogD" |
|
], |
|
"optSc": [ |
|
"[object Object]", |
|
"[object DogA1]", |
|
"[object Object]", |
|
"[object Object]", |
|
"[object Object]", |
|
"[object Object]" |
|
] |
|
} |
|
*/ |