Skip to content

Instantly share code, notes, and snippets.

@royriojas
Last active September 30, 2020 13:58
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save royriojas/6454479 to your computer and use it in GitHub Desktop.
Save royriojas/6454479 to your computer and use it in GitHub Desktop.
Why I love Javascript

Why I love Javascript

  1. Because it has a humble origin... Designed in 10 days... was proclaimed death several times, and it was planned to be replaced by many other more "powerful" languages

  2. Because it has closures and lambdas, and the prototypal inheritance (the good parts)

  3. Because it is weird, in a very nice sense... (some bad parts... maybe?)

From the video WAT (https://www.destroyallsoftware.com/talks/wat)

a. Adding two arrays

[] + []

//Expected: well, runtime error???

//Result: "" ( empty string)

//But Why???? 
//this is the same as calling [].toString() + [].toString() which for arrays is the same as calling
//[].join() which on an empty array returns ""

b. Adding two objects (Actually this is not accurate. You could think the first one is an object literal but it is not)

{} + {}

//Expected: again, runtime error??? 

//Result: NaN

//But Why???? 
// Well the first {} is evaluated as an "empty block" which does nothing 
// and the +{} will try to execute the unary operator + on an object literal
// which happens to return NaN

c. Adding and array + object

[] + {}

//Expected: again... I will expect a runtime error.

//Result [object Object]

//But Why???? 
// the + operator will try to convert the values to primitives first, 
// if it cannot it will execute the toString so for [] the valueOf 
// will be the array itself, not a primitive, then it will try with 
// toString, which for arrays it is the same as Array.prototype.join 
// and in that case... will return an empty string. the second one 
// will do the same and will end executing the toString method of the empty
// object which is... [object Object]

d. Adding and object to an array

{} + []

//Expected:  runtime error?
//Result: 0

//But Why???
//The first is an empty block, then the rest will be evaluated as +[]
//+[] === +"" 
//+"" === 0 
// so that's why it is 0

e. unary operator

+[]
//Expected:  runtime error?
//Result: 0
//But Why??
//Same as above explanation

+{}
//Expected:  runtime error?
//Result: NaN

//But Why??
//Unary of an empty Object is NaN

+{ valueOf : function () { return 3; }};
//Result: 3

+{ toString : function () { return 5; }};
//Result: 5

f. the type of NaN (Not a Number) is a Number :)

typeof NaN === 'number'

// lol! I kinda enjoy this one!

g. you can mess with it in a badly badly way:

var b = new Number(1);
(b).valueOf();
//Expected: 1
//Result: 1

b = new Number(-1);
(b).valueOf()
//Expected: -1
//Result: -1

NaN.valueOf()
//Expected: runtime error???
//Result: NaN (spec says it)

So far so good... now some interesting...

var oldProto = Number.prototype.valueOf;
Number.prototype.valueOf = function () { 
	return oldProto.apply(this, arguments) * 2; 
};

b = new Number(20);
b.valueOf()
//20? not... 40!


Number.prototype.valueOf = function () { return 10; }

var b = new Number(1);
b.valueOf()
// 1? not... 10!
b.toString()
// 10?? not... 1!

// You can do the same with all the objects in javascript... 
// so that's why you should not mess with the prototypes... 

// As a rule of thumb you only modify the objects that you own... 
// objects that you own are the ones you create. 
// Others objects should be treated as not yours, so don't mess with them!

// Note: This only affects the instances of Objects 
// created with the Number constructors... it does not affect primitives.

h. the instanceof is kinda broken (I almost never used it... well, maybe once...)

var Person = function () {};
var p = new Person();

p instanceof Person // true

p instanceof Person === (p.constructor === Person)

p instanceof Window // false as expected...

//but what if I do...

//WARNING: non standard __proto__ property!!! but almost all host environments have it!
Person.prototype.__proto__ = Window.prototype;

//now... 

p instanceof Window === true 

//Even when this might look wrong is actually ok

i. typeof is broken...

typeof null === //object
typeof [] === //object

//better use
({}).toString.call(null) === //[Object Null]
//or even better 
Object.prototype.toString.call(null) === //[Object Null]

f. arrays are fun!

var arr = [];

arr[1] = 10;

arr.length === 2  // true! the array looks like : [undefined, 10]

Object.keys(arr)  // ["1"], note that there is no length property in the keys, nor the 0 index

arr[-1] = 20      // runtime error? range error?? nope... silently creates a key "-1"

arr.length === 2  // still true! the array looks like : [undefined, 10] // where is my -1 property???

Object.keys(arr)  // ["1", "-1"], not 0 not length... but there is the -1

arr["10"] = 23;   // the array looks like [undefined, 10, undefined x 8, 23]. Note the index was a string...

arr[00] = 2;      // setting arr[00] it is the same as arr[0], the array is now [2, 10, undefined x 8, 23]

arr["00"] = 12;   // so another string again...
		  // will this set the 0 index?? nope... the array is now [2, 10, undefined x 8, 23]
Object.keys(arr)  // ["0", "1", "10", "-1", "00"] 		  
		  
arr["00 "] = 12;  // note the extra space at the end.
		  
Object.keys(arr) // ["0", "1", "10", "-1", "00", "00 "]

g. arrays cannot be subclased easily

http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/

Now they can... here is my attempt...

function SubArray() {
  this.push.apply(this, arguments); 
  //Array.apply(this, arguments); 
}
SubArray.prototype = Object.create(Array.prototype, {
  constructor : { 
     value : SubArray
  },
  lastElement : {
    value : function() {
      return this[this.length - 1];
    }
  }, 
  clone : {
    value : function () {
      var s = Object.create(SubArray.prototype);
      // note the comma operator
      return s.push.apply(s, this), s;
    }
  }
});

var sub = new SubArray(1, 2, 3);
sub instanceof SubArray; //true
sub instanceof Array; //true
sub.lastElement() //3

h. Many Ways to make inheritance ala "Class based languages..."

Some of them, very complicated, maybe because they will trying to emulate all the features of a class based language...

I guess I have a simpler one... My own implementation... done 1.5 years ago, in the file z-oop.js

Now with Object.create, everything it is lot simpler...

Take a look to the SubArray example above.

Conclusion

So as a conclusion...

Javascript is weird... well that's not true. It is not even fair to say it. It is weird because most of the time we don't read the spec... So here is the spec in case you feel curious http://www.ecma-international.org/ecma-262/5.1/.

/**
* Tiny utility to provide support to object oriented development in javascript. For a complete example see
* http://jsfiddle.net/royriojas/hU4Ce/
*
* @module oop
*/
;(function($) {
'use strict';
/**
* This is not really a class. It extends the Object object to provide some utility methods
*
* TODO: Should this be moved to kno namespace?
*
* @class OOP
* @static
*/
/**
* Utility method to provide full support to OOP in javascript. It is the smallest implementation, but yet equally
* powerfull than other libraries. Despite of using the underscore prefix it is a static public method.
*
* TODO: should we rename it?
*
* @method _extend
*
* @static
* @param child {Function} the child Class (function constructor)
* @param parent {Function} the parent Class (function constructor)
*
* @example
var Person = function (name) {
this.name = name;
console.log('Person constructor');
};
$.extend(Person.prototype, {
walk : function () {
console.log(this.name + ' is Walking!!');
}
});
var Student = function () {
// call the base class constructor
Student._parent.constructor.apply(this, arguments);
console.log('Student Constructor');
}
Object._extend(Student, Person);
$.extend(Student.prototype, {
walk : function () {
console.log(this.name + ' walks like Student');
//calling a parent method
Student._parent.walk.apply(this, arguments);
}
});
var p = new Person('Jon Doe');
p.walk();
console.log(p instanceof Person) // true
var s = new Student('Joan Doe');
s.walk();
console.log(s instanceof Person) // true
console.log(s instanceof Student) // true
*/
Object._extend = function(child, parent) {
var Nexus = function() {}; // helper function to avoid create an instance of the child class
var base = Nexus.prototype = parent.prototype;
child.prototype = new Nexus();
var cp = child.prototype;
cp.constructor = child;
child._parent = base;
};
/**
* Helper utility to create classes in an easy way, just specify the constructor, the methods, and the class to
* inherit from
*
* Despite of using the underscore prefix it is a static public method.
*
* TODO: should we rename it?
* @method _class
* @param ctrFn {Function} the constructor function
* @param methods {Object} and object with the methods and properties to add to the prototype of the class
* @param [baseClass] {Function} the base Class to inherit from
* @return {Function} the constructor function
* @static
* @example
var $log = $('.log');
var log = function (msg) {
console.log.apply(console, arguments);
$log.append('<div>- ' + msg + '</div>');
};
var Person = Object._class(function (name) {
log('person constructor called');
this.name = name;
}, {
walk : function () {
log('I\'m walking, like a person');
},
sayHello : function (name) {
log('hello, ' + name + ', I am a Person, my name is : ' + this.name );
}
});
var p = new Person('Person 1');
p.walk();
p.sayHello('Visitor');
log('p an instance of Person : ' + (p instanceof Person));
var Student = Object._class(function (name) {
Student._parent.constructor.apply(this, arguments);
log('Student constructor called');
this.name = this.name + ' st.';
}, {
walk : function () {
Student._parent.walk.apply(this, arguments);
log('but with lot more of style!');
},
sayHello: function (name) {
log('hello, how are you today, ' + name+ '. I am a very polite student!, my name is ' + this.name );
}
}, Person);
log('===================================================');
var s = new Student('Student 1');
s.walk();
s.sayHello('Visitor');
log('s an instance of Student : ' + (s instanceof Student));
log('s an instance of Person : ' + (s instanceof Person));
var Teacher = Object._class(function (name) {
Teacher._parent.constructor.apply(this, arguments);
log('Teacher constructor called, they usually are students first');
}, {
walk : function () {
Teacher._parent.walk.apply(this, arguments);
log('We teachers walk the best!');
},
sayHello: function (name) {
log('hello, ' + name+ ', I hope you enjoy your visit, my name is ' + this.name);
}
}, Student);
log('===================================================');
var t = new Teacher('Teacher 1');
t.walk();
t.sayHello('Visitor');
log('s an instance of Student : ' + (t instanceof Teacher));
log('s an instance of Student : ' + (t instanceof Student));
log('s an instance of Person : ' + (t instanceof Person));
*/
Object._class = function (ctrFn, methods, baseClass) {
if (baseClass) {
Object._extend(ctrFn, baseClass);
}
$.extend(ctrFn.prototype, methods);
return ctrFn;
};
}(jQuery));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment