#javascript
Прототипное программирование — это модель ООП которая не использует классы, а вместо этого сначала выполняет поведение класса и затем использует его повторно (эквивалент наследования в языках на базе классов), декорируя (или расширяя) существующие объекты прототипы. (Также называемое бесклассовое, прототипно-ориентированное, или экземплярно- ориентированное программирование.) Прошу помощи в объяснении данного текста.. Пытаюсь перейти паралельно на веб, сложно осваивается подход к Java Script после Java. Что такое прототипное программирование ? "это модель ООП которая не использует классы, а вместо этого сначала выполняет поведение класса" Чего ??? С пониманием объектно ориентированного программирования нету проблем.
Ответы
Ответ 1
В отличие от большинства других ОО-языков (Java, C#), объектная система в JavaScript основана на прототипах, а не классах. Классы, которые вы знаете по таким языкам, как Java, технически не существуют в JavaScript (JS). Вся иерархия объектов строиться на цепочках - прототипах. Object.prototype - объект, от которого "наследуются" все остальные объекты. Он содержит такие методы, как toString() или valueOf().Прототип у него равен null. Замечу, что Object это просто функция-конструктор для создания объектов: typeof Object // 'function' Object.prototype // объект Object {} Object.prototype.__proto__ // 'null' prototype, который используется в примере, применим только к функциям, а для созданных объектов используется __proto__ (или [[Prototype]]). Например, Array: typeof Array // 'function' Array.prototype.__proto__ === Object.prototype // true var arr = [1,3] arr.__proto__ // [] arr.__proto__ === Array.prototype // true arr.__proto__.__proto__ // объект Object {} arr.__proto__.__proto__ === Object.prototype // true Все методы массивов (slice(), splice()) хранятся в объекте Array.prototype, а прототип этого объекта узакывает на Object.prototype. Получается: arr -> Array.prototype -> Object.prototype -> null Так же с другими встроенными функциями-конструкторами, например Function, Date, Number и т.д. Сейчас все усложнилось ещё тем, что в новом стандарте (ES6) разработчики JS ввели class – удобный «синтаксический сахар» для задания конструктора вместе с прототипом. Насколько мне известно, его ввели в том числе специально для разработчиков, которые хотят писать на JS, но которых смущает, что в них нет классов :). На самом деле class в JS это обычная функция: class Car {} typeof Car // 'function' Поэтому я до сих пор считаю, что понятие класс и классическое наследование немного некорректны в отношении JS. Из плюсов прототипного наследования, наверно, это гибкость. Класс (например в Java) определяет все свойства для всех его экземпляров. Невозможно добавить свойства динамически во время выполнения. В тоже время в JS функция-конструктор определяет начальный набор свойств. Можно добавлять или удалять свойства динамически для отдельных объектов или сразу всем. Возможно после использования интерфейсов/абстрактных классов в Java это покажется не плюсом, а минусом, но если этим уметь пользоваться, то потом этого не будет доставать в других языках. // Функция конструктор function Calculator () {} Calculator.prototype.max = function (a, b) { return Math.max(a, b); } // Создадим два экземпляра (объекта) var ins1 = new Calculator(); var ins2 = new Calculator(); // Протестируем метод max: console.log(ins1.max(1, 5), ins2.max(1, 5)); // -> 5, 5 // Изменим метод прототипа, чтобы можно было выбирать макс. значение // передавая сколько угодно параметров Calculator.prototype.max = function () { var args = Array.prototype.slice.apply(arguments); return Math.max.apply(null, args); } // Протестируем опять: console.log(ins1.max(1, 5), ins2.max(1, 5)); // -> 5, 5 console.log(ins1.max(9, -5, 13), ins2.max(9, -5, 13)); // -> 13, 13 Мы так же могли поменять реализацию только для одного объекта (сделать как нужно в рамках задачи). Повторюсь, prototype, который используется в примере, применим только к функциям, а для созданных объектов используется __proto__ (или [[Prototype]]). Метод max будет находится в прототипе созданных объектов (ins1.__proto__.max), а прототипы у них указывают на один и тот же объект: ins1.__proto__ === ins2.__proto__ // true Интересный момент: ins1.__proto__ // объект прототип ins1.__proto__.constructor // function Calculator () {} - функция, с помощью который создаются объекты ins1.__proto__.constructor.prototype // объект, который является прототипом // Проверим? ins1.__proto__.constructor.prototype === ins1.__proto__ // true Другие достоинства, которые можно отметить: простота, мощность данного подхода, меньше избыточного кода, динамичность. Если интересует момент, почему прототипное наследование в JS похоже на классическое (по синтаксису, например, использование оператора new), то это легко объяснить. Создатель JS, Brendan Eich, хотел чтобы JavaScript стал младшим братом Java и пытался его сделать максимально похожим синтаксически. В общем я надеюсь, что вас ещё больше не запутал, просто нужно изучать изучать и ещё раз изучать :) UPD.: Пример использования "классов" из стандарта ES6. class Auto { constructor(options) { this.name = options.name; } getName() { return this.name; } } const car = new Auto({ name: 'Lightning McQueen' }); car.getName(); // 'Lightning McQueen' Все методы, объявленные в "классе" автоматически помещаются в прототип созданных объектов, а поля - в сам объект. car.__proto__.getName // function getName() {return this.name;} car.__proto__.name // undefined car.__proto__.constructor // Auto car.name // 'Lightning McQueen' Ну и прототип протитипа Auto будет ссылкать на Object.prototype car.__proto__.__proto__ === Object.prototype // true Код "класса" Auto (без методов) в стандарте ES5 будет выглядеть следующим образом: 'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Auto = function Auto(options) { _classCallCheck(this, Auto); this.name = options.name; }; Обычная функция-конструктор с проверкой (за счет вызова _classCallCheck), чтобы нельзя вызывать Auto как функцию. Это все же класс :) Auto() // Uncaught TypeError: Cannot call a class as a function(…) Для создания методов используются Object.defineProperty, а посмотреть как работает транспайлер для методов (переводит код из ES6 в ES5 и не только) можно тут: https://babeljs.io/repl/ (Babel - один из самых популярных транспайлеров). Рекомендую почитать: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Details_of_the_Object_Model https://learn.javascript.ru/prototypeОтвет 2
Классическое ООП и JS прототипы на самом деле имеют мало различий: Любая функция в JS (кроме кратких - стрелочных функций ES6) является в терминологии ООП классом: к любой функции JS можно применять оператор new, что делает её конструктором. Это уже расширяет позицию ООП, в котором объекты конструируют не функции, а классы. Далее вместо термина функция - использую термин класс. Прототип - это класс-предок объекта, всё как в ООП - разница только в том, что прототип в JS - это уже сконструированный "готовый" объект, а в классическом ООП "прототип" - неотделим от самого класса-потомка: то есть не является ни объектом, ни чем-либо "физическим". Как работает наследование в JS, аналогичное обычному ООП наследованию - легко понять по такому примеру: var classA = function(){this.x = function(){ this.a++; return this.a; } }; var classB = function(){ this.a = 2;}; classB.prototype = new classA(); console.log((new classB) instanceof classA); console.log((new classB).x()); Разве что прототипы могут больше, чем могут классы - например прототип(класс-предок говоря языком ООП) можно поменять/установить как для одного объекта, так и для класса в целом во время выполнения. Например можно типу-числу (Number) добавить поведение функции (вызов через скобки), см. скрещивание ужей с ежами в MDN. Где вы видели, чтоб в ООП можно было на лету подменить класс-предок?) При операции установки прототипа - происходит тоже самое, что произошло бы при подмене базового класса: а именно все свойства/методы объекта, не найденные у себя - ищутся уже в другом классе-предке, логика изменяется. Собственно эта возможность подмены/установки базового класса - и является главной фишкой прототипного программирования. Если нужно вызывать метод класса родителя, при том что он переопределён в текущем классе: до ES6 (в котором есть super) можно было делать так this.__proto__.someMethod.apply(this, [arg1, arg2]); . Длиннее, чем обычно в ООП - но суть остаётся той-же. Доступ к родительскому конструктору также имеется через this.__proto__.constructor.apply(this,[...]). Указываю на это - т.к. не очевидно поначалу - и прототипы кажутся совсем унылыми в плане ООП. ES6 и его синтаксис определения классов - это просто декорации старых добрых прототипов: то есть ничего принципиально нового в нём нет. Разве что ООП в JS стало удобнее. То есть ИМХО - Прототипное программирование это просто красное словцо. Ну да - нету protected, тоже сначала плевался, а потом понял что и не надо: инкапсуляция в JS очень красиво делается на замыканиях. Зато никто не мешает изобрести своё ООП если уж хочется(я бы не рекомендовал, но порой встечается в фреймворках), а также делать объекты мутанты - например массив может быть одновременно функцией через setPrototypeOf . Как прототипы описаны в книжках и пособиях по JS дело другое: в ответе описана краткая помощь для разбора прототипов людям, знающим другие ООП-языки.Ответ 3
По-моему прототипное программирование очень просто для понимания. У тебя есть совокупность объектов и одни из них наследуют свойства или методы других. Вот и всё, тут просто не появляется лишняя сущность "класс". "это модель ООП которая не использует классы, а вместо этого сначала выполняет поведение класса" - а это неправильный перевод вот этого фрагмента: Prototype-based programming is an OOP model that doesn't use classes, but rather it first accomplishes the behavior of any class and then reuses it (equivalent to inheritance in class-based languages) by decorating (or expanding upon) existing prototype objects. Впрочем, тут и в оригинале заумно написано.
Комментариев нет:
Отправить комментарий