Це дуже проста об'єктова модель, заснована на прототипі, яка буде розглядатися як зразок під час пояснення, без коментарів:
function Person(name){
this.name = name;
}
Person.prototype.getName = function(){
console.log(this.name);
}
var person = new Person("George");
Є кілька важливих моментів, які ми повинні врахувати, перш ніж перейти до концепції прототипу.
1- Як насправді працюють функції JavaScript:
Щоб зробити перший крок, ми повинні розібратися, як насправді працюють функції JavaScript, як клас, як функція, що використовує this
ключове слово в ньому, або просто як звичайна функція з її аргументами, що вона робить і що повертає.
Скажімо, ми хочемо створити Person
об'єктну модель. але на цьому кроці я намагаюся зробити те саме, що не використовую prototype
і не використовую new
ключове слово .
Таким чином , в цьому кроці functions
, objects
і this
ключове слово, все у нас є.
Першим питанням буде те, як this
ключове слово може бути корисним без використання new
ключового слова .
Отже, щоб відповісти, що скажімо, у нас порожній об’єкт і дві функції, такі як:
var person = {};
function Person(name){ this.name = name; }
function getName(){
console.log(this.name);
}
і тепер без використання new
ключового слова, як ми могли використовувати ці функції. Отже, у JavaScript є 3 різних способи зробити це:
а. Перший спосіб - просто викликати функцію як звичайну функцію:
Person("George");
getName();//would print the "George" in the console
в цьому випадку це буде поточний контекстний об'єкт, який, як правило, є глобальним window
об'єктом у браузері або GLOBAL
в Node.js
. Це означає, що ми мали б, window.name у веб-переглядачі або GLOBAL.name у Node.js, із значенням "George".
б. Ми можемо прикріпити їх до об'єкта, як його властивостей
- Найпростіший спосіб зробити це - змінити порожній person
об’єкт, наприклад:
person.Person = Person;
person.getName = getName;
таким чином ми можемо їх назвати так:
person.Person("George");
person.getName();// -->"George"
і тепер person
об’єкт виглядає так:
Object {Person: function, getName: function, name: "George"}
- Інший спосіб приєднати властивість до об'єкта - це використання prototype
цього об’єкта, який можна знайти в будь-якому об’єкті JavaScript з ім'ям __proto__
, і я спробував це трохи пояснити у підсумковій частині. Таким чином, ми могли отримати аналогічний результат, зробивши:
person.__proto__.Person = Person;
person.__proto__.getName = getName;
Але таким чином те, що ми насправді робимо, - це модифікація Object.prototype
, тому що щоразу, коли ми створюємо об’єкт JavaScript за допомогою літералів ( { ... }
), він створюється на основі Object.prototype
, а це означає, що він приєднується до новоствореного об'єкта як атрибут, названий __proto__
, так що якщо ми його змінимо , як ми це робили в попередньому фрагменті коду, всі об’єкти JavaScript будуть змінені, що не є хорошою практикою. Отже, яка зараз може бути найкраща практика:
person.__proto__ = {
Person: Person,
getName: getName
};
а тепер інші спокійні об'єкти, але це все ще не здається гарною практикою. Отже, у нас є ще одне рішення, але для використання цього рішення ми повинні повернутися до того рядка коду, де person
створений об’єкт ( var person = {};
), а потім змінити його так:
var propertiesObject = {
Person: Person,
getName: getName
};
var person = Object.create(propertiesObject);
що він робить це створення нового JavaScript Object
і прикласти propertiesObject
до __proto__
атрибуту. Отже, щоб переконатися, що ви можете зробити:
console.log(person.__proto__===propertiesObject); //true
Але справа в тому, що ви маєте доступ до всіх властивостей, визначених __proto__
на першому рівні person
об'єкта (детальніше читайте підсумкову частину).
як ви бачите, використання будь-якого з цих двох способів this
точно вказувало б на person
об'єкт.
c. У JavaScript є ще один спосіб надати функцію this
, яка використовує виклик або застосувати для виклику функції.
Метод apply () викликає функцію із заданим цим значенням та аргументами, наданими у вигляді масиву (або об’єкта, подібного до масиву).
і
Метод call () викликає функцію із заданим цим значенням та аргументами, що подаються окремо.
таким чином, який є моїм улюбленим, ми можемо легко назвати наші функції, наприклад:
Person.call(person, "George");
або
//apply is more useful when params count is not fixed
Person.apply(person, ["George"]);
getName.call(person);
getName.apply(person);
ці 3 методи - важливі початкові кроки для з'ясування функціональності .prototype.
2- Як працює new
ключове слово?
це другий крок для розуміння .prototype
функціональності. Це те, що я використовую для імітації процесу:
function Person(name){ this.name = name; }
my_person_prototype = { getName: function(){ console.log(this.name); } };
в цій частині я намагаюся зробити всі кроки, які виконує JavaScript, не використовуючи new
ключове слово, і prototype
, коли ви використовуєте new
ключове слово. тож коли ми робимо new Person("George")
, Person
функція виконує функції конструктора. Ось що робить JavaScript один за одним:
а. Перш за все, це робить порожній об'єкт, в основному порожній хеш, як:
var newObject = {};
б. Наступним кроком, який робить JavaScript, є приєднання всіх об'єктів-прототипів до новоствореного об’єкта
ми маємо my_person_prototype
тут схожий на об’єкт-прототип.
for(var key in my_person_prototype){
newObject[key] = my_person_prototype[key];
}
Це не спосіб, яким JavaScript надає властивості, визначені в прототипі. Фактичний спосіб пов'язаний з концепцією прототипу ланцюга.
а. & b. Замість цих двох кроків ви можете отримати точно такий же результат, виконавши:
var newObject = Object.create(my_person_prototype);
//here you can check out the __proto__ attribute
console.log(newObject.__proto__ === my_person_prototype); //true
//and also check if you have access to your desired properties
console.log(typeof newObject.getName);//"function"
тепер ми можемо викликати getName
функцію в нашому my_person_prototype
:
newObject.getName();
c. тоді він дає цей об'єкт конструктору,
ми можемо зробити це за допомогою нашого зразка, наприклад:
Person.call(newObject, "George");
або
Person.apply(newObject, ["George"]);
тоді конструктор може робити все, що завгодно, тому що саме всередині цього конструктора є саме створений об’єкт.
тепер кінцевий результат перед моделюванням інших кроків: Об'єкт {name: "George"}
Підсумок:
В основному, коли ви використовуєте нове ключове слово у функції, ви закликаєте це, і ця функція виконує функції конструктора, тому коли ви говорите:
new FunctionName()
JavaScript внутрішньо робить об’єкт, порожній хеш, а потім дає цей об’єкт конструктору, тоді конструктор може робити все, що завгодно, тому що це всередині цього конструктора - це той об’єкт, який щойно створений, і він дає вам цей об'єкт, звичайно якщо ви не використовували оператор return у своїй функції або якщо ви поставили в return undefined;
кінці тіла функції.
Отже, коли JavaScript переходить до пошуку властивості на об'єкт, перше, що він робить, це шукає його на цьому об'єкті. А потім є секретна властивість, [[prototype]]
яку ми зазвичай маємо, __proto__
і ця властивість - це те, що JavaScript дивиться далі. І коли він переглядає __proto__
, наскільки це знову інший JavaScript-об’єкт, у нього є свій __proto__
атрибут, він піднімається вгору і вгору, поки не дістанеться до точки, коли наступна __proto__
є нульовою. Точка є єдиним об’єктом у JavaScript, __proto__
атрибут якого є null, є Object.prototype
об'єктом:
console.log(Object.prototype.__proto__===null);//true
і саме так працює спадкування в JavaScript.
Іншими словами, коли у вас є властивість прототипу функції і ви викликаєте нове, після того, як JavaScript закінчить перегляд цього новоствореного об'єкта для властивостей, він перегляне функцію, .prototype
а також можливо, що у цього об'єкта є власний внутрішній прототип. і так далі.