Subclassing ES6 Set in javascript - javascript

I'm running into problems when trying to inherit from the new Set available in ecmascript 6. The class is defined as such:
function SelectionManager () {
Set.call(this);
}
SelectionManager.prototype = Object.create(Set.prototype);
SelectionManager.prototype.add = function (unit) {
unit.setIsSelected(true);
Set.prototype.add.call(this, unit);
};
/* Some functions left out */
When trying to call add I get the following error: TypeError: Set operation called on non-Set object
The code is available at http://jsfiddle.net/6nq1gqx7/
The draft for ES6 clearly states that it should be possible to subclass Set, what is the correct way of doing so?

Looks like this is being tracked:
https://github.com/google/traceur-compiler/issues/1413

No correct way for now. Chrome/V8 (as most other browsers) does not yet properly support subclassing of builtins -- in part because it potentially is quite complicated to implement, and in part because the precise semantics are still wildly in flux, and have just been overthrown at the latest ES6 meeting, with no final resolution yet (as of October 2014).

In Chrome, Firefox, and Edge (But not IE11) you can Subclass ES6 Set and Map with this workaround, done with ES6 classes:
class MySet extends Set {
constructor() {
// instead of super(), you must write:
const self = new Set();
self.__proto__ = MySet.prototype;
return self;
}
// example method:
add(item) {
console.log('added!')
super.add(item); // Or: Set.prototype.add.call(this, item);
}
}
var mm = new MySet();
console.log(mm.size); // logs 0
mm.add(5); // logs 'added!'
console.log(mm.size); // logs 1

Related

Is it ever a good idea to append functions to a JavaScript Object Literal's __proto__ property?

I have an object literal:
var tasks = {};
That I basically add things to like so:
function addTask(task) {
tasks[task.id] = task
}
I want to modify that so that I can call a start function on each task. So:
var tasks = {};
tasks.__proto__.start = function(key) {
// do stuff with this[key]
}
function addTask(task) {
tasks[task.id] = task
tasks.start(task.id)
}
I've heard that it's best to avoid the proto object and that it slows down execution. However I'm not re-assigning it, I'm appending to it.
Is there an alternative that would be considered better?
There isn't really any need to use a prototype for this. You're not creating many instances where you need a common functionality abstracted at a higher level, you can just add a method on the tasks object.
const tasks = {
start(key) {
const task = this[key]
// do stuff with task
}
}
// example call
tasks.start('123');
If you want to make sure there's no clash with an existing key, you can use a Symbol instead.
const startSymbol = Symbol('start');
const tasks = {
[startSymbol](key) {
const task = this[key]
// do stuff with task
}
}
// example call
tasks[startSymbol]('123');
You could also just have a standalone function that does this, similarly to your addTask function:
function start(tasks, key) {
const task = tasks[key]
// do stuff with task
}
// example call
start(tasks, '123')
Having this standalone function is probably better because you won't have to worry about clashes between your task keys and method names.
You could also create a wrapper object that does this separation:
const taskManager = {
tasks: {} // map of key to task
// methods
add(task) {
this.tasks[task.id] = task;
this.start(task.id);
}
start(key) {
const task = this.tasks[key];
// do stuff with task
}
}
// example usage
taskManager.start('123')
The advantage of this approach is that your tasks are encapsulated within a container that manipulates on them, constricting the scope in which tasks should be used and making it more clear (suggesting to the programmer) which functions are meant to be used on the tasks.
If you plan on having multiple task managers, then using prototypes might make sense here:
class TaskManager {
constructor() {
this.tasks = {} // map of key to task
}
// methods
add(task) {
this.tasks[task.id] = task;
this.start(task.id);
}
start(key) {
const task = this.tasks[key];
// do stuff with task
}
}
// example usage
new TaskManager().start('123')
This is not a good idea from both a performance and browser compatibility perspective.
See these warnings from mozilla's documentation:
Warning: Changing the [[Prototype]] of an object is, by the nature of
how modern JavaScript engines optimize property accesses, a very slow
operation, in every browser and JavaScript engine. The effects on
performance of altering inheritance are subtle and far-flung, and are
not limited to simply the time spent in obj.proto = ... statement,
but may extend to any code that has access to any object whose
[[Prototype]] has been altered. If you care about performance you
should avoid setting the [[Prototype]] of an object. Instead, create a
new object with the desired [[Prototype]] using Object.create().
--
Warning: While Object.prototype.proto is supported today in most
browsers, its existence and exact behavior has only been standardized
in the ECMAScript 2015 specification as a legacy feature to ensure
compatibility for web browsers. For better support, it is recommended
that only Object.getPrototypeOf() be used instead.

Testing functionality based on HTML5 DOM-methods

I'm wondering how to test functionality based on HTML5 DOM-methods. Like the new dialog-API for example.
Consider the following function;
function show(dialog)
{
if(typeof(dialog.showModal) !== "function")
throw new Error("Update your browser");
dialog.showModal();
}
I want to make sure that
an error is thrown when the method is not available
dialog.showModal is called if available
I'm currently running karma + jasmine with the firefox and chrome launchers but none of them seems to support the dialog-API.
Can I shim this in some kind of way? Can I create a function on all dialog DOM nodes?
I could do the following
var modal = document.createElement("dialog");
Object.defineProperty(modal, "showModal", {
value: function()
{
console.log("hello world!");
}
});
however, that isn't carried over to other instances of a dialog.
This is a very simplified example of what I'm trying to achieve.
This is what polyfills are: an attempt to shim support for newer specs into browsers that don't support them. What you're talking about is essentially creating a polyfill for the <dialog> element.
Some older ECMAScript 6 specs specified a function to test if something existed and was a function, but this seems to have been scrapped in more recent specs. This is a "polyfill" for that (in whatever way that term can apply to a scrapped spec), both as an example of polyfilling and as a tool we'll use to explore the concept further:
(function () {
"use strict";
var ops = Object.prototype.toString;
Function.isFunction = function(o) {
return ops.call(o) === "[object Function]";
};
}());
Note that we have to cache the original Object.prototype.toString: most JavaScript runtimes override it in Function.prototype.toString, and we really need to be calling the original. This should give you an idea of how fiddly and error-prone polyfilling can be: even in a case like this where the code is short and simple, nuances like using Object.prototype.toString.call(o) instead of o.toString() are still critical.
Anyway, once you've got this function, it becomes much easier to test for things like modal support:
(function () {
"use strict";
var test = document.createElement("dialog");
if (!Function.isFunction(test.showModal)) {
throw new Error("HTMLDialogElement.showModal() is not supported");
};
}());
To modify all dialog element instances, you would extend the prototype that most immediately pertains to that object. In the case of the HTML dialog spec, that's HTMLDialogElement. You could do something like this:
(function () {
"use strict";
var HTMLDialogElement = HTMLDialogElement; //undefined if unsupported
if (!Function.isFunction(HTMLDialogElement)) {
// No native dialog support
throw new Error("No support for <dialog> elements");
}
if (!Function.isFunction(HTMLDialogElement.prototype.showModal)) {
// The interface exists, but showModal isn't supported
HTMLDialogElement.prototype.showModal = function () {
console.log("Hello world!");
};
}
}());
If there's no support for the prototype either, then things get trickier. An unknown element doesn't get a distinctive prototype; it's just an HTMLElement (like all other elements, eventually). So to make a function work for <dialog> elements in an unsupported environment, you'd have to add it to all elements. Still, you can sort of make it work:
(function () {
"use strict";
HTMLElement.prototype.showModal = function () {
if (this.tagName !== "DIALOG") {
throw new TypeError("showModal() called on non-dialog");
}
console.log("Hello world!");
};
}());
Most polyfills work based on compromises like this: they don't support the spec perfectly, but do well enough to support common use cases.
Be warned: polyfilling extends native objects, and this is commonly considered bad practice. Most people who call it bad practice will make an exception for polyfills, but the rule is still considered good even though polyfills are so useful. That should give you an idea of how complex and error-prone the task can be. This is black magic: not a task to be undertaken lightly.

Javascript Intellisense in Microsoft Visual Studio objects defined by custom code define / derive

Situation: using functions to declare your Classes
If you are using and declaring classes with some custom (or framework function) as WinJs does (check their open source git directory), you are certainly familiar with this kind of code:
function define(constructor, instanceMembers, staticMembers) { }
function derive(baseClass, constructor, instanceMembers, staticMembers) { }
define(function constructor(){
this.yourProperty = 1;
}, {
// Prototype object
somePrototypeFunction: function(){
// When you type "this." here, it will not show up "yourProperty" declared
// in the constructor, because you have not instanciated the class,
// intellisense does not know that everything is linked
}
}
Common problem on these "custom" functions
Intellisense does not show up the values declared within the constructor when you try to reach them from the prototype functions.
I found something that have helped me: http://social.msdn.microsoft.com/forums/windowsapps/en-US/3eee400a-fefd-4f5e-9109-68df03fef006/javascript-intellisense-with-this-inside-gettersetter
This leaded me to the solution that I share to you below, it was a pain to make it work, and actually I was about to ** AGAIN ** let go with that problem which was something really disapointing especially with big team projects.
I find it weird that there are not many complaints about this on the web, maybe it's a configuration problem? However I had that problem on all VSD installations I saw.
So I hope the following solution will help you too if you run into the same situation.
After a few hours I finally have a solution which is not perfect (I have handled .base like in C# in my javascript library, but with the following code I can't say to intellisense that this ".base(...) " exists in the context of the prototype functions and constructor). If you have any tip on how to do that let me know, I'm interested.
Tested on Visual Studio 2013.
Simply change window.define / window.derive to the namespace and name you actually use (for WinJs it would be WinJS.Class.define and WinJS.Class.derive).
Add in _references.js the relative path of the file where you will put the following code, just after your library
And that's all! You'll have intellisense inside your
(function (window) {
"use strict";
/*
* Goal: make intellisense understand that the constructor of your define/derive functions are linked to the prototype object you have supplied.
* Tested on WinJs library and other custom libraries.
* Save this in a file, and reference it only in _references.js, insert it after your library containing the define/derive functions
*/
function initIntellisenseFor(constructor, baseClass) {
var inst = new constructor();
// Force intellisense to run function
for (var key in inst) {
if (typeof inst[key] == 'function') {
try {
inst[key]();
} catch (e) {
// Silent fail if wrong arguments (not sure if needed)
}
}
}
// Force intellisense to discover constructor
inst.constructor = constructor;
// Missing: .base() implementation for each method with redirection to the appropriate parent class method
}
var oldDefine = window.define;
window.define = function (constructor, instanceMembers, staticMembers) {
var result = oldDefine.call(this, constructor, instanceMembers, staticMembers);
initIntellisenseFor(result);
return result;
};
var oldDerive = window.derive;
window.derive = function (baseClass, constructor, instanceMembers, staticMembers) {
var result = oldDerive.call(this, baseClass, constructor, instanceMembers, staticMembers);
initIntellisenseFor(result, baseClass);
return result;
};
})(this);

Dispatching custom events

I have version 0.9.0.1 of the typescript compiler installed in my VS 2012 Update 3.
I want to dispatch a custom event but the ambient variable declared in lib.d.ts does not expose the expected constructor signatures.
When I use
var myEvent = new CustomEvent("my:event:type", { detail: { some: "data"} });
window.dispatchEvent(myEvent);
the type script compiler complains, because according to him, only
var myEvent = new CustomEvent();
is correct.
The latter is erroneous according to Chrome 27 and Aurora 24.02, due to "missing arguments"
MDN also lists the actually-correct-but-not-for-typescript constructor signatures.
My thinking was now to add the known-correct constructor signature to the ambient variable declaration, but without touching the shipped lib.d.ts file. Would this technically be possible? I could not come up with the correct syntax for it, and the language specification did not mention how to merge two such declarations.
Alternatively, I simply edited lib.d.ts, which after a restart of the IDE provided me with the updated signature. Nevertheless, I'd rather not tamper with 3rd-party files in such a way.
Last, is there some other mechanism I (sh|c)ould use to write type script code that dispatches a custom event?
(Update: Restarting the IDE reloads lib.d.ts correctly. Also, corrected made-up event type name)
The reason this isn't part of lib.d.ts is that Internet Explorer doesn't support this object in IE10.
If the CustomEvent definition was purely an interface, you would be able to extend it thus:
interface CustomEvent {
new(eventType: string, data: {});
}
But the CustomEvent constructor is actually defined in a variable declaration, which you can't extend:
declare var CustomEvent: {
prototype: CustomEvent;
new(): CustomEvent;
}
You can use this (nasty looking) work around to get hold of custom events while you wait for Internet Explorer / the TypeScript library to be updated.
class StandardsCustomEvent {
static get(eventType: string, data: {}) {
var customEvent = <any>CustomEvent;
var event = new customEvent(eventType, data);
return <CustomEvent> event;
}
}
var x = StandardsCustomEvent.get("myevent", { detail: { some: "data"} });
This fixes your compiler warnings - but won't make it work in IE10 or older - you would need to polyfill.
I ended up doing all of this:
Use the --nolib option on build
Add a local copy of the lib.d.ts
Patch in the expected constructor signatures
Add a polyfill for IE <= 11 as proposed by MDN
var event = <CustomEvent>(new (<any>CustomEvent)('specifiedEvent'))
Might be a solution. Nice looking and tricky.

What is the Chrome console displaying with log()?

I think I may have found a bug with Google Chrome (16.0.912.75 m, the latest stable at the time of this writing).
var FakeFancy = function () {};
console.log(new FakeFancy());
var holder = {
assignTo : function (func) {
holder.constructor = func;
}
};
holder.assignTo(function () { this.type = 'anonymous' });
var globalConstructor = function () { this.type = 'global' }
console.log(new holder.constructor());
If you run that block in Firefox, it shows both listed as "Object" with the second having type = local, pretty good. But if you run it in Chrome, it shows
> FakeFancy
> globalConstructor.type
If you expand the trees, the contents are correct. But I can't figure out what Chrome is listing as the first line for each object logged. Since I'm not manipulating the prototypes, these should be plain old objects that aren't inheriting from anywhere.
At first, I thought it was WebKit related, but I tried in the latest Safari for Windows (5.1.2 7534.52.7) and both show up as "Object".
I suspect that it's attempting to do some guesswork about where the constructor was called from. Is the anonymous constructor's indirection messing it up?
The first line is a result of
console.log(new FakeFancy());
The WebKit console generally tries to do "constructor name inference" to let you know what type of object it's outputting. My guess is that the more recent version included with Chrome (as opposed to Safari 5.1) can do inference for constructor declarations like
var FakeFancy = function () {};
and not just ones like
function FakeFancy() {}
so that's why you're seeing the disparity.

Categories

Resources