I'm not sure of the best approach for handling scoping of "this" in TypeScript.
Here's an example of a common pattern in the code I am converting over to TypeScript:
class DemonstrateScopingProblems {
private status = "blah";
public run() {
alert(this.status);
}
}
var thisTest = new DemonstrateScopingProblems();
// works as expected, displays "blah":
thisTest.run();
// doesn't work; this is scoped to be the document so this.status is undefined:
$(document).ready(thisTest.run);
Now, I could change the call to...
$(document).ready(thisTest.run.bind(thisTest));
...which does work. But it's kinda horrible. It means that code can all compile and work fine in some circumstances, but if we forget to bind the scope it will break.
I would like a way to do it within the class, so that when using the class we don't need to worry about what "this" is scoped to.
Any suggestions?
Update
Another approach that works is using the fat arrow:
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
alert(this.status);
}
}
Is that a valid approach?
You have a few options here, each with its own trade-offs. Unfortunately there is no obvious best solution and it will really depend on the application.
Automatic Class Binding
As shown in your question:
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
alert(this.status);
}
}
Good/bad: This creates an additional closure per method per instance of your class. If this method is usually only used in regular method calls, this is overkill. However, if it's used a lot in callback positions, it's more efficient for the class instance to capture the this context instead of each call site creating a new closure upon invoke.
Good: Impossible for external callers to forget to handle this context
Good: Typesafe in TypeScript
Good: No extra work if the function has parameters
Bad: Derived classes can't call base class methods written this way using super.
Bad: The exact semantics of which methods are "pre-bound" and which aren't create an additional non-typesafe contract between your class and its consumers.
Function.bind
Also as shown:
$(document).ready(thisTest.run.bind(thisTest));
Good/bad: Opposite memory/performance trade-off compared to the first method
Good: No extra work if the function has parameters
Bad: In TypeScript, this currently has no type safety
Bad: Only available in ECMAScript 5, if that matters to you
Bad: You have to type the instance name twice
Fat arrow
In TypeScript (shown here with some dummy parameters for explanatory reasons):
$(document).ready((n, m) => thisTest.run(n, m));
Good/bad: Opposite memory/performance trade-off compared to the first method
Good: In TypeScript, this has 100% type safety
Good: Works in ECMAScript 3
Good: You only have to type the instance name once
Bad: You'll have to type the parameters twice
Bad: Doesn't work with variadic parameters
Another solution that requires some initial setup but pays off with its invincibly light, literally one-word syntax is using Method Decorators to JIT-bind methods through getters.
I've created a repo on GitHub to showcase an implementation of this idea (it's a bit lengthy to fit into an answer with its 40 lines of code, including comments), that you would use as simply as:
class DemonstrateScopingProblems {
private status = "blah";
#bound public run() {
alert(this.status);
}
}
I haven't seen this mentioned anywhere yet, but it works flawlessly. Also, there is no notable downside to this approach: the implementation of this decorator -- including some type-checking for runtime type-safety -- is trivial and straightforward, and comes with essentially zero overhead after the initial method call.
The essential part is defining the following getter on the class prototype, which is executed immediately before the first call:
get: function () {
// Create bound override on object instance. This will hide the original method on the prototype, and instead yield a bound version from the
// instance itself. The original method will no longer be accessible. Inside a getter, 'this' will refer to the instance.
var instance = this;
Object.defineProperty(instance, propKey.toString(), {
value: function () {
// This is effectively a lightweight bind() that skips many (here unnecessary) checks found in native implementations.
return originalMethod.apply(instance, arguments);
}
});
// The first invocation (per instance) will return the bound method from here. Subsequent calls will never reach this point, due to the way
// JavaScript runtimes look up properties on objects; the bound method, defined on the instance, will effectively hide it.
return instance[propKey];
}
Full source
The idea can be also taken one step further, by doing this in a class decorator instead, iterating over methods and defining the above property descriptor for each of them in one pass.
Necromancing.
There's an obvious simple solution that doesn't require arrow-functions (arrow-functions are 30% slower), or JIT-methods through getters.
That solution is to bind the this-context in the constructor.
class DemonstrateScopingProblems
{
constructor()
{
this.run = this.run.bind(this);
}
private status = "blah";
public run() {
alert(this.status);
}
}
You can write an autobind method to automatically bind all functions in the constructor of the class:
class DemonstrateScopingProblems
{
constructor()
{
this.autoBind(this);
}
[...]
}
export function autoBind(self)
{
for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
{
const val = self[key];
if (key !== 'constructor' && typeof val === 'function')
{
// console.log(key);
self[key] = val.bind(self);
} // End if (key !== 'constructor' && typeof val === 'function')
} // Next key
return self;
} // End Function autoBind
Note that if you don't put the autobind-function into the same class as a member function, it's just autoBind(this); and not this.autoBind(this);
And also, the above autoBind function is dumbed down, to show the principle.
If you want this to work reliably, you need to test if the function is a getter/setter of a property as well, because otherwise - boom - if your class contains properties, that is.
Like this:
export function autoBind(self)
{
for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
{
if (key !== 'constructor')
{
// console.log(key);
let desc = Object.getOwnPropertyDescriptor(self.constructor.prototype, key);
if (desc != null)
{
if (!desc.configurable) {
console.log("AUTOBIND-WARNING: Property \"" + key + "\" not configurable ! (" + self.constructor.name + ")");
continue;
}
let g = desc.get != null;
let s = desc.set != null;
if (g || s)
{
var newGetter = null;
var newSetter = null;
if (g)
newGetter = desc.get.bind(self);
if (s)
newSetter = desc.set.bind(self);
if (newGetter != null && newSetter == null) {
Object.defineProperty(self, key, {
get: newGetter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
else if (newSetter != null && newGetter == null) {
Object.defineProperty(self, key, {
set: newSetter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
else {
Object.defineProperty(self, key, {
get: newGetter,
set: newSetter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
continue; // if it's a property, it can't be a function
} // End if (g || s)
} // End if (desc != null)
if (typeof (self[key]) === 'function')
{
let val = self[key];
self[key] = val.bind(self);
} // End if (typeof (self[key]) === 'function')
} // End if (key !== 'constructor')
} // Next key
return self;
} // End Function autoBind
In your code, have you tried just changing the last line as follows?
$(document).ready(() => thisTest.run());
Related
I am exporting the following ES6 class from one module:
export class Thingy {
hello() {
console.log("A");
}
world() {
console.log("B");
}
}
And importing it from another module:
import {Thingy} from "thingy";
if (isClass(Thingy)) {
// Do something...
}
How can I check whether a variable is a class? Not a class instance, but a class declaration?
In other words, how would I implement the isClass function in the example above?
If you want to ensure that the value is not only a function, but really a constructor function for a class, you can convert the function to a string and inspect its representation. The spec dictates the string representation of a class constructor.
function isClass(v) {
return typeof v === 'function' && /^\s*class\s+/.test(v.toString());
}
Another solution would be to try to call the value as a normal function. Class constructors are not callable as normal functions, but error messages probably vary between browsers:
function isClass(v) {
if (typeof v !== 'function') {
return false;
}
try {
v();
return false;
} catch(error) {
if (/^Class constructor/.test(error.message)) {
return true;
}
return false;
}
}
The disadvantage is that invoking the function can have all kinds of unknown side effects...
I'll make it clear up front here, any arbitrary function can be a constructor. If you are distinguishing between "class" and "function", you are making poor API design choices. If you assume something must be a class for instance, no-one using Babel or Typescript will be be detected as a class because their code will have been converted to a function instead. It means you are mandating that anyone using your codebase must be running in an ES6 environment in general, so your code will be unusable on older environments.
Your options here are limited to implementation-defined behavior. In ES6, once code is parsed and the syntax is processed, there isn't much class-specific behavior left. All you have is a constructor function. Your best choice is to do
if (typeof Thingy === 'function'){
// It's a function, so it definitely can't be an instance.
} else {
// It could be anything other than a constructor
}
and if someone needs to do a non-constructor function, expose a separate API for that.
Obviously that is not the answer you are looking for, but it's important to make that clear.
As the other answer here mentions, you do have an option because .toString() on functions is required to return a class declaration, e.g.
class Foo {}
Foo.toString() === "class Foo {}" // true
The key thing, however, is that that only applies if it can. It is 100% spec compliant for an implementation to have
class Foo{}
Foo.toString() === "throw SyntaxError();"
No browsers currently do that, but there are several embedded systems that focus on JS programming for instance, and to preserve memory for your program itself, they discard the source code once it has been parsed, meaning they will have no source code to return from .toString() and that is allowed.
Similarly, by using .toString() you are making assumptions about both future-proofing, and general API design. Say you do
const isClass = fn => /^\s*class/.test(fn.toString());
because this relies on string representations, it could easily break.
Take decorators for example:
#decorator class Foo {}
Foo.toString() == ???
Does the .toString() of this include the decorator? What if the decorator itself returns a function instead of a class?
Checking the prototype and its writability should allow to determine the type of function without stringifying, calling or instantiating the input.
/**
* determine if a variable is a class definition or function (and what kind)
* #revised
*/
function isFunction(x) {
return typeof x === 'function'
? x.prototype
? Object.getOwnPropertyDescriptor(x, 'prototype').writable
? 'function'
: 'class'
: x.constructor.name === 'AsyncFunction'
? 'async'
: 'arrow'
: '';
}
console.log({
string: isFunction('foo'), // => ''
null: isFunction(null), // => ''
class: isFunction(class C {}), // => 'class'
function: isFunction(function f() {}), // => 'function'
arrow: isFunction(() => {}), // => 'arrow'
async: isFunction(async function () {}) // => 'async'
});
There are subtle differences between a function and a class, and we can take this advantage to distinguish between them, the following is my implementation:
// is "class" or "function"?
function isClass(obj) {
// if not a function, return false.
if (typeof obj !== 'function') return false;
// ⭐ is a function, has a `prototype`, and can't be deleted!
// ⭐ although a function's prototype is writable (can be reassigned),
// it's not configurable (can't update property flags), so it
// will remain writable.
//
// ⭐ a class's prototype is non-writable.
//
// Table: property flags of function/class prototype
// ---------------------------------
// prototype write enum config
// ---------------------------------
// function v . .
// class . . .
// ---------------------------------
const descriptor = Object.getOwnPropertyDescriptor(obj, 'prototype');
// ❗functions like `Promise.resolve` do have NO `prototype`.
// (I have no idea why this is happening, sorry.)
if (!descriptor) return false;
return !descriptor.writable;
}
Here are some test cases:
class A { }
function F(name) { this.name = name; }
isClass(F), // ❌ false
isClass(3), // ❌ false
isClass(Promise.resolve), // ❌ false
isClass(A), // ✅ true
isClass(Object), // ✅ true
What about:
function isClass(v) {
return typeof v === 'function' && v.prototype.constructor === v;
}
This solution fixes two false positives with Felix's answer:
It works with anonymous classes that don't have space before the class body:
isClass(class{}) // true
It works with native classes:
isClass(Promise) // true
isClass(Proxy) // true
function isClass(value) {
return typeof value === 'function' && (
/^\s*class[^\w]+/.test(value.toString()) ||
// 1. native classes don't have `class` in their name
// 2. However, they are globals and start with a capital letter.
(globalThis[value.name] === value && /^[A-Z]/.test(value.name))
);
}
const A = class{};
class B {}
function f() {}
console.log(isClass(A)); // true
console.log(isClass(B)); // true
console.log(isClass(Promise)); // true
console.log(isClass(Promise.resolve)); // false
console.log(isClass(f)); // false
Shortcomings
Sadly, it still won't work with node built-in (and probably many other platform-specific) classes, e.g.:
const EventEmitter = require('events');
console.log(isClass(EventEmitter)); // `false`, but should be `true` :(
Well going through some of the answers and thinks to #Joe Hildebrand for highlighting edge case thus following solution is updated to reflect most tried edge cases. Open to more identification where edge cases may rest.
Key insights: although we are getting into classes but just like pointers and references debate in JS does not confirm to all the qualities of other languages - JS as such does not have classes as we have in other language constructs.
some debate it is sugar coat syntax of function and some argue other wise. I believe classes are still a function underneath but not so much so as sugar coated but more so as something that could be put on steroids. Classes will do what functions can not do or didn't bother upgrading them to do.
So dealing with classes as function for the time being open up another Pandora box. Everything in JS is object and everything JS does not understand but is willing to go along with the developer is an object e.g.
Booleans can be objects (if defined with the new keyword)
Numbers can be objects (if defined with the new keyword)
Strings can be objects (if defined with the new keyword)
Dates are always objects
Maths are always objects
Regular expressions are always objects
Arrays are always objects
Functions are always objects
Objects are always objects
Then what the heck are Classes?
Important Classes are a template for creating objects they are not object per say them self at this point.
They become object when you create an instance of the class somewhere, that instance is considered an object.
So with out freaking out we need to sift out
which type of object we are dealing with
Then we need to sift out its properties.
functions are always objects they will always have prototype and arguments property.
arrow function are actually sugar coat of old school function and have no concept of this or more the simple return context so no prototype or arguments even if you attempt at defining them.
classes are kind of blue print of possible function dont have arguments property but have prototypes. these prototypes become after the fact object upon instance.
So i have attempted to capture and log each iteration we check and result of course.
Hope this helps
'use strict';
var isclass,AA,AAA,BB,BBB,BBBB,DD,DDD,E,F;
isclass=function(a) {
if(/null|undefined/.test(a)) return false;
let types = typeof a;
let props = Object.getOwnPropertyNames(a);
console.log(`type: ${types} props: ${props}`);
return ((!props.includes('arguments') && props.includes('prototype')));}
class A{};
class B{constructor(brand) {
this.carname = brand;}};
function C(){};
function D(a){
this.a = a;};
AA = A;
AAA = new A;
BB = B;
BBB = new B;
BBBB = new B('cheking');
DD = D;
DDD = new D('cheking');
E= (a) => a;
F=class {};
console.log('and A is class: '+isclass(A)+'\n'+'-------');
console.log('and AA as ref to A is class: '+isclass(AA)+'\n'+'-------');
console.log('and AAA instance of is class: '+isclass(AAA)+'\n'+'-------');
console.log('and B with implicit constructor is class: '+isclass(B)+'\n'+'-------');
console.log('and BB as ref to B is class: '+isclass(BB)+'\n'+'-------');
console.log('and BBB as instance of B is class: '+isclass(BBB)+'\n'+'-------');
console.log('and BBBB as instance of B is class: '+isclass(BBBB)+'\n'+'-------');
console.log('and C as function is class: '+isclass(C)+'\n'+'-------');
console.log('and D as function method is class: '+isclass(D)+'\n'+'-------');
console.log('and DD as ref to D is class: '+isclass(DD)+'\n'+'-------');
console.log('and DDD as instance of D is class: '+isclass(DDD)+'\n'+'-------');
console.log('and E as arrow function is class: '+isclass(E)+'\n'+'-------');
console.log('and F as variable class is class: '+isclass(F)+'\n'+'-------');
console.log('and isclass as variable function is class: '+isclass(isclass)+'\n'+'-------');
console.log('and 4 as number is class: '+isclass(4)+'\n'+'-------');
console.log('and 4 as string is class: '+isclass('4')+'\n'+'-------');
console.log('and DOMI\'s string is class: '+isclass('class Im a class. Do you believe me?')+'\n'+'-------');
shorter cleaner function covering strict mode, es6 modules, null, undefined and what ever not property manipulation on object.
What I have found so far is since from above discussion classes are blue print not as such object on their own before the fact of instance. Thus running toString function would almost always produce class {} output not [object object] after the instance and so on. Once we know what is consistent then simply run regex test to see if result starts with word class.
"use strict"
let isclass = a =>{
return (!!a && /^class.*{}/.test(a.toString()))
}
class A {}
class HOO {}
let B=A;
let C=new A;
Object.defineProperty(HOO, 'arguments', {
value: 42,
writable: false
});
console.log(isclass(A));
console.log(isclass(B));
console.log(isclass(C));
console.log(isclass(HOO));
console.log(isclass());
console.log(isclass(null));
console.log(HOO.toString());
//proxiy discussion
console.log(Proxy.toString());
//HOO was class and returned true but if we proxify it has been converted to an object
HOO = new Proxy(HOO, {});
console.log(isclass(HOO));
console.log(HOO.toString());
console.log(isclass('class Im a class. Do you believe me?'));
Lead from DOMI's disucssion
class A {
static hello (){console.log('hello')}
hello () {console.log('hello there')}
}
A.hello();
B = new A;
B.hello();
console.log('it gets even more funnier it is properties and prototype mashing');
class C {
constructor() {
this.hello = C.hello;
}
static hello (){console.log('hello')}
}
C.say = ()=>{console.log('I said something')}
C.prototype.shout = ()=>{console.log('I am shouting')}
C.hello();
D = new C;
D.hello();
D.say();//would throw error as it is not prototype and is not passed with instance
C.say();//would not throw error since its property not prototype
C.shout();//would throw error as it is prototype and is passed with instance but is completly aloof from property of static
D.shout();//would not throw error
console.log('its a whole new ball game ctaching these but gassumption is class will always have protoype to be termed as class');
I'm shocked lodash didnt have the answer. Check this out - Just like Domi I just came up with a solution to fix the glitches. I know its a lot of code but its the most working yet understandable thing I could produce by now. Maybe someone can optimize it by a regex-approach:
function isClass(asset) {
const string_match = "function";
const is_fn = !!(typeof asset === string_match);
if(!is_fn){
return false;
}else{
const has_constructor = is_fn && !!(asset.prototype && asset.prototype.constructor && asset.prototype.constructor === asset);
const code = !asset.toString ? "" : asset.toString();
if(has_constructor && !code.startsWith(string_match)){
return true;
}
if(has_constructor && code.startsWith(string_match+"(")){
return false;
}
const [keyword, name] = code.split(" ");
if(name && name[0] && name[0].toLowerCase && name[0].toLowerCase() != name[0]){
return true;
}else{
return false;
}
}
}
Just test it with:
console.log({
_s:isClass(String),
_a:isClass(Array),
_o:isClass(Object),
_c:isClass(class{}),
fn:isClass(function(){}),
fnn:isClass(function namedFunction(){}),
fnc:isClass(()=>{}),
n:isClass(null),
o:isClass({}),
a:isClass([]),
s:isClass(""),
n:isClass(2),
u:isClass(undefined),
b:isClass(false),
pr:isClass(Promise),
px:isClass(Proxy)
});
Just make sure all classes have a frist uppercased letter.
Maybe this can help
let is_class = (obj) => {
try {
new obj();
return true;
} catch(e) {
return false;
};
};
I was reading up on some discussions about extending native objects in javascript. Extending native objects in current browsers seem to have much less disadvantages than they used to. Certainly considering we have more control about how objects are extended through methods such as:
Object.defineProperty(<some parameters...>)
A big risk that however remains is that of collision between different code/libraries, resulting in unexpected behavior.
Risks of naming collision (and global clobbering) can be reduced by namespacing your own functions. So I thought why do we not do this if we extend the native object? Of course execution context is a problem but we can fix that with bind(), making native functions of that object available in our extended functionality. So i created the following to extend the Element object:
// Define the extend function
Object.defineProperty(Element.prototype, 'extend', { value: function(namespace, object) {
var cache = {};
// Create the namespace and make sure on invocation the Element is bound
Object.defineProperty(Element.prototype, namespace, { value: function() {
var objectCheck = typeof cache[Element.prototype.constructor.name] !== 'undefined';
if(objectCheck && typeof cache[Element.prototype.constructor.name][namespace] !== 'undefined'){
console.log('cache used');
return cache[Element.prototype.constructor.name][namespace];
} else {
var extended = Element.prototype[namespace].extended;
for (var e in extended) {
extended[e] = extended[e].bind(this);
}
if(!objectCheck)
cache[Element.prototype.constructor.name] = {};
cache[Element.prototype.constructor.name][namespace] = extended;
}
return extended;
}});
this[namespace].extended = object;
return this;
}});
// Extend the Element prototype with an attr function in
// the namespace 'namespace' or ofcourse whatever function
Element.prototype.extend('namespace',{
attr: function(name, value) {
if(arguments.length === 1) {
return this.getAttribute(name);
} else {
this.setAttribute(name, value);
return this;
}
}
});
It all looks good when check it out on an actual element, the namespace is there and inside it we find our 'attr' function. We can invoke it like this:
document.querySelector('.a-css-class').namespace().attr('class')
// returns 'a-css-class'
The code could be refactored further to dynamically extend all sorts of objects. However I'm curious, what could this mean for performance, does this experiment make sense? The main question, is this any better than extending directly?
EDIT (based on Bergi's comments regarding performance):
It would be possible to cache the created function in the outer function. Let's see if I can come up with an implementation.
EDIT 2:
Added a simple caching function to make sure not all namespaced methods are create each en every invocation.
EDIT 3:
The updated code. In an attempt to make extending native object safer, the following code was produced. It might prevent naming collision. Thanks #Bergi:
/**
* Extend a (NATIVE) object with a namespace. See below for an example
*/
Object.defineProperty(Object.prototype, 'extend', { value: function(namespace, object) {
function Wrapper(that) {
this.that = that;
}
Wrapper.prototype = object;
Object.defineProperty(Object.prototype, namespace, { value: function() {
return new Wrapper(this);
}});
}});
// This example uses the Element object, but it could be any object
Element.prototype.extend('namespace',{
attr: function(name, value) {
if(arguments.length === 1) {
return this.that.getAttribute(name);
} else {
this.that.setAttribute(name, value);
return this;
}
}
});
However I'm curious, what could this mean for performance
Your current code means that whenever you call namespace(), you create a new object and lots of bound functions, which will impact performance quite heavily for large namespaces.
I would recommend to let your namespace method return a wrapper object with an element: this property, which inherits all the methods that can be called on it. You'd use this.element.getAttribute then instead of this.getAttribute:
Element.prototype.extend = function(namespace, object) {
function Wrapper(el) {
this.element = el;
}
Wrapper.prototype = object;
Element.prototype[namespace] = function() {
return new Wrapper(this);
};
};
The main question, is this any better than extending directly?
Not much. You still are defining extend and namespace properties on the Element.prototype, and for these two properties all the arguments against extending the DOM are still valid. You might lower the risk of collisions with more common names (attr), but it's not better than just defining a namespace_attr method.
implement change prototype, so why not just directory change it with 'some class'.prototype..., It seems very useless or palaver
There are two aspects to implement. In a Class context and on Objects (Types/Natives).
Common benefits
Common between them is, they are API. Because it's API, MooTools can add ways that ensure that you don't (accidentally) overwrite prototype methods that are protected on various Native types. See this bit which forces protection for many native methods: https://github.com/mootools/mootools-core/blob/master/Source/Core/Core.js#L238-282
Also, implement is overloaded - which means you can pass on an object that implements more than one method at the same time, rather than break and have a new line for each prototype call.
Function Methods in Classes are wrapped.
These decorators are available:
Function.implement({
hide: function(){
this.$hidden = true;
return this;
},
protect: function(){
this.$protected = true;
return this;
}
});
This means you can do
Obj.implement({
foo: foo.hide(),
bar: bar.protect()
});
This allows you to have non-wrapped methods via .hide() to add properties that are not wrapped:
Number.prototype.$family = function(){
return isFinite(this) ? 'number' : 'null';
}.hide();
.protect() is used by Class, see below.
This does not mean you can't do Number.prototype.$family = somethingElse - you can.
Specifics to Class
When using implement on a class constructor, notice reference to $hidden and call to wrap():
var implement = function(key, value, retain){
if (Class.Mutators.hasOwnProperty(key)){
value = Class.Mutators[key].call(this, value);
if (value == null) return this;
}
if (typeOf(value) == 'function'){
if (value.$hidden) return this;
this.prototype[key] = (retain) ? value : wrap(this, key, value);
} else {
Object.merge(this.prototype, key, value);
}
return this;
};
This means when you pass a function, it will get wrapped automatically - wrapping enables you to have private methods via the .protect() decorator or special ones via .hide()
It also supports Class Mutators - you should check this but it's ability to define special keys that can modify the constructor, as opposed to just add to the prototype.
Once again, you can easily do:
ClassConstructor.prototype.foo = someFn;
And this will work.
// but:
ClassConstructor.prototype.foo = someFn.protect();
// however...
instanceOfClassconstructor.foo(); // works also.
see this:
// generic method.
var foo = function() {
console.log('hi');
};
var ClassConstructor = new Class({});
// implement the foo method as private.
ClassConstructor.implement({
foo: foo.protect()
});
var instance = new ClassConstructor();
// try to call it, will throw.
try {
instance.foo();
}
catch(e) {
console.warn(e);
}
// do it directly on the prototype
ClassConstructor.prototype.foo = foo.protect();
var instance2 = new ClassConstructor();
instance2.foo(); // works.
You can look at MooTools source or inspect a MooTools object in your console to see what it is doing under the hood when implement() is called.
In MooTools 1.4.5, it's doing this:
function (key, value){
if ($type(key) == 'object'){
for (var p in key) this.implement(p, key[p]);
return this;
}
var mutator = Class.Mutators[key];
if (mutator){
value = mutator.call(this, value);
if (value == null) return this;
}
var proto = this.prototype;
switch ($type(value)){
case 'function':
if (value._hidden) return this;
proto[key] = Class.wrap(this, key, value);
break;
case 'object':
var previous = proto[key];
if ($type(previous) == 'object') $mixin(previous, value);
else proto[key] = $unlink(value);
break;
case 'array':
proto[key] = $unlink(value);
break;
default: proto[key] = value;
}
return this;
}
As you can see, there is definitely some extra logic in there. For example, it seems that you can pass objects with corresponding property names for key and value to have it implement add multiple things to the object prototype in one call.
I would use implement() and avoid adding things directly to the prototype chain unless you are very sure you know what you're doing. That extra stuff is there for a reason, no doubt.
// Don't break the function prototype.
// pd - https://github.com/Raynos/pd
var proto = Object.create(Function.prototype, pd({
"prop": 42
}));
var f = function() { return "is a function"; };
f.__proto__ = proto;
console.log(f.hasOwnProperty("prop")); // false
console.log(f.prop); // 42
console.log(f()); // "is a function"
.__proto__ is non-standard and deprecated.
How am I supposed to inherit prototypically creating an object but having that object be a function.
Object.create returns an Object not a Function.
new Constructor returns an Object not a Function.
Motivation: - A cross-browser finherit
var finherit = function (parent, child) {
var f = function() {
parent.apply(this, arguments);
child.apply(this, arguments);
};
f.__proto__ = parent;
Object.keys(child).forEach(function _copy(key) {
f[key] = child[key];
});
return f;
};
I don't believe this is possible, so we should probably propose a Function.create to the es-discuss mailing list
/*
Creates a new function whose prototype is proto.
The function body is the same as the function fbody.
The hash of propertydescriptors props is passed to defineproperties just like
Object.create does.
*/
Function.create = (function() {
var functionBody = function _getFunctionBody(f) {
return f.toString().replace(/.+\{/, "").replace(/\}$/, "");
};
var letters = "abcdefghijklmnopqrstuvwxyz".split("");
return function _create(proto, fbody, props) {
var parameters = letters.slice(0, fbody.length);
parameters.push(functionBody(fbody));
var f = Function.apply(this, parameters);
f.__proto__ = proto;
Object.defineProperties(f, props);
return f;
};
})();
Related es-discuss mail
As mentioned in the es-discuss thread there exists a ES:strawman <| prototype operator which would allow for this.
Let's see what it would look like using <|
var f1 = function () {
console.log("do things");
};
f1.method = function() { return 42; };
var f2 = f1 <| function () {
super();
console.log("do more things");
}
console.log(f1.isPrototypeOf(f2)); // true
console.log(f2()); // do things do more things
console.log(f2.hasOwnProperty("method")); // false
console.log(f2.method()); // 42
I hope that I'm understanding this right.
I believe you want a functor that's both an instance of a predefined prototype (yes, a class, just not a classic class) as well as directly callable? Right? If so, then this makes perfect sense and is very powerful and flexible (especially in a highly asynchronous environment like JavaScript). Sadly there is no way to do it elegantly in JavaScript without manipulating __proto__. You can do it by factoring out an anonymous function and copying all of the references to all of the methods (which seems to be the direction you were heading) to act as a proxy class. The downsides to this are...
It's very costly in terms of runtime.
(functorObj instanceof MyClass) will never be true.
Properties will not be directly accessible (if they were all assigned by reference this would be a different story, but primitives are assigned by value). This can be solved with accessors via defineProperty or simply named accessor methods if necessary (it appears that that is what you're looking for, just add all properties to the functor with defineProperty via getters/setters instead of just functions if you don't need cross-engine support/backwards compatability).
You're likely to run into edge cases where final native prototypes (like Object.prototype or Array.prototype [if you're inheriting that]) may not function as expected.
Calling functorObj(someArg) will always make the this context be the object, regardless of if it's called functorObj.call(someOtherObj, someArg) (this is not the case for method calls though)
Because the functor object is created at request time, it will be locked in time and manipulating the initial prototype will not affect the allocated functor objects like a normal object would be affected (modifying MyClass.prototype will not affect any functor objects and the reverse is true as well).
If you use it gently though, none of this should be a big deal.
In your prototype of your class define something like...
// This is you're emulated "overloaded" call() operator.
MyClass.prototype.execute = function() {
alert('I have been called like a function but have (semi-)proper access to this!');
};
MyClass.prototype.asFunctor = function(/* templateFunction */) {
if ((typeof arguments[0] !== 'function') && (typeof this.execute !== 'function'))
throw new TypeError('You really should define the calling operator for a functor shouldn\'t you?');
// This is both the resulting functor proxy object as well as the proxy call function
var res = function() {
var ret;
if (res.templateFunction !== null)
// the this context here could be res.asObject, or res, or whatever your goal is here
ret = res.templateFunction.call(this, arguments);
if (typeof res.asObject.execute === 'function')
ret = res.asObject.execute.apply(res.asObject, arguments);
return ret;
};
res.asObject = this;
res.templateFunction = (typeof arguments[0] === 'function') ? arguments[0] : null;
for (var k in this) {
if (typeof this[k] === 'function') {
res[k] = (function(reference) {
var m = function() {
return m.proxyReference.apply((this === res) ? res.asObject : this, arguments);
};
m.proxyReference = reference;
return m;
})(this.asObject[k]);
}
}
return res;
};
Resulting usage would look something like...
var aobj = new MyClass();
var afunctor = aobj.asFunctor();
aobj.someMethodOfMine(); // << works
afunctor.someMethodOfMine(); // << works exactly like the previous call (including the this context).
afunctor('hello'); // << works by calling aobj.execute('hello');
(aobj instanceof MyClass) // << true
(afunctor instanceof MyClass) // << false
(afunctor.asObject === aobj) // << true
// to bind with a previous function...
var afunctor = (new MyClass()).asFunctor(function() { alert('I am the original call'); });
afunctor() // << first calls the original, then execute();
// To simply wrap a previous function, don't define execute() in the prototype.
You could even chain bind countless other objects/functions/etc until the cows came home. Just refactor the proxy call a bit.
Hope that helps. Oh, and of course you could change the factory flow so that a constructor called without the new operator then instantiates a new object and returns the functor object. However you prefer (you could surely do it other ways too).
Finally, to have any function become the execution operator for a functor in a bit more elegant of a manner, just make the proxy function a method of Function.prototype and pass it the object to wrap if you want to do something like (you would have to swap templateFunction with this and this with the argument of course)...
var functor = (function() { /* something */ }).asFunctor(aobj);
With ES6 it's possible to inherit from Function, see (duplicate) question
javascript class inherit from Function class
default export Attribute extends Function {
...
}
we currently learn some Javascript stuff in a course at the university.
For that we implement a library for common tasks like show(), hide(), write and such things.
Currently im running with an implementation like:
var myLib_maker = function () {
/*
private scope
*/
var debuggingMode=true;
var currentElement=null;
/*
end private scope
*/
return {
getElement: function (id) {
var o;
if (typeof id === 'string') { o = document.getElementById(id); }
if (!!(o && o.nodeType !== 1)) {
throw {
name: 'Type Error',
message: 'Wrong node type at id: '+id
}
}
currentElement=o;
return this;
},
getCurrentElement: function() {
console.log(currentElement)
return currentElement;
},
isVisible: function () {
return this.getCurrentElement().style.display.toLowerCase() === "block";
},
show: function () {
this.debug("show "+this.getCurrentElement())
this.getCurrentElement().style.display = "block";
return this;
},
hide: function () {
this.debug("hide "+this.getCurrentElement())
this.getCurrentElement().style.display = "none";
return this;
},
toggle: function() {
this.debug("toggle "+this.getCurrentElement())
this.isVisible() ? this.hide(): this.show();
return this;
},
write: function (content){
this.debug("write to"+this.getCurrentElement().id);
var tg = this.getCurrentElement().tagName.toLowerCase();
if (tg === 'input' || tg === 'textarea') {
currentElement.value = content;
} else {
currentElement.innerHTML = content;
}
return this
},
debug: function (what) {
if (debuggingMode===true){
console.log("[DEBUG] "+what);
}
return this;
}
};
}
var myLib=myLib_maker();
Than I have an external function (for testing) to switch 2 textareas contents.
function switchEditors(id1, id2){
c1=myLib.getElement(id1).getCurrentElement().value;
c2=myLib.getElement(id2).getCurrentElement().value;
myLib.getElement(id1).write(c2)
myLib.getElement(id2).write(c1)
}
I first tried with the following code, which obviously does not work, cause I overwrite my private currentElement and so I write always to id2
function switchEditors(id1, id2){
tmp=myLib.getElement(id1).getCurrentElement().value
myLib.getElement(id1).write(myLib.getElement(id2).getCurrentElement().value)
myLib.getElement(id2).write(tmp)
}
But what I really wanted initially was not using a private currentElement variable.
The first implementation of the write method extended the Element Object
Element.prototype.write= function (content){
var tg = this.tagName.toLowerCase();
if (tg === 'input' || tg === 'textarea') {
this.value = content;
} else {
this.innerHTML = content;
}
return this;
}
and such the getElement function returned
document.getElementById(id)
I want cascading (I hope this is the right word -> I mean the myLib.getElement("myid").show().hide() concatenation thing) and getting direct access to
all Element attributes but we must not use global scope for our library, so I have to encapsulate my library in any way.
So is there an elegant way to use the cascading thing and be able to get a direct access to all attributes on an element object without implementing each method within the global element scope?
Or is my lib desing completely wrong and has to be done totally different.
If so, just tell me, I appreciate any help.
(I tried to figure out how jQuery actually implement these things, but didn't get a real clue how it is done ... too much code ... :) )
I hope I described my wishes and requirements. If not please ask for more specific details.
As you've figured out, the currentElement is shared between calls to getElement. Instead you could create a new instance of myLib-object with Object.create and bind currentElement to that.
getElement: function (id) {
var o, self = Object.create(this);
/* ... */
self.currentElement = o;
return self;
}
And use this.currentElement throughout so that each call uses its own current element.
While Magnar's solution will work with this (singleton) pattern, it is a better idea to avoid creating a whole new object each time you call getElement. There is a reason for creating "classes" instead of singletons.
You can do it like this:
var MyLib_Maker = (function () { // I capitalized the class as a helpful
// convention recommended by Douglas Crockford
// Private static vars
var debuggingMode = true;
var currentElement = null;
// Private static methods
function _myMethod (x, y) { // call below by _myMethod(3,4);
return x * y;
}
// Private instance methods (but only if called properly:
// invoke below by _instMethod.call(this, 3, 4); )
function _instMethod (x, y) {
return this.anInstanceNumber * x * y;
}
// Private instance properties (quite cumbersome but doable if you
// absolutely must--e.g., for classes whose objects need to be clean when iterated)
// See http://brettz9.blogspot.com/2009/02/true-private-instance-variables-in.html
// and http://brettz9.blogspot.com/2009/02/further-relator-enhancements.html
// (put the Relator inside the closure if you don't want it reusable (and public),
// but then your extending classes must be inside the closure too)
function MyLib_Maker (arg1, arg2) {
// I recommend the following check to allow your class to be
// instantiated without the 'new' keyword (as in jQuery/$):
if (!(this instanceof MyLib_Maker)) {
return new MyLib_Maker(arg1, arg2);
}
// Other constructor code here
// ...
}
// Methods added on the prototype benefit from merely
// providing a low-memory reference across all instances;
// this will avoid adding a whole new object unnecessarily
// into memory
MyLib_Maker.prototype.getElement = function () {
// ....
return this; // Keep the chain going (if not public
// properties, you could add a method which also avoids
// extending the chain, like $(el).get() in jQuery
};
return MyLib_Maker;
}()); // We can invoke immediately to wrap up the closure
// Usage example:
var mlm = MyLib_Maker(2, 3).getElement().doSomething();
By the way, what you describe is called chaining; cascading is used in the likes of CSS to indicate that like different waves out of a waterfall, one may write over the other, as you can do by writing rules which override prior ones in CSS.
And it is good you moved away from overriding the Element object because, whatever the browser incompatibilities, this is the worst kind of global namespace pollution because it affects all elements, increasing the chance that another library which depends on that method (or which is careless in overriding the built-in prototypes itself) may get you unexpected results.