8

Использование 'prototype' и 'this' в JavaScript?

1

Вопрос: Какова разница между двумя способами определения функции в JavaScript?

Я создал два различных варианта определения функции x в конструкторе A. Вот их код:

Первый вариант:

var A = function () {
    this.x = function () {
        // здесь выполняются какие-то действия
    };
};

Во втором варианте:

var A = function () { };
A.prototype.x = function () {
    // здесь выполняются какие-то действия
};

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

5 ответ(ов)

2

Как уже отмечали другие, первая версия, использующая "this", приводит к тому, что у каждого экземпляра класса A есть свой собственный независимый метод "x". В то время как использование "prototype" означает, что каждый экземпляр класса A будет использовать одну и ту же копию метода "x".

Вот код, который демонстрирует это тонкое различие:

// x — это метод, назначенный объекту с помощью "this"
var A = function () {
    this.x = function () { alert('A'); };
};
A.prototype.updateX = function( value ) {
    this.x = function() { alert( value ); }
};

var a1 = new A();
var a2 = new A();
a1.x();  // Выводит 'A'
a2.x();  // Также выводит 'A'
a1.updateX('Z');
a1.x();  // Выводит 'Z'
a2.x();  // По-прежнему выводит 'A'

// Здесь x — это метод, назначенный объекту с помощью "prototype"
var B = function () { };
B.prototype.x = function () { alert('B'); };

B.prototype.updateX = function( value ) {
    B.prototype.x = function() { alert( value ); }
}

var b1 = new B();
var b2 = new B();
b1.x();  // Выводит 'B'
b2.x();  // Также выводит 'B'
b1.updateX('Y');
b1.x();  // Выводит 'Y'
b2.x();  // Также выводит 'Y', потому что мы изменили метод для всех экземпляров, используя prototype

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

0

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

Одной из причин использовать первый вариант является доступ к "приватным членам". Например:

var A = function () {
    var private_var = ...;

    this.x = function () {
        return private_var;
    };

    this.setX = function (new_x) {
        private_var = new_x;
    };
};

В силу правил области видимости в JavaScript, private_var доступен функции, назначенной this.x, но недоступен извне объекта.

0

Первый пример изменяет интерфейс только для этого объекта. Второй пример изменяет интерфейс для всех объектов этого класса.

0

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

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


В вашем первом примере вы добавляете свойство x к каждому экземпляру, созданному с помощью функции A.

var A = function () {
    this.x = function () {
        // сделать что-то
    };
};

var a = new A();    // вызывается функция-конструктор
                    // у вновь созданного объекта появляется свойство 'x'
                    // которое является функцией
a.x();              // и его можно вызвать вот так

Во втором примере вы добавляете свойство к объекту-прототипу, на который ссылаются все экземпляры, созданные с помощью A.

var A = function () { };
A.prototype.x = function () {
    // сделать что-то
};

var a = new A();    // вызывается функция-конструктор
                    // которая ничего не делает в этом примере

a.x();              // вы пытаетесь получить доступ к свойству 'x' экземпляра 'A'
                    // которого не существует
                    // поэтому JavaScript ищет это свойство в объекте-прототипе
                    // который был определен с использованием свойства 'prototype' конструктора

В заключение, в первом примере копия функции присваивается каждому экземпляру. Во втором примере одна копия функции делится всеми экземплярами.

0

Проблема использования this вместо prototype заключается в том, что при переопределении метода конструктор базового класса будет по-прежнему ссылаться на переопределенный метод. Рассмотрим следующий пример:

BaseClass = function() {
    var text = null;

    this.setText = function(value) {
        text = value + " BaseClass!";
    };

    this.getText = function() {
        return text;
    };

    this.setText("Hello"); // Это всегда вызывает BaseClass.setText()
};

SubClass = function() {
    // setText еще не переопределен,
    // поэтому конструктор вызывает метод суперкласса
    BaseClass.call(this);

    // Храним ссылку на метод суперкласса
    var super_setText = this.setText;
    // Переопределяем
    this.setText = function(value) {
        super_setText.call(this, "SubClass говорит: " + value);
    };
};
SubClass.prototype = new BaseClass();

var subClass = new SubClass();
console.log(subClass.getText()); // Hello BaseClass!

subClass.setText("Hello"); // setText уже переопределен
console.log(subClass.getText()); // SubClass говорит: Hello BaseClass!

Теперь сравним это с другим вариантом:

BaseClass = function() {
    this.setText("Hello"); // Это вызывает переопределенный метод
};

BaseClass.prototype.setText = function(value) {
    this.text = value + " BaseClass!";
};

BaseClass.prototype.getText = function() {
    return this.text;
};

SubClass = function() {
    // setText уже переопределен, поэтому это работает как ожидается
    BaseClass.call(this);
};
SubClass.prototype = new BaseClass();

SubClass.prototype.setText = function(value) {
    BaseClass.prototype.setText.call(this, "SubClass говорит: " + value);
};

var subClass = new SubClass();
console.log(subClass.getText()); // SubClass говорит: Hello BaseClass!

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

var A = function (param1) {
    var privateVar = null; // Приватная переменная

    // Вызов this.setPrivateVar(param1) здесь вызовет ошибку

    this.setPrivateVar = function (value) {
        privateVar = value;
        console.log("Значение setPrivateVar установлено в: " + value);

        // param1 все еще здесь, возможная утечка памяти
        console.log("setPrivateVar имеет param1: " + param1);
    };

    // Логика конструктора начинается здесь, возможно, после
    // многих строк кода, определяющих методы

    this.setPrivateVar(param1); // Это допустимо
};

var a = new A(0);
// Значение setPrivateVar установлено в: 0
// setPrivateVar имеет param1: 0

a.setPrivateVar(1);
// Значение setPrivateVar установлено в: 1
// setPrivateVar имеет param1: 0

Теперь сравним с другим вариантом:

var A = function (param1) {
    this.setPublicVar(param1); // Это допустимо
};
A.prototype.setPublicVar = function (value) {
    this.publicVar = value; // Нет приватной переменной
};

var a = new A(0);
a.setPublicVar(1);
console.log(a.publicVar); // 1

Таким образом, использование this приводит к проблемам с вызовами методов и потенциальным утечкам памяти, что делает prototype более предпочтительным подходом, особенно для сложной иерархии классов.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь