Прототип и конструктор в JavaScript (простой английский)?

"JavaScript - это самый непонятый язык в мире" -D.Crockford

Мои вопросы:

  • Конструктор и прототипы на простом английском языке
  • В чем заключается необходимость использования прототипа? Какова цель использования Прототипы и конструкторы? Я имею в виду, что они предоставляют больше гибкость. Я прошу об этом, поскольку я использую этот язык для за последние шесть месяцев и никогда не было ситуации, когда я использовал прототипы и конструктор.

Я не ищу ни синтаксиса, ни того, как разбираться в каких-то объяснениях, поскольку я понимаю какую-то часть того, что они есть, просто хотел знать эти вещи более простым способом. Аналогия (нетехническая) или пример будет большой. *

Подробности, почему я задал этот вопрос (игнорируйте, если хотите):

Я использую JavaScript в течение последних шести месяцев и был шокирован, когда узнал, что JavaScript - это язык, основанный на прототипах.

Я просмотрел некоторые вопросы о переполнении, связанные с тем, как использовать JavaScript и сталкивался с прототипами и конструкторами.

Я узнал об этом, и теперь могу сказать, что я не нуб, когда речь идет о конструкторах и прототипах. Я знаком с синтаксисом. Но все-таки я думаю, что у меня что-то не хватает, и я не понял сути этого языка, и иногда меня путают.

Надеюсь, я поняла.

Ответы

Ответ 1

Конструктор и протоипы на простом английском языке?

Конструкторные функции создают объекты и назначают им прототипы. Прототип - это объект с различными свойствами, которые объект может наследовать через цепочку прототипов. Как всегда, примеры помогают:

function Foo() {
}
Foo.prototype.answer = 42;

var f = new Foo();
console.log(f.answer); // "42"

Foo является конструкторской функцией. Когда вы используете new Foo, объект, который указывает Foo.prototype, станет прототипом создаваемого объекта. Когда вы выполняете f.answer, так как f не имеет собственного свойства с именем answer, механизм JavaScript смотрит на прототип f, чтобы узнать, есть ли у него его. Так как это делает, он использует значение из прототипа, и мы видим "42" в консоли. Вот как решаются свойства: глядя на объект, видя, есть ли у него свойство с заданным именем, а если нет, переходите к его прототипу, чтобы узнать, обладает ли оно свойством, и если он не собирается его прототипом и т.д..

Обратите внимание, что следствием вышеизложенного является то, что добавление свойств прототипу после создания объекта с использованием этого прототипа работает отлично; вы можете использовать эти новые свойства через объект:

function Foo() {
}
Foo.prototype.answer = 42;

var f = new Foo();
console.log(f.question); // "undefined", neither `f`, nor `Foo.prototype`, nor
                         // `Object.prototype` has a `question` property

Foo.prototype.question = "Life, the Universe, and Everything";
console.log(f.question); // "Life, the Universe, and Everything"

Как и в ES5, функции-конструкторы уже не являются единственным способом назначения прототипов объектам. Теперь вы также можете сделать это через Object.create. Вышеуказанное значение примерно эквивалентно этому:

var fooProto = {
    answer: 42
};
var f = Object.create(fooProto);
console.log(f.answer); // "42"

Какова цель использования прототипов и конструкторов?

Обмениваться характеристиками между объектами. Свойства прототипа могут быть функциями или данными, оба из которых объекты, использующие этот прототип, имеют доступ и могут повторно использоваться.

Повторите свой комментарий ниже:

Я понял часть об общих характеристиках, но могу ли я подробнее описать ее

Ну, рассмотрим конструктор Circle:

function Circle(radius) {
    this.r = radius;
}
Circle.prototype.radius = function() {
    return this.r;
};
Circle.prototype.diameter = function() {
    return this.r * 2;
};
Circle.prototype.circumference = function() {
    return 2 * Math.PI * this.r;
};
Circle.prototype.area = function() {
    return Math.PI * this.r * this.r;
};

Все объекты, созданные Circle, получат Circle.prototype в качестве своего прототипа, и поэтому у всех их есть удобные diameter, circumference, et. и др. функции.

var c1 = new Circle(3);
console.log(c1.area());          // 28.274333882308138
console.log(c1.circumference()); // 18.84955592153876

var c2 = new Circle(5);
console.log(c2.area());          // 78.53981633974483
console.log(c2.circumference()); // 31.41592653589793

Они делятся этими свойствами эффективным образом: каждый экземпляр не имеет собственной копии этих свойств (что означает сохранение каждого имени свойства и его значения в каждом объекте); вместо этого они просто имеют ссылку на свой прототип, который они разделяют, и которые имеют эти свойства.

Ответ 2

Прежде всего, я предлагаю вам взглянуть на этот плейлист с участием самого человека (Crockford). Возможно, он старый, но он действительно объясняет "логику" JavaScript очень хорошо, и на ваш вопрос особенно ответили в третьем видео.

Я собираюсь начать отвечать на этот вопрос, описав, как объекты изображены на других традиционных объектно-ориентированных языках программирования, потому что я хочу также направить комментарий Crockford, который вы опубликовали в начале вопроса.

Чтобы понять Конструкторы, сначала нужно иметь хорошее представление об объектах. В традиционных языках ООП объект представляет собой набор переменных (называемых свойствами или полями), которые описывают состояние объекта, а также функции (называемые методами), которые описывают его поведение. На этих (не-JavaScript) языках "план" этих объектов называется классом.

Итак, если я создаю класс Human в Java, очень упрощенное изображение будет выглядеть так:

class Human {
    String name;
    int weight; // kg
    int height; // cm

    void eat(int foodWeight) {
        this.weight += foodWeight;
    }

    Human(int weight, int height, int name) {
        this.weight = weight; 
        this.height = height;
        this.name   = name;
    }
}

И затем я создам Object, используя приведенный выше "чертеж":

Human Joe = new Human(90, 180, "Joe");

И теперь, мы говорим, что Joe - это пример Human, вес которого 90 кг, а высота - 180 см.

В вышеприведенном классе вы заметили, что у меня есть функция Human(), которая была использована для создания объекта и определения его состояния по мере его создания. Это то, что делает конструктор.

Так что же отличается от JavaScript?

Чтобы обратиться к массам во время создания (как вы услышите в видеоролике, опубликованной мной), JavaScript включил некоторый синтаксис, подобный Java. То, что это сделало, по словам Крокфорда, дает программистам идею, что, поскольку они уже знают/изучают некоторую Java, тогда они могут просто изучить несколько новых команд, а затем продолжить и программировать на JavaScript, в то время как на самом деле различия между два намного перевешивают их сходства.

В JavaScript, чтобы создать объект таким образом, чтобы он выглядел как класс Java, вы должны использовать синтаксис функции следующим образом:

var Human = function(name, height, weight) {
    this.name   = name;
    this.height = height;
    this.weight = weight;

    this.eat    = function(foodWeight) {
        this.weight += foodWeight;
    };
};

И затем, если вы хотите определить Joe, как мы это делали выше, вы должны сделать следующее:

var Joe = new Human("Joe", 180, 90);

Вы можете увидеть сходство между синтаксисами Java и JavaScript. Итак, чтобы ответить на ваш первый вопрос: JavaScript Конструкторы - это функции, которые при вызове new создают и возвращают неявно созданный объект, на который указывает this.

Итак, где же появился прототип? Ну, в JavaScript функции также являются самими объектами JS, и они имеют свойство, называемое prototype. Таким образом, созданный выше конструктор Human() имеет свойство, называемое prototype, и это свойство относится к объекту, свойства и методы которого наследуются Joe, а также все другие экземпляры Human, и этот объект может быть расширен для создания свойств, которые будут унаследованы всеми этими экземплярами.

Например, одним из методов в Function.prototype является известный метод toString. Вы можете определить

Human.prototype.toString = function() {
    return this.name + " is " + this.height + " cm tall and weighs " + this.weight + " kg";
}

тогда, если вы вызываете Joe.toString() или когда вы делаете что-то вроде alert(Joe), которое автоматически вызывает toString(), возвращаемое значение будет "Джо 190-метровый и весит 80 кг".

Есть много других подробностей о ООП и JavaScript, которые могут быть рассмотрены в контексте вашего вопроса, но я думаю, что мой ответ достаточно длинный! Надеюсь, это ответит на ваш вопрос.

Ответ 3

Конструктор и прототипы на простом английском языке?

Как следует из названия "конструктор", он создает что-то новое (объект) и все, что он создает, следует за шаблоном, прототипом.

В JavaScript любая функция может использоваться как конструктор, просто вызывая их иначе, чем обычный вызов функции; например:

function Foo()
{
}

Foo(); // normal function call, returns nothing
var f = new Foo(); // constructor call, returns a new Foo object

alert(f instanceof Foo) // "true"

Как упоминалось ранее, прототип подобен шаблону; вы можете изменить прототип во время выполнения, а изменения повлиять на все объекты, которые наследуются от этого прототипа. К прототипу любого объекта можно получить доступ через свой конструктор .prototype. Например:

var f = new Foo();
Foo.prototype.bar = 'baz';

alert(f.bar) // "baz"

В чем заключается необходимость использования Prototype? Я хочу понять цель использования прототипов и конструкторов? Я имею в виду, что они обеспечивают большую гибкость.

Прототипы используются для определения общего поведения и/или данных с использованием методов и свойств, аналогично тому, что вы можете ожидать от класса, ориентированного на язык. Они также могут наследовать друг от друга, создавая цепочку прототипов вплоть до Object; даже функции на самом деле являются объектами Function.

Без прототипа вам придется выполнять всю работу внутри вашего конструктора:

function Foo()
{
    // add methods and data
    this.bar = 'baz';
}

В приведенном выше примере вы можете не видеть прямое преимущество, но есть некоторые из них:

  • Память; добавление методов к каждому объекту экземпляра потребляет больше памяти, чем предоставление их через цепочку прототипов. Преимущество отсутствия необходимости проходить цепочку прототипов обычно выравнивается временем, затраченным на создание объектов.

  • Иерархия; когда ваш проект станет больше, вам в конечном итоге понадобится создать какую-то иерархию объектов, без прототипов это более громоздко.

Однако, если вы хотите создать привилегированный метод, вам нужно прикрепить его в самом конструкторе; это невозможно сделать из прототипа; например:

function Foo()
{
    var bar = 'baz';

    // privileged method
    this.bar = function() {
        return bar;
    }
}
var f = new Foo();
alert(f.bar()); // "baz"

Я спрашиваю об этом, поскольку я использовал этот язык в течение последних 6 месяцев и никогда не имел ситуации, когда использовал прототипы и конструктор.

Если вы использовали new Option(...) или new XYZ() в любом месте, вы использовали конструктор; если вы использовали .hasOwnProperty() или .toString() в любой точке, вы бы использовали цепочку прототипов:)

Ответ 4

Другие ответы уже отвечают на ваш вопрос довольно хорошо, но я хочу добавить еще один аспект prototype в микс: Inheritance

Как уже показывают другие ответы, любые свойства или методы, связанные с myObject.prototype, совместно используются между экземплярами:

var Car = function(color) {
    this.color = color;
}; 
Car.prototype.openDoor = function() {
    alert("Door is open!");
}

Теперь вы можете вызвать метод honk для каждого экземпляра:

var car1 = new Car('red');
var car2 = new Car('blue');
car1.openDoor();
car2.openDoor();

Мы могли бы включить openDoor внутри функции Car, т.е.

var Car = function(color) {
    this.color = color;
    this.openDoor = function() { alert("Door is open!"); }
}; 

Однако это добавит метод openDoor для каждого экземпляра Car, что очень расточительно, особенно если оно делает то же самое для всех экземпляров. Добавив его к прототипу, мы разделим его со всеми экземплярами.

Пока все хорошо, но сила prototype действительно показывает, когда вы назначаете другой объект прототипу:

var Vehicle = function(color) {
    this.color = color;
}; 
Vehicle.prototype.honk = function() {
    alert("Honk Honk! I am " + this.color);
}

var Car = function(color, maxPassengers){ 
    this.color = color;
    this.maxPassengers = maxPassengers;
} 
Car.prototype = new Vehicle();  
Car.prototype.constructor = Car;
Car.prototype.openDoor = function(){ 
    alert("Door is open! I have space for " + this.maxPassengers);
}

Поскольку мы назначаем Car.prototype конструктору Vehicle, мы, по существу, приковали Car к Vehicle и поэтому унаследовали все его свойства и методы. По сути, мы inherit все функции Vehicle.

Ответ 5

То, что вы, видимо, использовали до сих пор

Как вы уже не использовали конструкторы (и прототипы), это означает, что вы более или менее писали процедурный код JavaScript, который выглядит как серия серийно исполняемого кода от начала и до конца. Если вы хотите повторно использовать некоторые строки кода, вы поместили их внутри функции и назовите ее, когда это необходимо.

Это прекрасно, если на вашей странице не слишком много кода и не требуется повторного использования модуля, а именно объектов. Поскольку чем больше база кода, тем сложнее ее поддерживать. Модульность помогает, потому что она следует принципу делить и властвовать.

Конструкторы и прототипы

Здесь появляются конструкторы и прототипы. Каждая функция в JavaScript может быть конструктором, если вы выполняете ее правильно, используя ключевое слово new. В основном, используя конструкторы и прототипы, вы можете реализовать свой код объектно-ориентированным способом, когда вы определяете подходящие типы объектов [proto] и используете основы ООП, например наследование, инкапсуляция и полиморфизм.

Что в нем для меня?

Основным преимуществом ООП над процедурным программированием является краткосрочная и долгосрочная ремонтопригодность.

ОК, так что давайте сделаем объект и посмотрим, как работает прототип

Сделайте объект Rectangle:

var Rectangle = function(width, height) {
    this.width = width;
    this.height = height;
};

var instance = new Rectangle(4, 8);
console.log(instance.width); // 4
console.log(instance.height); // 8

Это создает прямоугольник указанного размера. Пусть также добавляет конкретный метод этому классу flip, который переворачивает прямоугольник. Мы можем сделать это двумя разными способами:

  • Определите его как метод экземпляра внутри конструктора:

    var Rectangle = function(width, height) {
        this.width = width;
        this.height = height;
        this.flip = function() {
             var temp = this.width;
             this.width = this.height;
             this.height = temp;
        };
    };
    
  • Определите его по типу прямоугольника или лучше указанному прототипу

    var Rectangle = function(width, height) {
        this.width = width;
        this.height = height;
    };
    
    Rectangle.prototype.flip = function() {
         var temp = this.width;
         this.width = this.height;
         this.height = temp;
    };
    

Однако мы определяем использование метода flip одним и тем же:

var instance = new Rectangle(4, 8);
instance.flip();
console.log(instance.width); // 8
console.log(instance.height); // 4

Но все еще есть разница. В случае №1, когда мы создаем метод экземпляра, каждый создаваемый объект будет иметь отдельный экземпляр этого метода, но если мы будем использовать # 2, все экземпляры объекта будут разделять то же самое метод.

Использование методов уровня прототипа будет поэтому экономить ресурсы памяти, и любые последующие изменения времени выполнения этого метода будут отражаться на всех экземплярах (уже созданных и будущих).

Но там больше

Никто не сказал, что мы не можем одновременно создать один и тот же метод в обоих случаях: как экземпляр и прототип.

var Rectangle = function(width, height) {
    this.width = width;
    this.height = height;
    this.flip = function() {
        var temp = this.width;
        this.width = this.height * 2;
        this.width = temp / 2;
    };
};

Rectangle.prototype.flip = function() {
    var temp = this.width;
    this.width = this.height;
    this.width = temp;
};

В этом случае наш метод экземпляра переворачивает и растягивает наш прямоугольник, сохраняя при этом его область одинаковой. Метод прототипа просто переворачивает его.

var instance = new Rectangle(4, 8);
console.log(instance.width); // 4
console.log(instance.height); // 8

instance.flip();
console.log(instance.width); // 16 = 8 * 2
console.log(instance.height); // 2 = 4 / 2

delete instance.flip;
instance.flip();
console.log(instance.width); // 2
console.log(instance.height); // 16

В этом примере мы создали два метода flip. Методы экземпляров имеют приоритет над прототипами, поэтому это дает возможность переопределить/переписать функциональность прототипа по умолчанию для конкретного экземпляра объекта.

После вызова метода экземпляра мы удалили его и напомнили flip. Поскольку метод экземпляра больше не существовал, прототип был выполнен, поэтому прямоугольник только перевернулся без изменений размеров.

Почему и где я буду использовать это в реальной жизни?

В любом случае, потому что всякий раз, когда ваша страница имеет, например, 200 строк кода, скорее всего, станет более сложной для продолжения и поддержки. Изменение его на ООП поможет. Но когда вы начнете использовать его, вы будете использовать его в любом случае, потому что вам не придется реорганизовывать что-либо, когда код страницы растет, а также будет соответствовать остальной части вашего приложения.

Пример реальной жизни

Вы можете представить, что Qaru определил класс Question, который имеет все свойства вопроса (id, title, details, array of tags, stats, comments и т.д.) и все методы, связанные с вопросом (upvote, downvote, редактировать, удалять, комментировать, отвечать и т.д.).

Верхняя страница Qaru просто запросит массив JSON объектов и перечислит их с использованием некоторого HTML-шаблона, который использует эти свойства. Все, что пользователь делает для вопроса, затем будет отражать вызов одного из его методов.

Таким образом, все прекрасно содержит и имеет только столько функциональности, сколько требуется, без каких-либо других помех, связанных с другими частями страницы (объявления, навигация, панель инструментов входа и т.д.). Это означает, что всякий раз, когда возникает ошибка в вопросительной функции, разработчикам приходится проходить через код, связанный с прототипом Question. Они не отвлекаются на какой-либо другой код, связанный с страницей.

Ответ 6

Прототип - это то, где вы обычно определяете функции или значения по умолчанию. Если я определяю объект person и метод getName Person, то могу с уверенностью сказать, что getName делает то же самое для экземпляров Jon, Mike и Betty (он вернет this.name). Поскольку функция getName делает то же самое для каждого экземпляра Person, вы не хотите, чтобы она была определена в теле конструктора Person:

function Person(name){
  this.name = name; // This refers to the current instance
  this.getName = function(){
    return this.name;
  }
}
var Paul = new Person("Paul");// Paul has its own getName function
var Ben = new Person("Ben");// Ben has its own getName function
...

В приведенном выше коде Person называется конструктором, вы можете создавать новые экземпляры Person, вызывая constrictor: var someone=new Person. Теперь someone является экземпляром человека. Вы видите в приведенном выше коде, что каждый экземпляр имеет собственное getName, если у объекта много функций, и вы создаете много экземпляров, вы будете тратить время процессора, инициируя функции каждый раз, когда вы создаете экземпляр и память (поскольку каждый экземпляр имеет кучу функций, которые делают то же самое, что и все другие экземпляры).

Для вышеуказанных созданных объектов, Пол и Бен, утверждение Paul.hasOwnProperty('getName') будет истинным.

Если вы поместите getName в Person.prototype, то фактически будет только одна функция getName для всех экземпляров Person. Новый экземпляр Person будет иметь getName через Person.prototype, но getName не инициализируется каждый раз, когда я создаю Person. Когда я создаю сотни экземпляров Person, а затем изменяю Person.prototype.getName, все эти созданные экземпляры будут использовать измененную функцию getName.

Тогда есть наследование, о котором вы хотите подумать (у JavaScript нет классов). Вы можете взять все эти общие методы Person и скопировать их в прототип (например) Employee. Поскольку getName является funciton на Person.prototype, а Emloyee наследует его, вы можете напрямую позвонить employeeInstance.getName(). Если Employee нуждается в дополнительной работе в getName, вы можете переопределить функцию Person, но все равно вызывать ее (см. Код ниже)

Employee.prototype.getName=function(){
  return Person.getName.call(this) + " " + this.jobTitle;
}

Для получения дополнительной информации о функциях конструктора, наследовании и переопределении функций ознакомьтесь с этим ответом.

Если вы не понимаете эти слова, я предлагаю прочитать учебник по Java. Это объясняет, зачем это делать. Хотя Java технически использует классы, он объяснит, что такое наследование и переопределение, и почему его использовать.

ООП довольно сложно объяснить в одном посте, но в приведенном выше учебном пособии будут рассмотрены некоторые из них. Java - это не JavaScript, и такие вещи, как частные члены, проверка типов и интерфейсы не поддерживаются в JavaScript. С другой стороны, JavaScript намного более гибкий, если вы хотите изменить экземпляры объекта.

Реальная мощь ООП проявится при проверке шаблонов. Вы можете использовать Google, поскольку в Интернете есть бесчисленные статьи.

Ответ 7

Хм, хорошо, что-то простое, чтобы вы начали, а не слишком много технических материалов.

Рассмотрим это:

function Person(){
    this.name = '';
    this.lastname = '';
    this.age = '';

    this.speak = function(msg){
        alert(msg);
    }
}

Как вы уже знаете, это простой объект со своими уникальными свойствами и методами/функциями Вы согласитесь, что у каждого человека есть уникальное имя, фамилия и возраст.

Все хорошо до сих пор... Но 99.999% (Предположим, 100%) люди могут говорить... так что у них есть общая способность или назовите его методом или функцией.

Другими словами, способность "Говорить" не является чем-то уникальным, а не чем-то распространенным среди людей. Так что ради потребления памяти и других технических средств вы можете реализовать "говорить" следующим образом:

Person.prototype.speak = function(msg){
    alert(msg);
}

Теперь мы сделали то, что всякий раз, когда вы создаете объект человека (var someone = new Person();) он/она будет иметь 3 уникальных свойства и 1 "общую" способность (метод-функция).

В краткосрочной перспективе это более эффективно.

Также рассмотрим следующее:

function Person(){
    this.name = '';
    this.lastname = '';
    this.age = '';
    this.category = 'human';
}

VS

function Person(){
    this.name = '';
    this.lastname = '';
    this.age = '';
}

Person.prototype.category = 'human'; // common among all people same as speak was.

И что-то попробовать на консоли, после вставки этой последней функции Person и ее прототипа объявления, сделайте это.

var a = new Person();
var b = new Person();

то

введите a и/или b и нажмите enter. затем попробуйте эти 2 "команды" и перепроверьте свои объекты.

a.category = 'whatever';
Person.prototype.category = 'whatever';

Ответ 8

Класс предоставляет шаблон (например, трафарет) для создания объектов. На большинстве языков трафарет выполнен из бриллианта, поэтому вы не можете его изменить.

На языке, основанном на прототипе, это похоже на то, что вы трассируете контур существующего объекта для создания нового объекта. Если вы затем решите: "Мне нужен больший рот на этом объекте снеговика", вы делаете рот большим на объекте, который используете в качестве прототипа, и любые объекты, созданные из этого модифицированного объекта снеговика, будут иметь больший рот. Если вы затем используете один из старых объектов снеговиков в качестве своего прототипа, создаваемые из него объекты снеговиков будут иметь оригинальный, меньший рот.

Конструктор - это код для создания нового объекта, заданного классом или прототипом объекта (в зависимости от языка).