Я играю роль учителя JavaScript и концепции прототипа всегда была спорной темой для покрытия, когда я преподаю. Мне потребовалось некоторое время, чтобы придумать хороший метод для разъяснения концепции, и теперь в этом тексте я попытаюсь объяснить, как работает JavaScript .prototype.
Это очень простая объектная модель на основе прототипа, которая будет рассматриваться в качестве примера при объяснении без комментариев:
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
.
с. У JavaScript есть еще один способ предоставить функции this
, который использует call или apply для вызова функции.
Метод 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 на самом деле присоединяет свойства, определенные в прототипе. Фактический путь связан с концепцией цепи прототипов.
а. & б. Вместо этих двух шагов вы можете получить точно такой же результат, выполнив:
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();
с. затем он передает этот объект конструктору,
мы можем сделать это с нашим примером, как:
Person.call(newObject, "George");
или
Person.apply(newObject, ["George"]);
тогда конструктор может делать все, что захочет, потому что this внутри этого конструктора является только что созданным объектом.
теперь конечный результат перед имитацией других шагов:
Объект {имя: "Георгий"}
Резюме:
Обычно, когда вы используете ключевое слово new для функции, вы вызываете ее, и эта функция служит конструктором, поэтому, когда вы говорите:
new FunctionName()
JavaScript внутренне создает объект, пустой хеш, а затем передает этот объект конструктору, тогда конструктор может делать все, что захочет, потому что this внутри этого конструктора - это только что созданный объект и затем он дает вам этот объект, конечно, если вы не использовали оператор return в своей функции или если вы поставили return undefined;
в конце тела вашей функции.
Поэтому, когда JavaScript отправляется на поиск свойства объекта, первое, что он делает, - это поиск этого объекта. И затем есть секретное свойство [[prototype]]
, которое у нас обычно есть как __proto__
, и это свойство - то, на что JavaScript смотрит дальше. И когда он просматривает __proto__
, поскольку он снова является другим объектом JavaScript, он имеет свой собственный атрибут __proto__
, он поднимается вверх и вверх, пока не доберется до точка, в которой следующий __proto__
равен нулю. Точка - единственный объект в JavaScript, у которого атрибут __proto__
равен нулю: Object.prototype
object:
console.log(Object.prototype.__proto__===null);//true
и вот как наследование работает в JavaScript.
Другими словами, когда у вас есть свойство прототипа для функции, и вы вызываете новое для него, после того, как JavaScript завершит поиск этого вновь созданного объекта для свойств, оно пойдет смотреть на .prototype
функции, а также это Возможно, что этот объект имеет свой внутренний прототип. и т. д.