Pull to refresh

Comments 39

Вот за такие штуки я люблю JS. Удобно использовать, например, для надевания поведения на фрагменты вёрстки. Есть, конечно, и нюансы. Например, в функциональных примесях, поскольку это «процесс», бывает возникает желание влепить условие или состояние какое-нибудь. Что потом, несмотря на простоту реализации, для кого-то оказывается пеньком на дороге, особенно при попытке расширить («пронаследовать») примесь и изменить это поведение у «потомка».
Вот как наследовать функциональные примеси, я не очень понимаю. Впрочем, это не очень часто нужно.
Из тела функции примеси-потомка вызвать примесь-предка, передав ей текущий контекст. Когда предок заполнит его, добавить что-нибудь своё или изменить наполненное.
Отказался от примесей в пользу делегации — тяжело было следить, откуда появились методы у прототипа, объекты расли, иногда возникали неприятные конфликты имён (да, это не сказка).

Пока пользовался примесями — постоянно старался сократить количество свойств у примеси. Размеры методов расли, а их количество, даже если было необходимо — нет. После отказа архитектура стала лучше.
Еще объект должен знать о внутренностях примеси, приходится подстраивается под её формат и её внутренние имена переменных и зависимости. Как ты уже сказал в реальном проекте примеси сложно отследить. Подмешивание это не очень явная штука — приходится дольше разбираться в таком коде.

Вместо примесей лучше явно написать какой-то метод, который будет использовать методы объекта-донора. Такая реализация более чем явная, мы не зависим от реализации, зависимостей миксина и внутренних его имен.

var Shape = function (size) {
    this.size = size;
};
var CircleMixin = {
    area: function () {
        return Math.PI * this.radius * this.radius; // Мы не можем примешать CircleMixin к Shape ибо radius...
    }
};

// VS

// ## Geometry.js
var Geometry = {
    area: function (radius) {
        return Math.PI * radius * radius;
    }
};
// module.exports = Geometry;

// ## Shape.js
var Shape = function (size) {
    this.size = size;
};
// module.exports = Shape;

// ## Circle.js
// var Shape = require('Shape'),
//     Geometry = require('Geometry');

var Circle = function (size) {
    this.size = size;
};

Circle.prototype = new Shape();
Circle.prototype.radius = function () {
    return Geometry.radius(this.size); // явно передаем параметр
};

Получилось длиннее, но мы не зависим от внутренностей миксина и такой код более понятный.
Честно говоря, в JS попросту не хватает какого-то очень простого синтаксиса для микшенов. С явным и однозначным указанием того, что примешиваем, и чтобы при этом было понятно, откуда свойства берутся. И простое что-то, простое…
> Еще объект должен знать о внутренностях примеси, приходится подстраивается под её формат и её внутренние имена переменных и зависимости.

ООП учит, что с этим надо бороться с помощью инкапсуляции — определять интерфейсы и использовать только их. И тогда будет понятнее, что с чем смешивать можно.
А как на счёт protected свойств?
Да и у интерфейсов может быть конфликт имён.
Да и никогда не понимал, откуда возникла идея называть интерфейсы «множественным наследованием».
Я говорю об интерфейсе объекта как о контракте, который объект предлагает миру. Как это технически реализовано — другой вопрос. Может быть поддержано языком, а может быть и на уровне соглашений.

Будет ли конфликт имен — зависит от схемы именования.

Про идею называть интерфейсы множественным наследованием слышу впервые. Это разные понятия. Да, в Java разрешено множественное наследование только для интерфейсов, но это не отождествляет их. В С++ разрешено множественное наследование классов.
Мне кажется, это вопрос умеренности. Немножко примесей не помешает.
Не могли бы Вы прокомментировать следующий кусок кода:
[].slice.call(arguments, 0);

Выполнение его в консоли привело к простому созданию массива аналогичному arguments.
arguments — не массив, а только объект, похожий на массив. Этот кусок кода возвращает настоящий массив, содержащий те данные, которые содержит arguments
a.slice(0) создает копию массива a
но arguments — не массив и метода slice у него нет. поэтому метод Array.slice вызывается в контексте arguments, чтобы создать копию
Дополню, что здесь создаётся лишний пустой массив (для быстрого получения прототипа), что не слишком круто. Можно так:
Array.prototype.slice.call(arguments,0);


И ещё добавлю, что многие функции прототипа Array работают с массиво-подобными объектами (индексы [0], [1], свойство length...).
Кстати, в ES5 расширили возможности Function.apply:

alert(Math.max.apply(null, {length: 2, 0: 0, 1: 1}));​ // 1

Однако не все реализации Function.apply могут работать с массиво-подобными объектами (generic array-like object), поэтому вот имплементация с поправками для ES5:

(function(apply) {
	'use strict';

	try {
		Function.apply(null, {length: 0});
	}
	catch (error) {
		Function.prototype.apply = function(context, object)
		{
			if (Object.prototype.toString.call(object) !== '[object Array]')
				object = Array.prototype.slice.call(object);

			return apply.call(this, context, object);
		}
	}
}(Function.prototype.apply));
Обычно лень такие объёмы читать, но тут читал до конца, спасибо.

> OK, но это JavaScript, и у нас нет классов
Классическое заблуждение. Не буду в милионный раз объяснять, как они делаются, но есть и классы, и наследование (да хотя бы child.prototype = parent.prototype). Впрочем если хотите — объясню.
К тому же обёртки для классов реализованы во многих библиотеках (Mootools, Prototype, Dojo, ExtJS...), есть куча и отдельных реализаций. Приводить список не буду, дам ссылку: habrahabr.ru/post/132698/#comment_4404597
P.S. Кроме того, классы появились и в самом ECMAScript — см. функцию Object.create.
Я думаю, автор прекрасно это знает.

Строго говоря, он прав: в JS нет классов. Существуют реализации классического наследования в библиотеках — это вы правильно отметили.
Да, наверное, не подумал. В любом случае кому-нибудь мой комментарий может быть полезен.

А про отсутствие классов в JS…
Class = function(){}

var a = new Class;

Что это такое, как не класс? Просто они создаются по другому.
Классу, все же, присуще иметь ряд характеристик, которыми в данном случае конструктор Class не обладает. И даже если идти на разного рода ухищрения — полноценных классов мы не получим.

Похоже, что скоро все будет наоборот — классическим заблуждением станет то, что в JavaScript есть классы.
Тема классов в ES уже затерта до дыр.

В ES явно сказано:
ECMAScript does not use classes such as those in C++, Smalltalk, or Java. Instead objects may be created in various ways including via a literal notation or via constructors which create objects and then execute code that initialises all or part of them by assigning initial values to their properties. Each constructor is a function that has a property named “prototype” that is used to implement prototype-based inheritance and shared properties.


Если резюмировать по факту, то в ES отстутствуют классовая модель, в место нее используется прототипная. Однако наличие ключевого class не есть наличие классовой модели которая принята в таких языка как C++, C#, Java и пр. Чтобы в этом убедиться достаточно поработать с Ruby, Python, CoffeeScript и пр. В этих языках с динамической типизацией принята прототипно-классовая парадигма, в котором работа с классами реализована на уровне делегирующего прототипирования.

О том как реализованы классы в CoffeeScript и как их можно усовершенствовать (модификаторы доступа) можно прочитать у меня в статье.
Выкладывайте, какие характеристики присущи классу.
Это конструктор объекта. Описание класса это всё же некая метаконструкция, в которой содержатся свойства, методы, ссылка на описание родительской конструкции, описание поведения при определённых событиях.
Дык этот конструктор содержит всё вышеперечисленное.
да хотя бы child.prototype = parent.prototype

Ну именно тут вы загнули, конечно.
Сорри, не подумал конечно же. Расширяя child.prototype, мы расширяем parent.prototype.

Тогда так:
for(var i in parent.prototype){
  if( {}.hasOwnProperty.call(parent, i) )
    child.prototype[i] = parent.prototype[i];
}

// или, если есть jquery, $.extend(child.prototype, parent.prototype);
или да, проще с суперклассом:

var sclass = function(){}
sclass.prototype = parent.prototype;
child.prototype = new sclass;
Пожалуйста, ставьте пробел между ключевыми словами (function, if, switch, for, while) и круглой скобкой. Спасибо за статью!
Это перевод. Пожалуйста.
Не согласен. Некрасиво
Это вкусовщина, типа пробелы vs табы. Придерживайтесь стиля, принятого в конкрентном проекте, и всем станет щастье.
А вот с этим согласен. У меня самого вкусы меняются. Когда-то писал многие операторы без пробелов:
var a=1
if(a==1);

// теперь
var a = 1;
if( a == 1 );


Ну а ставить пробел перед скобками — это как (моё мнение, никому не навязываю) перед точкой.
, или ставить запятую в середине строки
var a=1 во это точно ужасно, ни в одном стайлгайде не видел такого стиля.
Согласен. А тогда у меня почему-то код с пробелами не работал :). Ну вы знаете: плохому танцору…
Это общепринятый стандарт jslint. Если не вызов функции или описание именованой функции, рекомендуется ставить пробел. www.jslint.com/lint.html

Есть тому рациональное объяснение — вызов функции синтаксически выделяется от всех других конструкций. Такой код гораздо опрятнее выглядит.
Вспомнил пример хорошей примеси — Backbone.Events
var object = {};

_.extend(object, Backbone.Events);

object.on("alert", function(msg) {
  alert("Сработало " + msg);
});

object.trigger("alert", "событие")
Ага. И в мутулзе такая примесь была. Я в Атоме пользовался, а потом отказался в пользу делегирования:

var object = {};
object.events = new atom.Events(object);

object.events.add('alert', function(msg) {
  alert("Сработало " + msg);
});

object.events.fire('alert', [ 'событие' ]);


Так значительно лучше.
У тебя чистый Observer (аж вспомнил Pro JavaScript Design Patterns) Зачем, кстати, передавать object в atom.Events? для this?
Ага. Внутри евентов правильный контекст должен быть) Нафиг нам контекст Events? =)
Sign up to leave a comment.

Articles

Change theme settings