So I am trying to learn javascript by learning how Mootools works internally. I am looking at these lines specifically:
var Type = this.Type = function(name, object){
if (name){
var lower = name.toLowerCase();
var typeCheck = function(item){
return (typeOf(item) == lower);
};
Type['is' + name] = typeCheck;
if (object != null){
object.prototype.$family = (function(){
return lower;
}).hide();
}
}
if (object == null) return null;
object.extend(this);
object.$constructor = Type;
object.prototype.$constructor = object;
return object;
};
//some more code
new Type('Type',Type);
What is happening here?
What is the object being assigned to in the constructor statement down at the bottom, the global window object?
Why is it being called as a constructor with the new statement when the Type function seems to only update the passed in object rather than creating a new one?
Specifically what is 'this' in the line object.extend(this); ? is it the global window object, if so is it adding all the key, value pairs of that object to the Type object?
Sheesh, questions lately seem to focus a lot more on mootools internals.
I will answer to the best of my knowledge as I am not a core dev.
Type in MooTools is pretty similar to Class (in fact, the Class constructor itself is a Type) but it focuses more on data / values and the Types of values. It also is meant to extend the native Types defined by ECMA spec and make them more flexible.
I guess there is no point in talking about data types in general (String, Number, Array, Object, etc). Why is there are need to extend them? Well, for starters, Duct Typing is a little quirky in js. typeof {}; // object, typeof []; // object, typeof new Date(); // object etc - not the most helpful, even if since all types inherit from object and is logical for them to be grouped together, it does not help you write code.
So, in the context of js objects, they are created from constructor objects...
What Type does is not replace the constructor functions but changes an existing constructor by adding new methods or properties to it.
eg. new Type('Array', Array);
This will turn the native Array constructor into a type object of sorts. You don't need to save the result or anything - it's a one off operation that mods the original and leaves it open for manipulation.
typeOf(Array); // type
So, what is different? Well, for starters, typeOf([]) is now able to actually tell us what it really is - array. And what really happens here is this: object.extend(Type);, the magical bit. It will copy to the target object all the properties defined on the Type object - you can see them here:
https://github.com/mootools/mootools-core/blob/master/Source/Core/Core.js#L211-232
So, immediately, your newly created Type gets the all important implement and extend methods.
More advanced, let's create a new type that is based on the native Array constructor:
var foo = new Type('foo', Array),
a = new foo();
// it's a real boy!
a.push('hi');
// ['hi'], foo, object
console.log(a, typeOf(a), typeof a);
But, what if we wanted a custom Type? One that is magical and special? No problem, because argument 2 can actually be a (anonymous) function.
var Awesome = new Type('awesome', function(name, surname) {
// console.log('I R teh construct0r!');
this.name = name;
this.surname = surname;
});
// extend it a little.
Awesome.implement({
getName: function() {
return this.name;
},
getSurname: function() {
return this.surname;
}
});
var Dimitar = new Awesome('dimitar', 'christoff');
console.log(typeOf(Dimitar)); // awesome
console.log(Dimitar.getName()); // dimitar
In another example, look at DOMEvent. It takes the above and makes it into a faster leaner Object type. https://github.com/mootools/mootools-core/blob/master/Source/Types/DOMEvent.js#L21
So why is that not a class? Because, Classes are more expensive and events happen all the time.
I hope this helps you somewhat. for a more detailed explanation, ask on the mailing list and hope for the best, perhaps a core dev will have time as it's not your standard 'how do I get the accordion working' type of question...
Related
Is there any difference in how these functions operate? The first one is more typically of what I think about when thinking of a constructor.
Example 1: using this to name and set properties. Then using new to create a new Book object.
function Book(name, numPages) {
this.name = name;
this.numPages = numPages;
}
var myBook = new Book('A Good Book', '500 pages');
Example 2: returning a object by using new and just calling the function itself.
function Movie(name, numMinutes) {
return { name:name, numMinutes:numMinutes };
}
var best = new Movie('Forrest Gump', '150');
var other = Movie('Gladiator', '180');
I guess what I'm trying to figure out is if these are different in the way they create an object? If so is one better than the other? Are there different situations where one would work better over the other?
The first one is a constructor, and can therefore be extended by a prototype, and you can test via instanceof wether the result is an Instance of this type.
Downside: if you forget the new-keyword your code will blow up (unless you write a workaround for that into each constuctor)
And you can't really use apply() with a constructor to pass an array of arguments, when you instantiate a new Object; on the other hand, don't do that, even if you can/could.
The second one is a factory, not a constructor. Independant wether you use the new-keyword or not.
with this implementation it creates Objects that look the same but don't share a type or prototype (although the underlying JS-engine recognizes them as similar and so they share the same hidden Class as long as they have the same properties, added in the same order, ... different topic)
long story short, neither performance nor memory-footprint suffer from this approach (anymore)
But you can't check wether they are of the same type, and you don't have a shared prototype that may affect all instances (maybe a pro or a con.)
My goto-approach If I need inheritance, is kind of a mix of both:
(if I just need a data-object I usually use a factory and plain objects).
function Book(conf) {
var book = Object.create(Book.prototype);
//a book like this usually has multiple configs/properties
if(typeof conf === "object"){
for(var k in conf) book[k] = conf[k];
}else if(conf){
//assuming that I get at least the name passed
book.name = String(conf);
}
return book;
}
//I have a prototype that can be extended
//with default-values for example; no idea for a good method
//to add to the prototype in this example ;)
Book.prototype.numPages = 0;
//but I can also use it like a plain function; no error if you
var myBook1 = Book("Peter Pan");
var myBook2 = Book({
name: "American Gods",
author: "Neil Gaiman"
});
If I add the following line to the top of the function I can also use that as a method to cast anything into an Instance of Book without cloning already existing instances
function Book(conf) {
//with this simple line I can also use this as a function to cast anything into a "Book"
if(conf instanceof Book) return conf;
var book = Object.create(Book.prototype);
//...
return book;
}
var data = [
"Peter Pan",
{name: "American Gods"},
//...
];
var books = data.map(Book);
In my opinion, I have the benefits of both worlds with this approach.
Basically, when you use new, the JS engine makes a brand new object for you and injects that as the value of this. It also automatically gives you any methods attach to the prototype of the constructor. Using a constructor also allows you to check if an object is an instanceof something more easily.
function MovieA(title) {
this.title = title;
}
MovieA.prototype.getTitle = function() {
return this.title;
};
function MovieB(title) {
return {
title: title
};
}
MovieB.prototype.getTitle = function() {
return this.title;
};
var a = new MovieA('A');
console.log(a instanceof MovieA); // true
console.log(a.getTitle()); // 'A'
var b = MovieB('B');
console.log(b instanceof MovieB); // false
console.log(b.getTitle()); // Uncaught TypeError: b.getTitle is not a function
Everything that new offers you can be attained through other methods but requires more manual labor.
The second method, factories, tend to work better for unit testing, custom object creation and functional programming. It works better for unit testing because if you have a factory producing all of your objects, you can just replace that factory with a mock-up to test different cases.
var Factory = {
makeThing: function() {
return { name: 'thing' };
}
};
// Want to test the case for if makeThing fails inside of some other code
var MockFactory = {
makeThing: function() {
return null;
};
};
As for when you use either, it all depends. Some people don't use new at all. Others exclusively use new. It all depends on if you need any of the things listed above, how much control you need over the creation of objects, when you want to use this or not, etc. In the end, it's all a matter of preference.
The difference is the constructor used to create the object returned.
new Book('A Good Book', '500 pages');
creates a Book object instance, with the instance inheriting properties from Book.prototype, including a constructor property value of Book. The Book.prototype object itself inherits from Object.prototype.
var other = Movie('Gladiator', '180');
uses Movie as a factory function (new not required) and returns an Object object instance, with the instance inheriting properties directly fromObject.prototype, including a constructor property value of Object.
More briefly stated, Object literal syntax creates an Object object.
So far I have relied on Object.prototype.toString.call(x) to distinguish between the different native object types in Javascript, arrays in particular.
If you subclass arrays, you get some strange behavior:
function Ctor() {}
Ctor.prototype = Object.create(Array.prototype);
var x = new Ctor();
x.push(1);
Object.prototype.toString.call(x); // [object Object]
Probably this is documented in the ES5 specs (and no longer an issue in ES6), but I consider it a quirk of the current version of the language. I adapted my corresponding functions as follows:
function objTypeOf(deep, type) {
return function _objTypeOf(x) {
do {
if (Object.prototype.toString.call(x).slice(8, -1).toLowerCase() === type) return true;
x = Object.getPrototypeOf(x);
} while(deep && x !== null);
return false;
};
}
var arr = objTypeOf(false, "array"),
arrP = objTypeOf(true, "array"); // array prototype
console.log(arr(x)); // false
console.log(arrP(x)); // true
objTypeOf checks the current object and the entire prototype chain until there is a type match. It accepts an object even if merely one of the prototypes matches the expected type. objTypeOf is not based on prototype identities, but on strings (lacking identity).
I wonder now if there are other edge cases when using Object.prototype.toString, that need special treatment?
Well your problem is not with Object.prototype.toString, but that you tried to subclass arrays. It just doesn't work, and toString correctly tells you that you failed to create an array. It's merely an object that has Array.prototype in its prototype chain (if that was what you cared for, use instanceof Array).
Regardless, to answer your title question:
What are the edge cases when using Object.prototype.toString?
Host objects. Everything that is not a native JS object, despite looking like one, might return any [[Class]] value that you didn't expect. There are even known cases where callable objects do not report Function.
I'm working on an AngularJS SPA and I'm using prototypes in order to add behavior to objects that are incoming through AJAX as JSON. Let's say I just got a timetable x from an AJAX call.
I've defined Timetable.prototype.SomeMethod = function() and I use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf in order to set the prototype of x to TimeTable.prototype. I have the polyfill in place too.
If I call x.SomeMethod() this works in IE > 9, FF, Chrome etc. However, IE 9 gives me a headache and says throws an error stating 'x does not have property or member SomeMethod'.
Debugging in IE shows me that the _proto_ of x has SomeMethod() in the list of functions, however, calling x.SomeMethod() gives the same error as described.
How can I make this work in IE9 ?
More comment than answer
The main problem with "extending" a random object retrieved from some other environment is that javascript doesn't really allow random property names, e.g. the random object may have a property name that shadows an inherited property. You might consider the following.
Use the random object purely as data and pass it to methods that access the data and do what you want, e.g.
function getName(obj) {
return obj.name;
}
So when calling methods you pass the object to a function that acts on the object and you are free to add and modify properties directly on the object.
Another is to create an instance with the methods you want and copy the object's properties to it, but then you still have the issue of not allowing random property names. But that can be mitigated by using names for inherited properties that are unlikely to clash, e.g. prefixed with _ or __ (which is a bit ugly), or use a naming convention like getSomething, setSomething, calcLength and so on.
So if obj represents data for a person, you might do:
// Setup
function Person(obj){
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
this[p] = obj[p];
}
}
}
Person.prototype.getName = function(){
return this.name;
};
// Object generated from JSON
var dataFred = {name:'fred'};
// Create a new Person based on data
var p = new Person(dataFred);
You might even use the data object to create instances from various consructors, e.g. a data object might represent multiple people, or a person and their address, which might create two related objects.
This is how I solved it at the end:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
if (!isIE9()) {
obj.__proto__ = proto;
} else {
/** IE9 fix - copy object methods from the protype to the new object **/
for (var prop in proto) {
obj[prop] = proto[prop];
}
}
return obj;
};
var isIE9 = function() {
return navigator.appVersion.indexOf("MSIE 9") > 0;
};
I'm currently trying to test whether it's possible to make one object inherit from another object AFTER both objects have been created using literals. I tried to aim the constructors and prototypes at one another but it seems like no matter what, the only way Im going to pull this off is by building a new object using one of the pre-existing ones.. Let me know if I'm wrong. Here was my quick attempt to solve the problem.
Object.relate = function(parent, child){
function F(){};
F.prototype = parent;
child.constructor = F;
}
alpha = {a:1};
beta = {b:2};
Object.relate(alpha, beta);
In your code beta can access properties defined on alpha by using beta.constructor.prototype.
When you use new on a function like
var obj = new F();
then obj.__proto__ is automatically set to F.prototype. When you have already existing objects and you are not creating the object using new you must set the __proto__ by yourself.
obj.__proto__ = F.prototype;
So in your example you need to call
child.__proto__ = parent;
Resources:
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto
http://killdream.github.com//blog/2011/10/understanding-javascript-oop/index.html#sec-4-1
The problem is that now every browser expose the __proto__ property. IE doesn't support it so you cannot create a browser-compatible solution without actually creating a new object and copying the properties.
This method seems to work for me (tested in Chrome):
function relate(parent, child){
child.__proto__ = parent;
}
alpha = {a:1};
beta = {b:2};
relate(alpha, beta);
console.log(beta.a);
Output: '1'.
What's the fastest alternative to
JSON.parse(JSON.stringify(x))
There must be a nicer/built-in way to perform a deep clone on objects/arrays, but I haven't found it yet.
Any ideas?
No, there is no build in way to deep clone objects.
And deep cloning is a difficult and edgey thing to deal with.
Lets assume that a method deepClone(a) should return a "deep clone" of b.
Now a "deep clone" is an object with the same [[Prototype]] and having all the own properties cloned over.
For each clone property that is cloned over, if that has own properties that can be cloned over then do so, recursively.
Of course were keeping the meta data attached to properties like [[Writable]] and [[Enumerable]] in-tact. And we will just return the thing if it's not an object.
var deepClone = function (obj) {
try {
var names = Object.getOwnPropertyNames(obj);
} catch (e) {
if (e.message.indexOf("not an object") > -1) {
// is not object
return obj;
}
}
var proto = Object.getPrototypeOf(obj);
var clone = Object.create(proto);
names.forEach(function (name) {
var pd = Object.getOwnPropertyDescriptor(obj, name);
if (pd.value) {
pd.value = deepClone(pd.value);
}
Object.defineProperty(clone, name, pd);
});
return clone;
};
This will fail for a lot of edge cases.
Live Example
As you can see you can't deep clone objects generally without breaking their special properties (like .length in array). To fix that you have to treat Array seperately, and then treat every special object seperately.
What do you expect to happen when you do deepClone(document.getElementById("foobar")) ?
As an aside, shallow clones are easy.
Object.getOwnPropertyDescriptors = function (obj) {
var ret = {};
Object.getOwnPropertyNames(obj).forEach(function (name) {
ret[name] = Object.getOwnPropertyDescriptor(obj, name);
});
return ret;
};
var shallowClone = function (obj) {
return Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
};
I was actually comparing it against angular.copy
You can run the JSperf test here:
https://jsperf.com/angular-copy-vs-json-parse-string
I'm comparing:
myCopy = angular.copy(MyObject);
vs
myCopy = JSON.parse(JSON.stringify(MyObject));
This is the fatest of all test I could run on all my computers
The 2022 solution for this is to use structuredClone
See : https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
structuredClone(x)
Cyclic references are not really an issue. I mean they are but that's just a matter of proper record keeping. Anyway quick answer for this one. Check this:
https://github.com/greatfoundry/json-fu
In my mad scientist lab of crazy javascript hackery I've been putting the basic implementation to use in serializing the entirety of the javascript context including the entire DOM from Chromium, sending it over a websocket to Node and reserializing it successfully. The only cyclic issue that is problematic is the retardo navigator.mimeTypes and navigator.plugins circle jerking one another to infinity, but easily solved.
(function(mimeTypes, plugins){
delete navigator.mimeTypes;
delete navigator.plugins;
var theENTIREwindowANDdom = jsonfu.serialize(window);
WebsocketForStealingEverything.send(theENTIREwindowANDdom);
navigator.mimeTypes = mimeTypes;
navigator.plugins = plugins;
})(navigator.mimeTypes, navigator.plugins);
JSONFu uses the tactic of creating Sigils which represent more complex data types. Like a MoreSigil which say that the item is abbreviated and there's X levels deeper which can be requested. It's important to understand that if you're serializing EVERYTHING then it's obviously more complicated to revive it back to its original state. I've been experimenting with various things to see what's possible, what's reasonable, and ultimately what's ideal. For me the goal is a bit more auspicious than most needs in that I'm trying to get as close to merging two disparate and simultaneous javascript contexts into a reasonable approximation of a single context. Or to determine what the best compromise is in terms of exposing the desired capabilities while not causing performance issues. When you start looking to have revivers for functions then you cross the land from data serialization into remote procedure calling.
A neat hacky function I cooked up along the way classifies all the properties on an object you pass to it into specific categories. The purpose for creating it was to be able to pass a window object in Chrome and have it spit out the properties organized by what's required to serialize and then revive them in a remote context. Also to accomplish this without any sort of preset cheatsheet lists, like a completely dumb checker that makes the determinations by prodding the passed value with a stick. This was only designed and ever checked in Chrome and is very much not production code, but it's a cool specimen.
// categorizeEverything takes any object and will sort its properties into high level categories
// based on it's profile in terms of what it can in JavaScript land. It accomplishes this task with a bafflingly
// small amount of actual code by being extraordinarily uncareful, forcing errors, and generally just
// throwing caution to the wind. But it does a really good job (in the one browser I made it for, Chrome,
// and mostly works in webkit, and could work in Firefox with a modicum of effort)
//
// This will work on any object but its primarily useful for sorting the shitstorm that
// is the webkit global context into something sane.
function categorizeEverything(container){
var types = {
// DOMPrototypes are functions that get angry when you dare call them because IDL is dumb.
// There's a few DOM protos that actually have useful constructors and there currently is no check.
// They all end up under Class which isn't a bad place for them depending on your goals.
// [Audio, Image, Option] are the only actual HTML DOM prototypes that sneak by.
DOMPrototypes: {},
// Plain object isn't callable, Object is its [[proto]]
PlainObjects: {},
// Classes have a constructor
Classes: {},
// Methods don't have a "prototype" property and their [[proto]] is named "Empty"
Methods: {},
// Natives also have "Empty" as their [[proto]]. This list has the big boys:
// the various Error constructors, Object, Array, Function, Date, Number, String, etc.
Natives: {},
// Primitives are instances of String, Number, and Boolean plus bonus friends null, undefined, NaN, Infinity
Primitives: {}
};
var str = ({}).toString;
function __class__(obj){ return str.call(obj).slice(8,-1); }
Object.getOwnPropertyNames(container).forEach(function(prop){
var XX = container[prop],
xClass = __class__(XX);
// dumping the various references to window up front and also undefineds for laziness
if(xClass == "Undefined" || xClass == "global") return;
// Easy way to rustle out primitives right off the bat,
// forcing errors for fun and profit.
try {
Object.keys(XX);
} catch(e) {
if(e.type == "obj_ctor_property_non_object")
return types.Primitives[prop] = XX;
}
// I'm making a LOT flagrant assumptions here but process of elimination is key.
var isCtor = "prototype" in XX;
var proto = Object.getPrototypeOf(XX);
// All Natives also fit the Class category, but they have a special place in our heart.
if(isCtor && proto.name == "Empty" ||
XX.name == "ArrayBuffer" ||
XX.name == "DataView" ||
"BYTES_PER_ELEMENT" in XX) {
return types.Natives[prop] = XX;
}
if(xClass == "Function"){
try {
// Calling every single function in the global context without a care in the world?
// There's no way this can end badly.
// TODO: do this nonsense in an iframe or something
XX();
} catch(e){
// Magical functions which you can never call. That's useful.
if(e.message == "Illegal constructor"){
return types.DOMPrototypes[prop] = XX;
}
}
// By process of elimination only regular functions can still be hanging out
if(!isCtor) {
return types.Methods[prop] = XX;
}
}
// Only left with full fledged objects now. Invokability (constructor) splits this group in half
return (isCtor ? types.Classes : types.PlainObjects)[prop] = XX;
// JSON, Math, document, and other stuff gets classified as plain objects
// but they all seem correct going by what their actual profiles and functionality
});
return types;
};