The true prototype-based⠙ three-lines "framework".
The replacement for the new
operator. This is it:
function clone(/** Object */proto, /** object.literalOnly! */ownProperties){
ownProperties.__proto__ = proto;
return ownProperties;
}
The __proto__
is a part of upcoming ECMA Script 6⠙ standart.
Currently, all major browsers have __proto__
support, except Internet Explorer.
This clone function can be also implemented through Object.create or function-constructors (JavaScript 1.0 / IE3.0).
clone(obj, {})
function produces new1 objects — Clones.
Clone object — this is the lazy shallow copy, i.e., it is actually not a copy, it's just a reference to the object,
with one difference: if you will add/replace any of its properties, it would not affect the cloned object (prototype).
All JavaScript objects are clones of Object.prototype
(except itself and objects, created by Object.create(null)
).
1 To make it true, you need to follow one rule:
The second argument of the clone(obj, {})
function should not be a variable. Only object literal ({}) allowed.
- Because its second argument isn't usable:
var talkingDuck = Object.create(duck, {
firstName: {value:"", enumerable:true, writable:true},
lastName: {value:"Duck", enumerable:true, writable:true},
quack: {value: function(){
duck.quack.call(this);
console.log("My name is "+ this.name +"!");
}}
});
- It's slow.
With this "framework" you can easilly create and manipulate objects without constructors, instead of classic js way, where you should define a constructor for every object (that you want to use as prototype), even if you didn't need it. It's possible to build and maintain extremely large numbers of "classes" with comparatively little code.
It's trivial to create new "classes" - just clone the object and change a couple of properties and voila... new "class".
It's really class-free: clone()
produces objects (prototypes), not function-constructors, unlike all other class-producing tools (Ext.define
, dojo.declare
etc).
Read more:
- Advantages of prototype-based OOP⠙ by Mike Anderson
- Does JavaScript need classes? (in russian)⠙ (robot translation)⠙ by Me (Alexander Shvets)
- Myth: JavaScript needs classes⠙ by Dr. Axel Rauschmayer (University of Munich)⠙
- JS Objects: De”construct”ion⠙ by Kyle Simpson
- Stop Using Constructor Functions In JavaScript⠙ by Eric Elliott (Adobe)⠙
- Constructors Are Bad For JavaScript⠙ by Tarek Sherif
It faster than any other framework, even VanillaJS! Yes, it creates class-objects faster than JS core creates class-functions!
See http://jsperf.com/fw-class-creation/3 and http://jsperf.com/clonejs-nano-vs-vanillajs/5
Forget about classes (function-constructors).
Instead of creating class (function), create prototype (object):
var duck = {
name: "Duck",
color: "",
canFly: true,
quack: function(){
console.log( this.name +": Quack-quack!");
}
};
The classic way:
var Duck = function(name, color, canFly){
this.name = name || "Duck";
this.color = color || "";
this.canFly= canFly === undefined ? true : canFly;
}
Duck.prototype.quack = function(){
console.log(this.name +": Quack-quack!");
}
Inheritance is simple (talkingDuck prototype extends duck prototype):
var talkingDuck = lazyClone( duck, {
firstName: "",
lastName: "Duck",
quack: function(){
duck.quack.call(this);
console.log("My name is "+ this.name +"!");
},
// backward compatibility with duck interface:
get name(){
return (this.firstName +" "+ this.lastName).trim();
},
set name(newName){
var names = newName.split(" ");
this.firstName = names[0];
if(names.length > 1){
this.lastName = names[1];
}
}
});
The classic way:
var TalkingDuck = function(firstName, lastName, color, canFly){
this.firstName = firstName;
this.lastName = lastName || "Duck";
this.color = color || "";
this.canFly= canFly === undefined ? true : canFly;
}
var TalkingDuckPrototype = function(){};
TalkingDuckPrototype.prototype = Duck.prototype;
TalkingDuck.prototype = new TalkingDuckPrototype;
TalkingDuck.prototype.constructor = TalkingDuck;
TalkingDuck.prototype.quack = function(){
Duck.prototype.quack.call(this);
console.log("My name is "+ this.name +"!");
}
// backward compatibility with Duck interface:
Object.defineProperty(TalkingDuck.prototype, 'name', {
get: function(){
return (this.firstName +" "+ this.lastName).trim();
},
set: function(newName){
var names = newName.split(" ");
this.firstName = names[0];
if(names.length > 1){
this.lastName = names[1];
}
}
});
Forget about the new
operator, use clone
to create instances:
var donald = clone(talkingDuck, {firstName: "Donald", color: "White", canFly: false});
donald.quack();// Donald Duck: Quack-quack!
// My name is Donald!
The classic way:
var daffy = new TalkingDuck("Daffy", undefined, "Black", false);
daffy.quack();// Daffy Duck: Quack-quack!
// My name is Daffy!
Forget about the instanceof
operator, use JS native .isPrototypeOf()
method instead:
duck.isPrototypeOf(donald);// true
The classic way:
daffy instanceof Duck;// true
Create the root prototype for all your objects:
var object = {
clone: function(/** object.literalOnly! */ownProperties){
ownProperties.__proto__ = this;
return ownProperties;
}
}
After that, you can clone it:
var duck = object.clone({
name: "Duck",
quack: function(){
console.log(this.name +": Quack-quack!");
}
});
var donald = duck.clone({name: "Donald Duck"});
or just copy its clone
method to your prototype:
var duck = {
name: "Duck",
quack: function(){
console.log(this.name +": Quack-quack!");
},
clone: object.clone
};
var donald = duck.clone({name: "Donald Duck"});
1st, classic way:
var obj = {
base: 1000,
constructor: function(initBy){
if(initBy === undefined) initBy = 1;
this.num = this.base * initBy;
console.log("property calculated and stored");
}
}
obj.constructor.prototype = obj;
var instance = new obj.constructor(777);
// property calculated and stored
var num = instance.num;
console.log(instance.num);
// 1777
The second, more interesting way:
var obj = {
base: 1000,
initBy: 1,
get num(){
console.log("property calculated");
return this.num = this.base * this.initBy;
},
set num(newValue){
console.log("and stored");
Object.defineProperty(this, 'num', {value: newValue, writable:true,enumerable:true});
}
}
var instance = clone(obj, {initBy: 777});
var num = instance.num;
// property calculated
// and stored
console.log(instance.num);// num isn't calculated again
// 1777
The benefits of this technic is:
- separation of concerns: constructor separation into small accessor methods (to prevent "long method" antipattern)
- perfomance boost, because unused properties will be not initialized
If you like the idea, plese look at the extended version of this framework www.github.com/quadroid/clonejs