Javascript prototype class object
В переменную names мы записали объект, у которого есть два свойства – fname и lname – и ни одного метода. Откуда же взялся метод hasOwnProperty ?
Дело в том, что объект names не существует сам по себе, у него есть прототип – Object.prototype .
Выведем эту переменную в консоль:
Развернем загадочное свойство __proto__ :
Мы можем увидеть конструктор объекта names – функцию Object() – и множество методов под ним. Среди них – и hasOwnProperty . Все эти методы хранятся в прототипе Object , но доступны и самому объекту names .
Другими словами, все объекты в JavaScript создаются с использованием прототипа Object.prototype :
Теперь в свойстве __proto__ находится другая функция-конструктор и метод speak .
Object.create создает новый объект, автоматически устанавливая ему указанный прототип.
Ключевое слово new
Все то же свойство __proto__ , которое ссылается на Animals.prototype (-> Object.prototype).
Мы видим, что собственные значения свойств ( name и specie ) определяются внутри метода constructor . Кроме него создаются дополнительные функции sing и dance – методы прототипа.
Подкапотную реализацию этой конструкции мы разбирали только что – это функция-конструктор + ключевое слово new .
В свойстве clara.__proto__.constructor лежит класс Cats , через него осуществляется доступ к методу whiskers() . Дальше в цепочке прототипов – класс Animals , с методами sing() и dance() . name и age – это свойства самого объекта.
Перепишем этот код в прототипном стиле с использованием метода Object.create() :
function Animals(name, age) < let newAnimal = Object.create(animalConstructor); newAnimal.name = name; newAnimal.age = age; return newAnimal; >let animalConstructor = < sing: function() < return `$can sing`; >, dance: function() < return `$can dance`; > > function Cats(name, age, whiskerColor) < let newCat = Animals(name, age); Object.setPrototypeOf(newCat, catConstructor); newCat.whiskerColor = whiskerColor; return newCat; >let catConstructor = < whiskers() < return `I have $whiskers`; > > Object.setPrototypeOf(catConstructor, animalConstructor); const clara = Cats("Clara", 33, "purple"); clara.sing(); // "Clara can sing" clara.whiskers(); // "I have purple whiskers"
Метод Object.setPrototypeOf принимает два аргумента (объект, которому нужно установить новый прототип, и собственно сам желаемый прототип).
Функция Animals возвращает объект, прототипом которого является animalConstructor . Функция Cats создает объект с помощью конструктора Animals , но принудительно меняет его прототип на catConstructor , добавляя таким образом новые свойства. catConstructor в свою очередь тоже получает прототипом animalConstructor , чтобы образовалась цепочка прототипного наследования.
Таким образом, обычные животные будут иметь доступ только к методам animalConstructor , а кошки – еще и к catConstructor .
JavaScript оказался достаточно гибок, чтобы превратить свое прототипное ООП в классовое для удобства разработчиков.
Objects, Prototypes and Classes in JavaScript
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
Considering the fact that almost everything in JavaScript is an object, object oriented JavaScript code is very different from other object-capable languages. The JS object system is more of a prototype-based object system instead. Coming from a C++ background, I was aware of the Object Oriented programming paradigm and had a very rigid idea of how Objects and Classes should work. Exposure to other languages like Java only seemed to further establish this idea. While these languages have their own semantics on how objects and classes work; Javascript, for the new user, is quite a revelation. First off the bat, JavaScript objects are very different in the way they are created. There is no requirement for a class. Object instances can be created using the new operator:
Second, JavaScript objects are very flexible. While classic object oriented languages allow only property modification or property slots, JavaScript allows objects to modify their properties and methods; i.e. JavaScript objects have both property and method slots. My first thought at the discovery was “yeah freedom!” but this came with a cost — a need to understand the prototype property in JavaScript. The knowledge of the prototype is essential to a developer who wishes to implement any semblance of an object oriented system in JavaScript. All JavaScript objects are created from the Object constructor:
var Reptile = function(name, canItSwim) this.name = name; this.canItSwim = canItSwim; >
And the prototype allows us to add new methods to objects constructors, this means that the following method now exists in all instances of Reptile .
Reptile.prototype.doesItDrown = function() if (this.canItSwim) console.log(`$this.name> can swim`); > else console.log(`$this.name> has drowned`); > >;
// for this example consider alligators can swim and crocs cannot let alligator = new Reptile("alligator", true); alligator.doesItDrown(); // alligator can swim let croc = new Reptile("croc", false); croc.doesItDrown(); // croc has drowned
The prototype of the Reptile object is now the basis for inheritance, the doesItDrown method is accessible to both alligator and croc because the prototype of Reptile has this method. The prototype property is shared amongst all its instances and is accessible via the __proto__ property of a particular instance. Now, because of the existence of method slots and a common prototype instance property being shared across all instances, some very neat tricks are possible which are very weird to C++ folks:
croc.__proto__.doesItDrown = function() console.log(`the croc never drowns`); >; croc.doesItDrown(); // the croc never drowns alligator.doesItDrown(); // the croc never drowns
Change one instance’s prototype property or method, all instances of the object are affected. This means we could be deleting stuff as well. A croc tired of drowning could potentially do this:
delete croc.__proto__.doesItDrown alligator.doesItDrown(); //TypeError: alligator.doesItDrown // is not a function
Now no one gets to swim. This is just a silly example to show how fundamental the prototype is to the Object system in JavaScript and how it can be quite jarring to people from other object oriented languages. With the ES6 syntax, JavaScript has been provided the feature to create classes. However, the concept of true classes does not exist in JavaScript but it is emulated through prototype and the class syntax is just syntactic sugar around it. Therefore, understanding this behavior is important to realize the convenience and limitations of ES6 classes. With the new class syntax, Reptile would be defined as:
class Reptile constructor (name, canItSwim) this.name = name; this.canItSwim = canItSwim; > doesItDrown () if(this.canItSwim) console.log(`$this.name> can swim`); else console.log(`$this.name> has drowned`); > > let alligator = new Reptile("alligator", true); alligator.doesItDrown(); //alligator can swim
This does not mean it brings nothing new to the offer for prototype users, some pitfalls can be avoided by using ES6 classes, like making the new keyword mandatory for creating instances.
let croc = Reptile("croc", false); //TypeError: Class constructor Reptile cannot be invoked without 'new'
This is actually a good thing, since it prevents accessing the wrong context when using the object properties and methods, which is usually the global scope or the window object.
In Conclusion
Though JavaScript right now does certainly lack features like truly private members. It has made creating objects via class syntax, instead of prototypes closely resemble classes from other OO languages like C++/Java. PS. There has been a proposal to TC39 for creating truly private members in JavaScript classes, you can follow it here and contribute your opinion. If it were to be included in the next revision, then we’d have something like:
class Foo < #a; #b; // # indicates private members here #sum = function() < return #a + #b; >; > // personally this format reminds me of $variable in PHP. // I'm not sure if that's a good thing
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases. Learn more about us