07.02.2014

«Классы» :]

«Классы»

Don't Panic!

Введение

Всем хорошо известно, что любое упоминание об «классах» в JavaScript приводит к обильной нецензурной брани и обвинением оппонента, что он не понимает JS и ему не место среди «них».

А зря, ведь в ES6 черным по белому написано class, extends и super.

или Backbone

			var Note = Backbone.Model.extend({
				initialize: function() { /*...*/ },
				author: function() { /*...*/ },

				allowedToEdit: function(account) {
					return true;
				}
			});
		

или Backbone

			// Наследуемся от Note
			var PrivateNote = Note.extend({
				// Переопределяем родительский метод
				allowedToEdit: function(account) {
					return true;
				}
			});
		

или Ember

			App.UserView = Ember.View.extend({
				templateName: "user",
				firstName: "Albert",
				lastName: "Hofmann"
			});
		

.extend({ })

Что это?

Это «класс»

			var Hello = function (first){ // Конструктор
				this.setText(first);
			};
			// Методы «класса»
			Hello.prototype.setText = function (text){
				this.text = text;
			};
			Hello.prototype.say = function (){
				alert(this.text);
			};
		

«Наследование»

			var HelloXXX = function (first, second){
				this.setText(first+" "+second);
			};
			// Наcледуем методы
			HelloXXX.prototype = Object.create(Hello.prototype);
			// Переопределяем метод
			HelloXXX.prototype.setText = function (text){
				// Вызываем parent/super
				Hello.prototype.setText.call(this, text+"!");
			};
		

Инстанцируем

			// Создаем экземпляр класса
			var hello = new HelloXXX("Hi", "Class");

			// Вызываем метод "say"
			hello.say(); // alert: Hi Class!
		

Тут вы должны остановить меня и сказать, мы и так всё это знаем, ничего сложно.

И вы правы.

«Классы» и ругань начинаются когда кто-то пытается создать обертку над процессом создания функции-конструктора, а самое главное вызовов «родительского» метода.

Что тут сложного?

И это не все ;]

Основные реализации parent/super

SimpleClass (Resig)

			function makeParent(func, parentFunc){
				return function (){
					var tmp = this.parent/*запомним*/, retVal
					this.parent = parentFunc; // текущий родитель
					retVal = func.apply(this, arguments);
					this.parent = tmp; // востановим
					return retVal;
				};
			}
		

SimpleClass (Resig)

			var Foo = Class.extend({
				toString: function (){
					return "foo";
				}
			});

			var Bar = Foo.extend({
				toString: function (){
					return this.parent() + " bar";
				}
			});
		

Foo.$superp (jsface.js)

			var Foo = ...;

			var Bar = Foo.extend({
				toString: function (){
					return Bar.$superp.toString.call(this) + " bar";
				}
			});
		

arguments.callee.caller (def.js)

			function base(){
				var caller = arguments.callee.caller;
				return caller._class._super.prototype[caller._name]
					.apply(this, arguments.length
						? arguments
						: caller.arguments);
			}
		

Вот она, настоящая «магия» :]

arguments.callee.caller (def.js)

			def("Foo") ({ /*..*/ });

			def("Bar") << Foo ({
				toString: function (){
					return this._super() + " bar";
				}
			});
		

funcName.parent (gist)

			var Foo = ...;

			var Bar = Foo.extend({
				toString: function __(){
					return __.parent.call(this) + " bar";
				}
			});
		

Benchmarks: extend (jsperf)

Benchmarks: parent (jsperf)

The End

github.com/RubaXa
@ibnRubaXa