Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
сам удивляюсь. чем больше узнаю яваскрипт, тем больше убеждаюсь, что придумал его шизофреник…Вывод неверный. JavaScript никто не придумывал! Он постепенно создавался путём добавления средств, которых больше всего не хватало программистам, пишущим интерпретатор. Причём они добавлялись туда, куда было проще всего их добавить программистам. Тот же путь — у языков Perl (ужас), PHP (ужас, ужас, ужас), etc…
ПримесиНа выполнение конструкций вида this.property = value; требуется время. Поэтому, чем больше объектов в системе и чем сложнее функции-конструкторы этих объектов, тем медленнее будет работать наш код.
… при желании мы можем вообще не использовать прототипы
bird=function() {}
// ...
duck=function() {}
duck.prototype=new bird();Подобный код всегда вызывал у меня непонимание: с одной стороны функция-конструктор является как-бы инициализатором свойств нового объекта, а с другой — может выступать в роли конструктора прототипа другой функции. Напонимание в следующем: как, используя данный метод, передавать параметры для инициализации нового объекта? Ответа я так и не нашёл (вариант проверки параметров на undefined в функции-конструкторе не подходит, потому что код становится не красивым =)bird=function() {} //Это конструктор птички
bird.prototype.cry=function(){alert(this.crytext);} //Птичка умеет кричать
bird.prototype.fly=function(){alert('Я лечу!');} //и летать
make_a_bird=function(crytext) {
var nb=new bird();
nb.crytext=crytext;
return nb;
}
В чём проблема-то? Если вам нужна птичка - используете make_e_bird, а простой конструктор - оставьте для прототипов.А зачем нам «конструктор» и «сделай_на_основе_конструктора»?А это уж у Covex'а нужно спросить. Если мы хотим по разному создавать объекты, являющиеся прототипами и не являющиеся таковыми — но нам нужны две разные функции. В большинстве случаев это действительно не нужно.
Ладно бы Вы штамповали каскадно объекты через фабрику, но это здесь — уже создана «порождающая сущность» (конструктор), но потом еще и «порождающая-порождающая сущность».Эта так же раница, которая в C++ имеется между operator new класса и конструктором, а в Java — между конструктором и фабрикой. Порождать это дело руками или автоматом — дело вкуса.
function A() { alerr('A'); }
A.prototype.fn = function () {};
function B() {
B.parent.apply(this, arguments);
alert('B');
}
// связка прототипов для делегирования
var __wrapper = function() {};
__wrapper.prototype = A.prototype;
B.prototype = new __wripper();
B.prototype.constructor = B;
B.parent = A;
var obj = new B(); // alert('A'), alert('B')var klass= arguments.callee; // Говорят, что классы в JS - от лукавого !
// и
eval( 'var Cat= ' + Animal ); // А eval тем более =)function Func() {}
var Obj = new Func();
alert(Func.constructor == Function);Мне же хотелось иметь объект-функцию Func2, у которой был бы другой фактический конструктор — не Function, а какая-то другая моя функция.function class( methods ){
return function(){
if( ! this.constructor ) return null;
....
}
}
var animal = class({...})
var cat = new animal( args )
Array.prototype.randomize = function() {}
var a1 = new Array()
a1.randomize() // ok
var a2 = []
a2.randomize() // ошипкаvar a = function (){
this.set = function(a){this.var = a};
}
var b = new a();
b.set(1);var a = function (){}
a.prototype.set = function(a){this.var = a};
var b = new a();
b.set(1);var b = function (){}
b.set = function(a){this.var = a};
b.set(1);set не нужен снаружи, то его можно объявить только внутри области видимости функции, и будет еще быстрее.var a = function() { this.c = 0; }
a.prototype.set = function() { this.c++; }
var b = function() {
this.c = 0;
this.set = function() { this.c++; }
}
function T(T1) {
var T2 = new Date().getTime();
if (T1) alert(T2-T1);
return T2;
}
var i, T1, cnt = 100000, obj;
/* Первый тест: много созданий объекта и вызовов функции */
T1 = T();
for (i=0; i<cnt; i++) {
obj = new a();
obj.set();
}
T1 = T(T1);
for (i=0; i<cnt; i++) {
obj = new b();
obj.set();
}
/* На первый тест тратится на много меньше времени, чем на второй т.к. в первом случае на создание нового объекта set не тратится время */
/* Второй тест: одно создание объекта и много вызовов функции */
T1 = T(T1);
obj = new a();
for (i=0; i<cnt; i++)
obj.set();
T1 = T(T1);
obj = new b();
for (i=0; i<cnt; i++)
obj.set();
T1 = T(T1);
/* На первый тест тратится немного больше времени т.к. во втором случае не нужно искать функцию в прототипе объекта */
* This source code was highlighted with Source Code Highlighter.obj=new String('hello'); //Создаем строку как объект
simple='hello'; //Создаем примитивное значение>>> test = 'test'; "test" >>> String.prototype.some_property = 12 12 >>> test.some_property 12
String objects are created by calling the constructor new String(). The String object wraps Javascript's string primitive data type with the methods described below. The global function String() can also be called without new in front to create a primitive string. String literals in JavaScript are primitive strings.
var Element = function(tag, html, className, parent) {
var e = document.createElement(tag || 'div');
e.innerHTML = html;
e.className = className;
(parent || document.body).appendChild(e);
e.customMethod = function() { /*...*/ };
return e;
};
var test = new Element('span', 'Test', 'warning');
var Request = function(url, method) {
var req = new XMLHttpRequest();
req.open(method || 'GET', url, true);
/* other initialization */
req.customMethod = function() { /*...*/ };
return req;
};
var test = new Request('page.php');
var test = new Request('page.php');
test.send(); // вызов метода встроенного класса XMLHttpRequest
test.customMethod(); // вызов своего метода
var Request = function(url, method) {
var req = new XMLHttpRequest();
return req;
};
Request.prototype = {
method1: function() {},
method2: function() {}
};
var test = new Request('page.php');
A value is an entity that takes on one of nine types. There are nine types (Undefined, Null, Boolean, String, Number, Object, Reference, List, and Completion). Values of type Reference, List, and Completion are used only as intermediate results of expression evaluation and cannot be stored as properties of objects.
В этом коде, каждая строчка выполняет одно и то же действие: создает функцию (а функция, как мы помним, это полноправный объект) и делает переменную test ссылкой на нее. С точки зрения интерпретатора нет никакой разницы, каким именно способом мы воспользуемся — результат будет один.
А, если серьезно, то — расскажите, почему Вы так думаете, откуда, на основе чего Вы пришли к таким выводам?Я просто наблюдал за этими языками в то время как они создавались и за попытками сделать из дерьма конфетку. Результат… тот ещё. Самые ужасные ляпы удалось убрать (по-моему PHP4 был чуть ли не единственным языком в котором объекты передавались по значению, а не по ссылке), но чудес всё ещё хватает и там и там. Например пресловутая модель на основе протипов: штука простая и красивая но кому и зачем пришла в голову идея что прямой доступ к полу __proto__ людям не нужен? В результа вместо простых и понятных манипуляций приходится изобретать кучу странных приёмов. Но, конечно, переплюнуть PHP где до сих пор оператор == — не отношение эквивалентности, а операторы < и > — не отношения порядка весьма сложно.
приходится изобретать кучу странных приёмовТак это же самое интересное! =)
А с JavaScript'ом-то где дерьмо было?В ранних версиях нельзя было даже надёжно сравнить два объекта! Операторы === и !== появились в версии 1.3! В ранних версиях объекты функций прямо так и принадлежали к объекту «функция» и к ним был доступ снаружи (это отменили не то в версии 1.4, не то в 1.5). И вообще там было много чудес. Которые, в частности, до сих пор не позволяют сделать нормальный многопоточный движок…
Разве, во всех языках порожденный объект имеет доступ к своему конструктору (классу)?Нет, но зачем нам создавая динамический язык создавать какое-то дурацкое ограничение на ровном месте? Чтобы устроить всем программистам «кузькину мать»?
К тому же — лазейку оставили — через .constructor.prototype (это «куча странных приемов»?)Эта лазейка не позволяет изменять поле [[Prototype]] (которое, кстати, можно было бы разрешить делать не только указателем на объект, но и, скажем, массивом — на скорость это особо не повлияло бы, но зато сделало бы mix-in'ы возможными).
А в JS тоже. Поскольку ни там, ни там строгой типизации нет. Но дерьмо ли это? Для эквивалентности в обоих языках есть ===.Я боюсь вы даже не поняли о чём я :-( Отношение эквивалентности не имеет никакого отношения к оператору эквивалентности и наличие === отнюдь не означает что == не должно быть отношением эквивалентности. Скажем в Lisp'е тоже есть несколько операторов сравнения: «eq?» «eqv?» «equal?» «=» и прочие — и все они являются отношениями эквивалентности. Как и должно быть в нормальном языке — независимо от того динамически он типизирован или статически. Конечно в тех языках где операторы можно расширять (многие диалекты Lisp'а) можно расширить эти операторы так, что они перестанут быть отнешениями эквивалентности — но это уже от программиста зависит.
а, т.е. был один ==, который не учитывал типы? и 1 == '1' было false, да?Наоброт 'popa' == 0 было true. NaN появился позже.
Что значит «нельзя было надежно сравнить»? А как в других («не дерьмовых») языка дело обстояло в то время?Как и должно быть, собственно: если типы разные — то объекты разные, если справа и слева один объект — то они уж точно равны :-) Вот если типы одинаковые и объекты разные — можно двигаться дальше. Ту заплатку, которую в результате придумали сложно назвать вменяемой: в JavaScript a==a может быть false и даже a===a может быть false — что как бы несколько дико...
function surprise() {
test.arguments.i = 2;
}
function test() {
var i=1;
surprise();
alert(i); // Ой какой surprise!
}
test();Как и в любой разрботке, которая эволюционирует. А какие Вы видели «недерьмовые» эталонные разработки?Pascal, Java, даже где-то C или Python. Да, там есть чудеса и недоработки — но такого «коврового бомбометания» несуразностей как в PHP или JavaScript там никогда не было.
А какие «чудеса» не позволяют?Ну тут беда не в JavaScript'е, а в DOM'е — но на практике от этого не легче. Много чего доступно через глобальные переменные.
В смысле? Несколько объектов (массив) в одном звене цепи наследования? А какая разница.Большая разница: посмотрите на «классику жанра» — ios, iostream, ostream и iostream в C++.
И почему «тогда бы были доступы примеси»? Где точный алгоритм реализации примеси? Примесь может быть реализована и вклиниванием хидден-класса в цепь наследования и, так же, расширением самого объекта родными слотами.Про примеси даже Wikipedia неплохо пишет. Хидден-классы их не реализуют ни разу (ещё раз на пример с ios/istream/ostream/iostream, пожалуйста), множественное наследование и множественные прототипы — реализуют запросто. Через расширение объекта родными слотами можно сделать вообще что угодно, но там придётся много наворотов (понятно что в динамических языках можно менять и istream и ostream и эти изменения сразу и непосредственно влияют на iostream).
Почему не означает? Реализаций — много, идей — еще больше. Как реализовали, ==, так, вероятно, и задумывалось.Нет, увы. Реализовали — как левая пятка возжелала. Потом добавили NaN чтобы дыру заткнуть. Получился дурдом.
А покажите пример, эквивалентности объектов в разных языках. Мне интересно.Классическая тройка эквивалентностей из Lisp'а не подойдёт? Все три оператора «eq?», «eqv?», «equal?» — полноценные операторы эквивалентности; «=», «char=?» и прочие — специализированные (в смысле: могут кинуть исключение, но на тех данных, на которых не кидают — являются операторами эквивалентности). «eq?», «eqv?», «equal?» как бы охватывают всё более и более общие случаи:
А я вот не в курсе. Это как-то оговаривалось? Где-то описывался алгоритм ==?Ну какое-то описание там было. Но вот насколько оно соответствовало действительности? Если судить по описанию и функция sort() должна была работать — а она это начала делать на всех платформах только в JavaScript 1.2… Я же говорю — сильно быстро делали, не до того было. Самый разгар «браузерных войн», Netscape 2.0, 3.0, 4.0, MS IE 3.0, 4.0, etc — где уж тут думать о красоте языка? Да и не ожидал тогда никто, похоже, что на JavaScript кто-то будет большие и сложные программы писать...
Почему 1 == '1' при разных типах не может быть true, если типизация динамическая?Потому что если программа у вас не совсем тривиальна то сравнение переменных разных типов обозначает что вы что-то перепутали и нужно либо кинуть исключение, либо уж на худой конец вернуть ложь. У скриптов в 10 строк (под которые JavaScript и затачивался) ситуация другая, но сейчас — от такого поведения намного больше вреда, чем пользы.
Подробней расскажите, я не понял, что Вы имеете в виду.ios содержит базовую информацию (скажем режимы вывода: hex/oct/dec, etc), istream реализует функцию read(), ostream — функцию write(), iostream — просто умеет делать и то и другое. Конечно если руками копировать методы туда-сюда, то можно сделать некоторое подобие, но в динамическом языке хочется чтобы после доваление метода readline() в istream и writeln() в ostream они появились автоматом и в iostream()!
Кто сказал, что во всех остальных должна быть такая же? А еще, кроме Лиспа?Не обязательно. С учётом того, что стандарт IEEE прямо требует чтобы NaN не подчинялся логике я бы признал ситуацию с == удовлетворительной если бы аномалии касались бы только этого проклятого числа. Так она ведёт себя, скажем, в Python'е (objects of different types always compare unequal, and are ordered consistently but arbitrarily). Но нет же! Вполне может быть так что a==b, b==c, но a!=c. Что за @!@^*#)(*!? А когда у вас a<b, b<c, но c<a — то что можно о таком языке сказать? Правильно: ничего хорошего. Два языка для которых такие примеры можно придумать за рамками операций с плавающей точкой (которые всегда опасны и требуют особой квалификации от программиста) это JavaScript и PHP. Именно потому что они пытаются «помочь». Ровно этими благими намерениями вымощена дорога к бесконечным проблемам с безопасностью в PHP и JavaScript'е.
Я не пойму, Вы как-то за меня, зачем-то, говорите, пытаясь приписать мне то, чего нет :) Говорите за себя. С чего бы это значило (да еще и в таком утвердительном ключе :)), что я что-то перепутал? Если Вы что-то перепутали (и привыкли к строгой типизации), это же не означает, что все остальные тоже перепутали, так? Или не так? :)Это простая статистика. Всё легко проверяемо. У вас есть пример большой (скажем от 1000 строк) программы на JavaScript где бы больше половины сравнений при нормальной работе могли бы сравнивать объекты разных типов? Покажите — интересно. А если большая часть сравнений сравнивает объекты одинаковых типов, то с точки зрения безопасности очевидно что лучше бы чтобы объекты разных типов были бы и вообще несравнимы — меньше вероятность сделать случайную ошибку.
Может там и было накопировано из обоих сущностей (так тогда и копируйте), если is и os были подмешены (пронаследованы) к ios, то это уже другая реализация.Дык эта, мы, в общем-то, про примеси говорим.
Покажите мне примеры без NaN'ов, без сравнений {} и {} в JavaScript. Вероятно, все будет объяснимо, можно будет и в стандарте уточнить.Да легко:
var a1="1";
var b1=1;
var c1="01";
alert(a1==b1);
alert(b1==c1);
alert(a1==c1);
var a2=7;
var b2="13";
var c2="5";
alert(a2<b2);
alert(b2<c2);
alert(a2>c2);
И пример не приводите.А причём тут пример? Речь идёт от статистике — тут нужно брать реальный код и смотреть. Хотите чтобы я тут вставил несколько тысяч строк jQuery или Prototype?
Что такое? Это полный субъективизм, выработанный привычкой к конкретной технологии.Совершенно верно. Но вот именно этот субъективизм и определяет практически качество языка. Язык может быть безумно хорош со всех теоретических точек зрения, но если он не годится для реальных программистов — то язык дерьмо.
И что легко? Не интуитивно?Причём тут интуиция? Речь идёт о последствиях ошибок. Куча алгоритмов завязана на то, что оператор равенства является отношением эквивалентности, а операторы сравнения — отношением порядка. Нарушая эти фундаментальные посылки вы автоматически усугубляете последствия ошибок: несгибаемые алгоритмы могут согнуться (скажем отсортированный массив может стать неотсортированным после удаления одного элемента из него) и обнаружение ошибок превратится в лотерею.
И кто Вас строки в ожидании чисел просит сравнивать?Errare humanum est. Если вы в дизайне языка не учитываете тот факт, что человек делает ошибки — то вас следуют переквалифицировать в дворники.
Приводите все к числам и сравнивайте. Перед этим почитайте об алгоритмах приведения и о том, как в данной технологии работаю операторы ==, ===, <, >.Разумеется так и делается. Но тот факт что если этого не сделать, то получаешь не вменяемое сообщение об ошибке, а сошедшую с ума программу язык аж никак не красит.
Я понимаю, что Вы привыкли к строгой типизации. Здесь не так. Здесь не строгая динамическая. И работа вышеприведенных операторов — плата за это.Нифига подобного. Ерунда. Чем типизация в Python'е, Comon Lisp'е или Scheme так принципиально отличается от того, что имеется в недоязыках типа PHP или JavaScript? Однако же это не мешает им иметь вменяемые операторы сравнения и порядка (по модулю маразма с IEEE 754 — но это отдельная и, слава богу, небольшая песня).
Локальная привычка. Мысль в локальной идеологии.Ну если считать что половина математики и почти вся информатика — это «локальная привычка», и нужно всего-то навсего разработать специальную «JS/PHP информатику», то да. Извините — но к таким жертвам я не готов.
Я, как отмечал, поверхностно знаю Comon Lisp'е, со Scheme — не знаком. С Python'ом знаком, но сейчас его под рукой нет. Напишите примеры (те же самые, со строками, числами, и т.д.) — посмотрим, что там происходит.Смотрите:
>>> print 7 < "13"
True
>>> print "13" < "5"
True
>>> print 7 > "5"
False
>>> print "1" == 1
False
>>> print 1 == "01"
False
>>> print "1" == "01"
False
Если технология делает оговорки и однозначность (типологическая) множества может варьировать — соответственно и отклонения в операциях так же будут присутствовать. А то, что их кто-то не понимает (или не желает понимать? :)) — дело десятое.Хмм. Интересная точка зрения. А сейчас замутим опрос :-) Посмотрим что скажет народ…
Почитайте особенно пункт 3 и ниже, и все станет ясно.Что именно станет ясно? Что проблема не в конкретной реализации языка, а в самом языке? Так это и так ясно было.
Опять — почему? Возможно, в той версии, объект arguments был связующим звеном (я не в курсе, и спецификацию для 1.2 не читал).Это задокументировано только в версиях в которых эта фича была отменена. Очевидно потому что появилась она как артефакт от «реализации на скорую руку» и никто особо не думал к каким последствиям это может привести.
И почему обязательно объект обязательно должен иметь инваринты?Что его тогда отличает от ассоциативного массива, если у него никаких инвариантов нет?
Поэтому объект от ассоциативного массива (который является лишь теоретической структурой данных; как, собственно, и объект) может ничем не отличаться.Зачем тогда умножать сущности без необходимости?
Вы интересуетесь? Или даете мне помыслить, чтобы я пришел к какому-то ответу, который Вы, якобы, знаете?Нет. Я уточняю терминологию. Я всегда считал что объект — это как минимум набор инвариантов, связывающих биты, хранящиеся в этом объекте. Если для вас объект что-то другое — скажите что. Если вы не можете даже сформулировать что такое объект — то о чём мы тут говорим?
Примеры в студию.Классический пример объекта в JavaScript — это массив. Инвариантом в нём является соотношение между длиной массива (тем, что возвращает .length) и содержимым (количеством хранящихся в массиве элементов). Если эти фундаментальные величины начинают расходиться — пользоваться массивом нельзя.
Инварианты — лишь часть (малая часть!) объектов и классов, чтобы ограничить объекты (да, я понял, инвариация Вас притягивает все из-за той же строгой типизации, вернее, вытекает, как следствие ).Инварианты — это то, что остаётся если выбросить всё наносное.
Применительно к программированию — всего лишь абстрактный тип данных, не более.Окей — абстрактный тип данных, так абстрактный тип данных. А что такое абстрактный тип данных? Это набор действий сохраняющий набор инвариантов. В примере с массивом выше: у нас может быть предусмотрено действие, вставляющие новый элемент в середину массива… а может не быть. Но ежели такое действие есть и оно не меняет длину массива (или, наоборот, увеличивающее его на два) — у нас в программе ошибка.
(ага, инвариация — основа ООП :) простите, но это не так.)Простите, но если вы уберёте из массива кучу операций (pop, push, reverse, sort) — он останется массивом. А если уберёте его инварианты — не останется вообще ничего.
Ой, если я скажу, что и массивов в JS нет (ну, или более точно, что он мало чем отличается от любого другого объекта) и что .length (не зависимо от количества и сущности ключей и элементов) может возвращать не то, что Вы ожидаете, это только подкрепит Вашу «веру»? :)Я собственно потому и выбрал этот пример. То, что абстракции в JavaScript «протекают» — не делает этому языку чести.
Это просто совокупность свойств для обособления. При этом, действия — тоже свойства.А вот это как раз самая ужасная кривизна в большинстве современных языков программирования. С какой стати действия имеют отношения к объектам? Они должны объединять объекты, а не принадлежать им! Рассмотрим четыре объекта: плоттер, монитор, круг и квадрат. Где должна жить функция draw? Разумный ответ нормального человека: нигде. У нас четыре функции draw: для вывода круга на плоттер, квадрата на плоттер, круга на монитор и квадрата на монитор. То, что эта простая естественная парадигма не реализуется в C/C++ или Java я ещё понять могу: ну там эффективность, всё такое. Но почему в скриптовых языках, где об эффективности и наносекундах говорить глупо — те же самые дурацкие ограничения??? В Common Lisp'е этих ограничений нет, ещё в парочке языков — тоже, но в большинстве языков снова и снова действия пихаются в объекты — где им совсем не место.
Да «нет» там массивов, это абстрактное выделение.С тем что массивов нет — соглашусь.
Здесь инварианты не уместны вообще (а уж тем более в ключе — «основа ООП»), поскольку все объекты mutable, и каждый объект может перегруппироваться в «массив».О какое сильное утвеждение! Извините — если вы написали программу, то вам разве не интересно чтобы она делала то, что требуется? А если «инварианты не уместны», а выяснив что в массиве у вас три элемента вы сможете из него вытащить только два — то как вы будете доказывать что работа вашей программы вообще имеет хоть малейшее отношение к тому, что требуется?
Ок, давайте тогда мне ссылку на «бумагу с печатью», где Вы подобные утверждения выискиваете.Из разных мест — и бумага с печатью не нужна если у вас есть голова и некоторые базовые знания CS.
Насчет draw — что мешает сделать 4 своих метода для каждого объекта?И как тогда будет выглядеть реализация вывода векторной картинки (суть списка векторных фигур) на какое-нибудь устройство вывода — на плоттер или монитор (по выбору пользователя вашей библиотеки)? А если нам ещё в PDF, SVG и PostScript это потребуется перегонять? А если кроме квадрата и круга у нас будет ещё с десяток примитивов? Да, можно обойтись и вообще без ООП — но по-моему нежелание признать что в данном примере классический ООП неудобен — просто говорит о том, что вы не хотите думать, а хотите на всё получить «бумагу с печатью».
Работа с объектами в JavaScript: теория и практика