OOJS: Обектно-ориентиран JavaУрок за скриптове с пример
⚡ Умно обобщение
Обектно ориентиран JavaСкриптът моделира обекти от реалния свят като обекти, които носят свойства и методи. Тази страница обяснява литералите на обекти, конструкторските функции, прототипите, ES6 класовете, наследяването и частните полета с изпълними примери, които можете да изпълните и проверите.

Какво представлява концепцията OOPS JavaСценарий?
Много пъти променливите или масивите не са достатъчни за симулиране на ситуации от реалния живот. JavaСкриптът ви позволява да създавате обекти, които действат като обекти от реалния живот. Студент или дом могат да бъдат обекти с много уникални характеристики. Можете да създавате свойства и методи на обектите си, за да улесните програмирането. Ако обектът ви е студент, той ще има свойства като собствено име, фамилия и идентификатор, както и методи като calculateRank или changeAddress. Ако обектът ви е дом, той ще има свойства като брой стаи, цвят на боята и местоположение, както и методи като calculateArea или changeOwner.
Обектно-ориентирано програмиране в JavaСледователно скриптът се основава на четири идеи, с които ще се срещнете на тази страница: групаping състояние и поведение в обект, повторно използване на план чрез конструктор, споделяне на методи чрез прототипа и ограничаване на директния достъп до вътрешни данни. Ако сте начинаещ в езика, започнете с това въведение в JavaСценарий и след това се върнете тук.
Как да създадете обект
Можете да създадете обект като този:
var objName = new Object(); objName.property1 = value1; objName.property2 = value2; objName.method1 = function() { // line of code }
ИЛИ, използвайки по-кратката форма на обектен литерал:
var objName = { property1: value1, property2: value2, method1: function() { // lines of code } };
Достъп до свойствата и методите на обекта
Можете да получите достъп до свойство на обект с точкова нотация и можете да извикате метод, като добавите скоби:
objectname.propertyname; // read a property objectname.methodname(); // call a method objectname["propertyname"]; // bracket notation, useful for dynamic keys
Опитайте сами този пример:
<html>
<head>
<title>Objects!!!</title>
<script type="text/javascript">
var student = new Object();
student.fName = "John";
student.lName = "Smith";
student.id = 5;
student.markE = 76;
student.markM = 99;
student.markS = 87;
student.calculateAverage = function()
{
return (student.markE + student.markM + student.markS)/3;
};
student.displayDetails = function()
{
document.write("Student Id: " + student.id + "<br />");
document.write("Name: " + student.fName + " " + student.lName + "<br />");
var avg = student.calculateAverage();
document.write("Average Marks: " + avg);
};
student.displayDetails();
</script>
</head>
<body>
</body>
</html>
Изход:
Student Id: 5 Name: John Smith Average Marks: 87.33333333333333
💡 Съвет: В горния пример се използва документ.запис защото се изпълнява в редактора на тази страница. В реални проекти предпочитайте конзолен дневник за дебъгване или DOM методи, като например textContent, Защото документ.запис изтрива страницата, когато бъде извикана след завършване на зареждането.
OOPS Конструктор
Създаването на обекти един по един не е толкова полезно, защото ще трябва да повтаряте един и същ код за всеки ученик. Тук се намесва конструкторът на обекти. Конструкторът е обикновена функция, която се извиква с на нови ключова дума; тя определя тип обект, който може да се използва повторно за всеки отделен екземпляр.
Вътре в конструктор, това се отнася до новия обект, който се изгражда, така че всеки екземпляр получава свое собствено копие на предадените стойности. Свързани стойности, като например маркери, често се съхраняват в масив така че да могат да бъдат сумирани с едно преминаване.
Опитайте сами този пример:
<html>
<head>
<script type="text/javascript">
function Student(first, last, id, english, maths, science)
{
this.fName = first;
this.lName = last;
this.id = id;
this.markE = english;
this.markM = maths;
this.markS = science;
this.calculateAverage = function()
{
return (this.markE + this.markM + this.markS)/3;
}
this.displayDetails = function()
{
document.write("Student Id: " + this.id + "<br />");
document.write("Name: " + this.fName + " " + this.lName + "<br />");
var avg = this.calculateAverage();
document.write("Average Marks: " + avg + "<br /><br />");
}
}
var st1 = new Student("John", "Smith", 15, 85, 79, 90);
var st2 = new Student("Hannah", "Turner", 23, 75, 80, 82);
var st3 = new Student("Kevin", "White", 4, 93, 89, 90);
var st4 = new Student("Rose", "Taylor", 11, 55, 63, 45);
st1.displayDetails();
st2.displayDetails();
st3.displayDetails();
st4.displayDetails();
</script>
</head>
<body>
</body>
</html>
Изход:
Student Id: 15 Name: John Smith Average Marks: 84.66666666666667 Student Id: 23 Name: Hannah Turner Average Marks: 79 Student Id: 4 Name: Kevin White Average Marks: 90.66666666666667 Student Id: 11 Name: Rose Taylor Average Marks: 54.333333333333336
Преминаване през свойствата на обект
Синтаксис:
for (variablename in objectname) { // lines of code to be executed }
За/в контур обикновено се използва за преглед на свойствата на обект. Можете да дадете произволно име на променлив, но името на обекта трябва да съвпада с вече съществуващ обект. При всяко преминаване променливата съдържа свойството ключ като низ, така че стойността се чете с нотация в скоби, например служител[x].
Опитайте сами този пример:
<html>
<head>
<script type="text/javascript">
var employee={first:"John", last:"Doe", department:"Accounts"};
var details = "";
document.write("<b>Using for/in loops </b><br />");
for (var x in employee)
{
details = x + ": " + employee[x];
document.write(details + "<br />");
}
</script>
</head>
<body>
</body>
</html>
Изход:
Using for/in loops first: John last: Doe department: Accounts
Струва си да се запомнят две неща. Цикълът for/in посещава и изброими свойства, наследени чрез веригата от прототипи, така че защитен елемент като Object.prototype.hasOwnProperty.call(служител, x) често се добавя. За обикновени обекти с данни, Object.keys(служител) or Object.entries(служител) обикновено е по-ясно, защото и двете връщат само свойствата на обекта.
Какъв е синтаксисът на класа ES6 в JavaСценарий?
ECMAScript 2015, обикновено наричан ES6, добави клас ключова дума. Класът е синтактична захар върху системата от прототипи, която функцията-конструктор по-горе вече използва: не е въведен нов обектен модел. Изпълнение тип студент в клас все още връща низа „function“ и всеки метод, написан в тялото на класа, е инсталиран на Студентски прототип, което означава, че всички екземпляри споделят едно копие на този метод, вместо всеки обект да носи свое собствено.
Синтаксисът също така премахва някои груби черти. Телата на класовете винаги се изпълняват в строг режим, извиквайки клас без на нови хвърля TypeError, вместо тихо да замърсява глобалния обект, а getter-ите, setter-ите и статичните членове имат специално място за живеене. Резултатът е по-кратък код, който изразява същото намерение.
class Student { constructor(first, last, id, english, maths, science) { this.fName = first; this.lName = last; this.id = id; this.marks = [english, maths, science]; } calculateAverage() { const total = this.marks.reduce((sum, m) => sum + m, 0); return total / this.marks.length; } displayDetails() { console.log("Student Id: " + this.id); console.log("Name: " + this.fName + " " + this.lName); console.log("Average Marks: " + this.calculateAverage().toFixed(2)); } } const st1 = new Student("John", "Smith", 15, 85, 79, 90); st1.displayDetails(); console.log(typeof Student); console.log(st1.calculateAverage === Student.prototype.calculateAverage);
Изход:
Student Id: 15 Name: John Smith Average Marks: 84.67 function true
Последните два реда доказват тезата: класът все още е функция, а методът на инстанцията е същият функционален обект, съхранен на прототипа.
Конструкторска функция срещу ES6 клас
| Аспект | Функция-конструктор | ES6 клас |
|---|---|---|
| Декларация | функция Студент(…) { } | клас Студент { конструктор(…) { } } |
| Разположение на метода | Присвоява се вътре във функцията, така че всеки екземпляр получава свое собствено копие, освен ако не пишете в Студентски прототип | Написано в тялото на класа и автоматично поставено върху прототипа |
| Обаждане без ново | Работи безшумно и това е недефиниран или е глобален обект | Веднага хвърля TypeError |
| Подемно | Декларациите на функции се повдигат и могат да се използват преди дефиницията. | Във времевата мъртва зона до оценката |
| Строг режим | Следва заобикалящия сценарий | Винаги строг |
| наследяване | Ръчно: Object.create плюс повикване на родителя | простира намлява супер |
| Лични данни | Затваряния или конвенция за именуване, като например баланс | Истински частни полета с # |
| Базов модел | прототипи | Прототипи (идентични по време на изпълнение) |
Как да имплементираме наследяване, използвайки extends и super
Наследяването позволява на специализиран тип да използва повторно свойствата и методите на по-общ тип. Преди ES6 това означаваше ръчно свързване на веригата от прототипи в три отделни стъпки и беше лесно да се забрави една от тях.
function Person(fName, lName) { this.fName = fName; this.lName = lName; } Person.prototype.introduce = function () { return "I am " + this.fName + " " + this.lName; }; function Student(fName, lName, id) { Person.call(this, fName, lName); // 1. borrow the parent constructor this.id = id; } Student.prototype = Object.create(Person.prototype); // 2. link the chain Student.prototype.constructor = Student; // 3. repair constructor var st = new Student("Kevin", "White", 4); console.log(st.introduce()); console.log(st instanceof Person);
Изход:
I am Kevin White true
- простира ключовата дума изпълнява и трите стъпки вместо вас и супер предоставя чист начин за достигане до родителя. Има две различни употреби на супер:
- супер(…) като обаждане изпълнява родителския конструктор. Той е валиден само в рамките на производен конструктор и трябва да се изпълни, преди да го докоснете това, в противен случай се хвърля ReferenceError.
- супер.метод() тъй като търсенето на свойство извиква родителската версия на метод, който детето е презаписало, което е начинът, по който разширявате поведението, вместо да го замествате.
class Person { constructor(fName, lName) { this.fName = fName; this.lName = lName; } introduce() { return "I am " + this.fName + " " + this.lName; } } class Student extends Person { constructor(fName, lName, id) { super(fName, lName); // must run before "this" is used this.id = id; } introduce() { return super.introduce() + ", roll number " + this.id; } } const st = new Student("Hannah", "Turner", 23); console.log(st.introduce()); console.log(st instanceof Student, st instanceof Person); console.log(Object.getPrototypeOf(Student.prototype) === Person.prototype);
Изход:
I am Hannah Turner, roll number 23 true true true
Третият ред е важен. Той потвърждава, че простира произведе точно прототипната връзка, ръчната версия, изградена на ръка, така че класовете не замениха верижното свързване на прототипи. Те просто скриха шаблона. Когато дадено свойство се чете от st, двигателят разглежда екземпляра, след което Студентски прототип, тогава Лице.прототип, тогава Обект.прототипи спира на първото съвпадение; този път на търсене е веригата от прототипи.
⚠️ Предупреждение: Никога не възлагайте Студент.прототип = Лице.прототипТова кара и двата типа да споделят един обект, така че всеки метод, който добавите към детето, се добавя и към родителя. Използвайте Object.create(Person.prototype)или просто използвайте простира.
Как да постигнем капсулиране с полета на частен клас
Капсулирането означава запазванеping вътрешно състояние извън обсега, така че то може да се промени само чрез методи, които контролирате. В продължение на години JavaРазработчиците на скриптове са го фалшифицирали с водеща долна черта, но име като баланс е само конвенция и всеки код все още може да я презапише. Модерно JavaСкриптът предлага истинска поверителност: поле, чието име започва с # е достъпен само в тялото на класа, който го декларира.
Частните полета носят три конкретни предимства:
- Те трябва да бъдат декларирани в тялото на класа преди употреба, което документира вътрешното състояние на типа на едно място.
- Те са невидими за Object.keys, JSON.stringify, и for/in, така че вътрешното състояние да не се пренася в сериализирания изход.
- Докосването на частно поле извън класа е синтактична грешка, засечена преди кодът да се изпълни, а не тиха грешка.
class BankAccount { #balance = 0; constructor(owner, opening) { this.owner = owner; this.#balance = opening; } deposit(amount) { if (amount <= 0) { return "Deposit must be positive"; } this.#balance += amount; return this.owner + " balance: " + this.#balance; } get balance() { return this.#balance; } } const acc = new BankAccount("John", 500); console.log(acc.deposit(250)); console.log(acc.deposit(-40)); console.log("Read via getter: " + acc.balance); console.log("Own keys: " + Object.keys(acc)); console.log("JSON: " + JSON.stringify(acc));
Изход:
John balance: 750
Deposit must be positive
Read via getter: 750
Own keys: owner
JSON: {"owner":"John"}
Балансът никога не се появява в Object.keys или в JSON низа, и отрицателният депозит се отхвърля от метода, вместо да повреди полето. получаване на баланс() accessor предоставя изглед само за четене, което е обичайният начин за безопасно публикуване на частно състояние.
Сега, когато обектите, прототипите, класовете, наследяването и капсулирането имат смисъл, продължете да изграждате. Упражнявайте основите с тези... практически JavaПримери за скриптов код, преработи интерактивен JavaУпражнения със скриптове, прилагайте класове към алгоритми като бързо сортиране в JavaСценарий, подредете изхода, използвайки JavaФорматиране на низове в скриптаи вижте как статично еping променя дизайна на обекта в TypeScript.
