Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   оцените такой подход создания классов (https://javascript.ru/forum/misc/35880-ocenite-takojj-podkhod-sozdaniya-klassov.html)

Василий Б. 25.02.2013 17:40

оцените такой подход создания классов
 
Сабж. Насколько такой подход уместен в JS? Какие могут быть проблемы?
var popupObject = {
	// URL обработчика
	url: null,
	// Данные для запроса
	data: {},
	// Функция, которая будет выполнена в случае выполнения запроса
	success: function(data, textStatus, jqXHR){
		// Пустая функция, не надо сюда ничего писать
	},
	
	/**
	 * Конструктор объекта.
	 * 
	 * @param string url путь к обработчику запроса
	 * @param object data данные для запроса
	 * @param function success функция, которая выполнится в случае выполнения запроса
	 * @return object this 
	 */
	constuctor: function(url, data, success){
		this.url = url;
		this.data = data !== undefined ? data : this.data;
		this.success = success !== undefined ? success : this.success;
		return this;
	},
	
	/**
	 * Основной метод, выполняющий запрос, прорисовывающий данные из ответа и
	 * исполняющий метод success
	 */
	run: function() {
		$j.ajax({
			type: "POST",
			url: this.url,
			data: this.data,
			cache: false,
			context: this,
			success: function(data, textStatus, jqXHR){
				if (data.status.toLowerCase() === 'ok') {
					this.embed(data);
					this.success(data, textStatus, jqXHR);
				} else {
					if (window.console && window.console.log) {	
						// console.log(data);
					}
				}
			}
		});
	},
	
	/**
	 * Метод, выполняющий вставку данных из ответа куда-либо.
	 * В нашем случае - в попап.
	 * Данный метод можно перегрузить и вставлять данные куда угодно, в любое мето в DOM-дереве.
	 * 
	 * @param array data response данные
	 */
	embed: function(data) {
		var unique_id = 'unique_' + this.rand();
		var full_id = unique_id + '_for_popup';
		$j('<div id="' + full_id + '" style="display:none"></div>').appendTo('body').append(data.content);
		openStaticPopup(unique_id);
		$j('#' + full_id).remove();
	},

	rand: function() {
		return Math.floor(Math.random() * 1000);
	}
};

danik.js 25.02.2013 18:41

Василий Б., и как этим пользоваться?

Василий Б. 25.02.2013 18:52

danik.js,
success = function(data){
    // функция исполнится после успешного выполнения ajax запроса, аналог success  в API jQuery
}
var popup = popupObject.constuctor('/ajax/popup_delivery.php', {id: 123}, success);
popup.run();

// или 

popupObject.constuctor('/ajax/popup_delivery.php', {id: 123}, success).run();

типа того. просто как пример класса, что он делает - не так уж и важно (получает html из json и рендит его либо в попап, либо в любое место в DOM структуре).

Меня просто интересовало, подобный подход чем-то отличается от подхода, когда мы объявляем функцию со свойствами и методами через this, вот так:
function foo() {
   this.var1 = 123;
   this.bar = function(){
       // do 
   }
}

danik.js 25.02.2013 18:57

А каким боком это класс? Как можно создать два экземпляра этого класса?

Василий Б. 25.02.2013 19:12

ага, ща допишем...

melky 25.02.2013 19:23

Цитата:

Сообщение от Василий Б.
Сабж. Насколько такой подход уместен в JS? Какие могут быть проблемы?

никаких... все абсолютно нормально. такой подход называется "Синглетон" и подразумевает уникальность объекта.

создавать экземпляры нельзя, но вот наследовать его можно :
var popupMastaa = Object.create(popupObject);


вуаля, у нас есть новый объект, прототипом которого является popupObject.

danik.js 25.02.2013 19:46

Вот только нафига метод constructor. Какой же это конструктор.

Василий Б. 26.02.2013 11:04

melky,
а что должно быть в create методе? Чисто так, для любопытства спрашиваю.. Назначить прототипом Object мой объект popupObject?

Цитата:

Сообщение от danik.js
А каким боком это класс? Как можно создать два экземпляра этого класса?

посмотрите что дописал. Так хорошо?
/**
	 * Конструктор объекта.
	 * 
	 * @param string url путь к обработчику запроса
	 * @param object data данные для запроса
	 * @param function success функция, которая выполнится в случае выполнения запроса
	 * @return object this 
	 */
	constuctor: function(url, data, success){
		this.url = url;
		this.data = data !== undefined ? data : this.data;
		this.success = success !== undefined ? success : this.success;
		
		// Поскольку в JS нет классов, то мы создаем копию данного объекта (this)
		// и возвращаем его, что бы иметь возможность создавать несколько экземпляров объекта popupObject.
		var o = new Object();
		for (var i in this) {
			if (this.hasOwnProperty(i)) {
				o[i] = this[i];
			}
		}
		return o;
	},

и это работает, я пробовал создать два класса с разными параметрами. Ура!


Только я от не понял одного - я пытался изначально написать код клонирования объекта так:
var o = new Object();
for (var i in this) {
	if (this.hasOwnProperty(i)) {
		console.log(i + ' ' + this.i);
	}
}

вот что мне выдало в консоли:
Цитата:

url undefined
data undefined
success undefined
constuctor undefined
т.е. конструкция
this.i
- undefined. В упор не понимаю, почему так происходит. По идее, в i у меня содержится имя свойства. Почему я через точку не могу получить доступ к свойствам?

danik.js 26.02.2013 11:45

Цитата:

Сообщение от Василий Б.
и это работает

А почему это не должно работать? Ты создаешь копию объекта. Вот только зачем? Зачем цикл, зачем копировать ссылки на все методы? Не проще ли использовать для этого функцию-конструктор, вызываемую с ключевым словом new? А все методы совать в прототип.
Цитата:

Сообщение от Василий Б.
this.url = url;
this.data = data !== undefined ? data : this.data;
this.success = success !== undefined ? success : this.success;

А это зачем? В объекте popupObject будут постоянно изменяться эти свойства, а значит ими нельзя будет пользоваться. Тогда зачем они popupObject'у? И как насчет наследования?
Вопрос - с какой целью ты пытаешься изобрести классы? Ради экспериментов? Или свято веришь что что-то изобретешь новое? Или просто не знаешь основ JS и пытаешься по не знанию создать свой велосипед?

Цитата:

Сообщение от Василий Б.
Почему я через точку не могу получить доступ к свойствам?

Я вижу у тебя определено this.url, this.data, а вот this.i не вижу. А ты видишь? Наверное ты имел ввиду this[i].

Василий Б. 26.02.2013 11:58

Цитата:

Сообщение от danik.js
А это зачем? В объекте popupObject будут постоянно изменяться эти свойства, а значит ими нельзя будет пользоваться.

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

Вопрос - с какой целью ты пытаешься изобрести классы?
ради удобства разработки. да я и не пытаюсь изобрести классы.

danik.js 26.02.2013 12:51

var popup = popupObject.constuctor('/ajax/popup_delivery.php', {id: 123}, success);
console.log(popupObject.url); // что выдаст? Зачем это свойство хранится?
Цитата:

Сообщение от Василий Б.
ради удобства разработки.

А чем конструкторы не удобны?

Василий Б. 26.02.2013 13:38

Цитата:

Сообщение от danik.js
А чем конструкторы не удобны?

Вы про какие конструкторы? Объявление через
function foo()
{
   this.prop = ...
}

?

Цитата:

Сообщение от danik.js
что выдаст? Зачем это свойство хранится?

выдаст URL определенный в конструкторе при первом вызове
popupObject.constructor(...)

Вы что предлагаете? Сделать "uset" или.. вообще не писать в popupObject? Вот так:
constuctor: function(url, data, success){
		// Поскольку в JS нет классов, то мы создаем копию данного объекта (this)
		// и возвращаем его, что бы иметь возможность создавать несколько экземпляров объекта popupObject.
		var o = new Object();
		for (var i in this) {
			if (this.hasOwnProperty(i)) {
				o[i] = this[i];
			}
		}
				
		// свойства назначаем только конкретному объекту 
		o.url = url;
		o.data = data !== undefined ? data : this.data;
		o.success = success !== undefined ? success : this.success;
		return o;
	},
Так?

melky 26.02.2013 17:20

Цитата:

Сообщение от Василий Б.
а что должно быть в create методе?

по-поему, ничего. вы пытаетесь из синглетона класс.

это ... как из дивана пытаться делать завод по изготовлению мебели. надеюсь, меня поняли :)

Цитата:

Сообщение от Василий Б.
и это работает,

а это называется примесью.

рекомендую открыть learn.javascript.ru и почитать про способы создание объектов ... там уже есть вся инфа :)

Цитата:

Сообщение от Василий Б.
ради удобства разработки. да я и не пытаюсь изобрести классы.

а код и сообщения указывают на обратное.

Цитата:

Сообщение от Василий Б.
Вы про какие конструкторы?

те, которые функции, которые имеют имена с большой буквы и те, которые используются с оператором new

еще раз рекомендую открыть learn.javascript.ru :)
function Blah () {

}

var myBlah = new Blah();

Василий Б. 26.02.2013 18:13

да читал я 20 раз это все уже. меня интересует оптимальный подход создания объектов а-ля класс.

Конструкторы мне не нравятся. Они не логичны что ли... вот я на конструкторе писал класс для работы с Аякс:

function Ajax()
{
    /**
     * Экземпляр XMLHttpRequest.
     * 
     * @access private
     * @var object XMLHttpRequest
     */
    var req;

    /**
     * HTTP-заголовки
     * 
     * @access private
     * @var array
     */
    var httpHeaders = {"If-Modified-Since" : "Sat, 1 Jan 2000 00:00:00 GMT"}

    /**
     * Описания статусов readyState
     * 
     * @access private
     * @var array
     */
    var statuses = ['Не инициализиован',
                    'Метод open() вызван, запрос не отправлен',
                    'Запрос был передан',
                    'Ответ сервера принят частично',
                    'Данные приняты, соединение закрыто'];

    this.addUniqueQS = false;
    
    /**
    * Создает объект XMLHttpRequest
    * 
    * @param void
    * @return XMLHttpRequest|null
    */
    (function()
    {
        if (window.XMLHttpRequest) {
            try {
                req = new XMLHttpRequest();
            } catch (e){}
        }
        // only IE 6 =< 
        else if (window.ActiveXObject) {
            try {
                var aVersions = ["MSXML2.XMLHttp.5.0", "MSXML2.XMLHttp.4.0", 
                                 "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp",
                                 "Msxml2.XMLHTTP", 'Microsoft.XMLHTTP'];

                for (var j in aVersions)
                {
                    try {
                        req = new ActiveXObject(aVersions[j]);
                        break;
                    } catch (e){}
                }
            } catch (e){}
        }

        if (!req)
        {
            throw new Krugozor_Exception(_CONFIG.messages[_CONFIG.lang].ajax.xmlhttprequest_not_found);
        }
    })();

    /**
     * Метод принимает в качестве аргумента анонимную функцию,
     * которая привязывается к обработчику onreadystatechange.
     * Функция должна иметь интерфейс для принятия двух объектов:
     * - первый объект - ссылка на Ajax объект
     * - второй объект - ссылка на объект xmlHttpRequest 
     * 
     * @param object 
     * @return void
     */
    this.setObserverState = function(observer_function)
    {
        var ajax_object = this;
        var req_object = req;

        this.observerState = function()
        {
            if (req_object.readyState == 4) {
                if (req_object.status == 200) {
                    observer_function(ajax_object, this);
                }
            }
        }

        return this;
    }
    
    /**
     * Абстрактный предопределяемый метод, привязанный
     * к обработчику onreadystatechange.
     * Пример предопределения в клиентском скрипте:
     * 
     * var ajax = new Ajax();
     * ajax.observerState = function()
     * {
     *     if (ajax.getHttpRequest().readyState == 4) {
     *         if (ajax.getHttpRequest().status == 200)
     *         {
     *             alert(ajax.getHttpRequest().responseText)
     *         }
     *     }
     * }
     *
     * @param void
     * @return mixed
     */
    this.observerState = function()
    {
        if (req.readyState == 4) {
            if (req.status == 200) {
                alert('Метод observerState должен быть предопределен перед использованием объекта');
            }
        }
    }

    /**
     * Отправляет GET-запрос по адресу url
     * 
     * @param string url
     * @param boolean синхронность запроса.
     *        true - асинхронный, false - синхронный
     * @return void
     */
    this.get = function(url, synchronicity)
    {
    	if (arguments.length == 1)
    	{
    		synchronicity = true;
    	}
    	
        if (!!this.addUniqueQS)
        {
        	url += (url.indexOf('?') == -1 ? '?' : '&') + Math.floor(Math.random()*1000);   
        }
        
        req.open('GET', url, !!synchronicity);

        this.sendHeaders();

        if (synchronicity)
        {
            req.onreadystatechange = this.observerState;
        }

        req.send(null);

        return this;
    }

    /**
     * Устанавилвает заголовок HTTP с ключом key 
     * и значением value.
     * 
     * @access public
     * @param key имя HTTP-заголовка
     * @param value значение HTTP-заголовка
     * @return void
     */
    this.setHeader = function(key, value)
    {
        httpHeaders[key] = value;
    }

    /**
     * Отправляет HTTP-заголовки.
     * 
     * @access private
     * @param void
     * @return void
     */
    this.sendHeaders = function()
    {
        for (var i in httpHeaders)
        {
            if (typeof httpHeaders[i] == 'string')
            {
                req.setRequestHeader(i, httpHeaders[i]);
            }
        }
    }

    /*  Методы получения ответа (XMLHttpRequest) и результата из ответа */

    /**
     * Возвращает экземпляр объекта XMLHttpRequest
     *
     * @param void
     * @return object req
     */
    this.getHttpRequest = function()
    {
        return req;
    }

    /**
     * Возвращает статус HTTP
     * 
     * @param void
     * @return int
     */
    this.getStatus = function()
    {
        return req.status;
    }

    /**
     * Возвращает стандартный объект JS, который является
     * "сериализованным" объектом в виде строки текста ответа - JSON.
     *
     * @access public
     * @param void
     * @return object
     */
    this.getJson2HashByKey = function()
    {
        return eval( "(" + req.responseText + ")" );
    }

    /**
     * Возвращает текст ответа сервера
     *
     * @access public
     * @param void
     * @return string
     */
    this.getText = function()
    {
        return req.responseText;
    }
}

Пример использования:

// Для асинхронных запросов:
    var async_ajax = new Ajax();
    async_ajax.setObserverState(
        function(ajx, xhr) {
            alert( ajx.getJson2HashByKey() + '   ' + xhr.responseText);
        }
    ).get("/ajax/country");



Ещё один способ пробовал, но это я так понимаю jQuery стиль и это сингелтон:
var click = (function(){
    var prop = '...';
    var method = function(){
        // do it 
    }

    return {
        init: method
    }
})();

var myClick = click.init();
и т.д.

Этот способ по вашему лучше, чем описанный в первом посте?

melky 26.02.2013 19:18

Цитата:

Сообщение от Василий Б.
Этот способ по вашему лучше, чем описанный в первом посте?

этот способ тоже ужасен.

в JavaScript прототипное программирование... а это значит, что вы должны писать таким способом, и более пока никак

идти против языка нет смысла - только добавит тормозов

конечно, можно заюзать библиотеки для облегчения объектного программирования, но это уже сами :)

Василий Б. 27.02.2013 11:03

melky,
Цитата:

в JavaScript прототипное программирование... а это значит, что вы должны писать таким способом, и более пока никак
ок. как создать "класс" более правильно?
Я кажется понимаю что такое прототип и чем он отличается от свойства объекта:
function Base(){
	this.f11 = function(){alert('call Base::f11')}
};
Base.prototype.f1 = function(){alert('call Base::f1');}

function Parent(){
	this.f11 = function(){alert('call Parent::f11')}
}
Parent.prototype.f1 = function(){alert('call Parent::f1')}
Parent.prototype = new Base();

var o = new Parent();
o.f1(); // `call Base::f1` - перезагрузили Parent::f1 методом из прототипа Base?
o.f11(); // `call Parent::f11` - перезагрузки не произошло, т.к. ф-ия f11() - свойство объекта, а не прототипа

правильно ли я понимаю отличие прототипа от свойства объекта?

danik.js 27.02.2013 11:24

Это один и тот же подход. Просто в конструкторе нужно назначать свойства экземпляра (личные), а в прототипе - свойства прототипа (обычно методы). Почитай хотя бы http://learn.javascript.ru/prototype

Василий Б. 27.02.2013 12:05

danik.js,
как из можно назвать личными, если
function Base(){
	this.f = function(){alert('Base::f')}
};

function Parent(){}
Parent.prototype = new Base();

(new Parent()).f();

метол f доступен в Parent! Не могу понять почему.. метод f - это свойство объекта Parent, а не прототипа Base?

danik.js 27.02.2013 12:16

Под "личными" я не имел ввиду private. Я имел ввиду принадлежащие экземпляру.
Цитата:

Сообщение от Василий Б.
метол f доступен в Parent! Не могу понять почему.. метод f - это свойство объекта Parent, а не прототипа Base?

Ты явно не читал материал по ссылке.

Василий Б. 27.02.2013 13:04

danik.js,
да читал я материал, читал! Там написано следующее:
Цитата:

При создании объекта через new, в его прототип __proto__ записывается ссылка из prototype функции-конструктора.
и
Цитата:

Если один объект, например, rabbit, имеет специальную ссылку __proto__ на другой объект animal, то все свойства, которые ищутся в rabbit, будут затем искаться в animal.
Это понятно.
Свойства ищутся в прототипе объекта.

Правильно ли я понимаю, что только при использовании new в дочерний объект подтягиваются ВСЕ свойства объекта-прототипа?

Base = function(){};
Parent = function(){};
Parent.prototype = new Base();

var o = new Parent();

Base.func_as_prop = function(){};
Base.prototype.func_in_proto = function(){};

console.log(o.func_as_prop); // undefined - в объекте o нет свойства, т.к. оно не  было подтянуто конструктором
console.log(o.func_in_proto); // function

console.log(Base.func_as_prop); // function
console.log(Base.func_in_proto); // почему undefined? почему не ищет в своем прототипе?

melky 27.02.2013 13:23

Цитата:

Сообщение от Василий Б.
Правильно ли я понимаю, что только при использовании new в дочерний объект подтягиваются ВСЕ свойства объекта-прототипа?

потому что они наследуемы ... у каждого свойства есть свой дескриптор

в ES5 добавили возможность смотреть на них, и управлять ими
Object.getOwnPropertyDescriptor({  "foo": "bar" }, "foo");
// удобнее смотреть в консоли разработчика


ссылка на MDN

как видим, нет параметра "наследуемый", т.е. все свойства в объектах JavaScript по умолчанию публичные.

поэтому мы их видим, когда присваиваем прототипу одного объекта другой.

danik.js 27.02.2013 13:41

Цитата:

Сообщение от Василий Б.
// почему undefined? почему не ищет в своем прототипе?

Потому что свойства ищутся в __proto__ , а не в prototype.
Вобще, открой консоль, накалякай там ченибудь да поизучай объекты. Я использую Chrome, там консоль как мне кажется самая удачная.


Часовой пояс GMT +3, время: 05:46.