Javascript: мне нужно поместить this.var для каждой переменной в объект?

В языке С++, на котором мне больше всего нравится, обычно объявляется такой объект:

class foo
{
public:
    int bar;
    int getBar() { return bar; }
}

Вызов getBar() отлично работает (игнорируя тот факт, что bar может быть неинициализирован). Переменная bar внутри getBar() находится в области класса foo, поэтому мне не нужно говорить this->bar, если мне действительно не нужно четко указывать, что я имею в виду класс 'bar вместо, скажем, параметра.

Теперь я пытаюсь начать работу с ООП в Javascript. Итак, я ищу, как определить классы и попробовать такие же вещи:

function foo()
{
     this.bar = 0;
     this.getBar = function() { return bar; }
}

И это дает мне bar is undefined. Изменение bar на this.bar устраняет проблему, но для этого всякая переменная меняет код довольно немного. Это необходимо для каждой переменной? Поскольку я не могу найти никаких вопросов, связанных с этим, это заставляет меня чувствовать, что я делаю что-то принципиально неправильное.


EDIT: Правильно, поэтому из комментариев, что я получаю, является то, что this.bar, свойство объекта, ссылается на нечто, отличное от bar, локальной переменной. Может ли кто-нибудь сказать, почему именно это, с точки зрения области видимости и объектов, и если есть другой способ определить объект, где это не нужно?

Ответ 1

JavaScript не имеет объектной модели класса class. Он использует более мощное прототипическое наследование, которое может имитировать классы, но для него не подходит. Все является объектом, а объекты [могут] наследоваться от других объектов.

Конструктор - это просто функция, которая присваивает свойства вновь созданным объектам. Объект (созданный вызовом с new keyword) может быть привязан через this keyword (который является локальным для функции).

Метод также является просто функцией, вызываемой объектом - снова с this, указывающей на объект. По крайней мере, когда эта функция вызывается как свойство объекта, используйте оператор-член (точка, скобки). Это вызывает много путаницы для новичков, потому что если вы передадите эту функцию (например, прослушивателю событий), она "отсоединится" от объекта, к которому он был обращен.

Теперь, где наследование? Экземпляры "класса" наследуются от одного и того же прототипа. Методы определяются как свойства функции для этого объекта (вместо одной функции для каждого экземпляра), экземпляр, на который вы их называете, просто наследует это свойство.

Пример:

function Foo() {
    this.bar = "foo"; // creating a property on the instance
}
Foo.prototype.foo = 0; // of course you also can define other values to inherit
Foo.prototype.getBar = function() {
    // quite useless
    return this.bar;
}

var foo = new Foo; // creates an object which inherits from Foo.prototype,
                   // applies the Foo constructor on it and assigns it to the var
foo.getBar(); // "foo" - the inherited function is applied on the object and
              // returns its "bar" property
foo.bar; // "foo" - we could have done this easier.
foo[foo.bar]; // 0 - access the "foo" property, which is inherited
foo.foo = 1;  // and now overwrite it by creating an own property of foo
foo[foo.getBar()]; // 1 - gets the overwritten property value. Notice that
(new Foo).foo;     // is still 0

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

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

Посмотрите на него в действии:

function Foo() {
    var bar = "foo"; // a local variable
    this.getBar = function getter() {
        return bar; // accesses the local variable
    }; // the assignment to a property makes it available to outside
}

var foo = new Foo; // an object with one method, inheriting from a [currently] empty prototype
foo.getBar(); // "foo" - receives us the value of the "bar" variable in the constructor

Эта функция getter, которая определена внутри конструктора, теперь называется "привилегированным методом", поскольку она имеет доступ к "private" (локальным) атрибутам (переменным). Значение bar никогда не изменится. Вы также можете объявить для него функцию setter, и с этим вы можете добавить некоторые подтверждения и т.д.

Обратите внимание, что методы объекта-прототипа не имеют доступа к локальным переменным конструктора, но они могут использовать привилегированные методы. Добавьте один:

Foo.prototype.getFooBar = function() {
    return this.getBar() + "bar"; // access the "getBar" function on "this" instance
}
// the inheritance is dynamic, so we can use it on our existing foo object
foo.getFooBar(); // "foobar" - concatenated the "bar" value with a custom suffix

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

Это становится еще более сложным, когда вы настраиваете наследование от одного класса к другому - в основном вы должны наследовать объект-прототип ребенка от родительского и применять родительский конструктор к дочерним экземплярам для создания "частные атрибуты". Посмотрите Исправить наследование javascript, Частные переменные в унаследованных прототипах, Определить членов и наследование частного поля в шаблоне модуля JAVASCRIPT и Как реализовать наследование в шаблоне прототипов JS Revealing?

Ответ 2

Явно говоря, this.foo означает (как вы поняли хорошо), что вас интересует свойство foo текущего объекта, на которое ссылается this. Поэтому, если вы используете: this.foo = 'bar';, вы должны установить свойство foo текущего объекта, на которое ссылается this, равное bar.

Ключевое слово this в JavaScript не всегда означает то же самое, что и в С++. Здесь я могу привести пример:

function Person(name) {
   this.name = name;
   console.log(this); //Developer {language: "js", name: "foo"} if called by Developer
}

function Developer(name, language) {
   this.language = language;
   Person.call(this, name);
}

var dev = new Developer('foo', 'js');

В приведенном выше примере мы вызываем функцию Person с контекстом функции Developer, поэтому this ссылается на объект, который будет создан Developer. Как вы можете видеть из результата console.log this происходит от Developer. При первом аргументе метода call мы укажем контекст, с которым будет вызываться функция.

Если вы не используете this, просто созданное вами свойство будет локальной переменной. Поскольку вы, возможно, знаете, что JavaScript имеет функциональную область, поэтому переменная будет локальной, видимой только для функции, где она была объявлена ​​(и, конечно же, все дочерние функции, объявленные внутри родителя). Вот пример:

function foo() {
    var bar = 'foobar';
    this.getBar = function () {
        return bar;
    }
}

var f = new foo();
console.log(f.getBar());  //'foobar'

Это верно, если вы используете ключевое слово var. Это означает, что вы определяете bar как локальную переменную, если вы забудете var к сожалению bar станет глобальным.

function foo() {
    bar = 'foobar';
    this.getBar = function () {
        return bar;
    }
}

var f = new foo();
console.log(window.bar);  //'foobar'

Именно локальная область может помочь вам добиться конфиденциальности и инкапсуляции, которые являются одним из самых больших преимуществ ООП.

Пример реального мира:

function ShoppingCart() {
    var items = [];

    this.getPrice = function () {
       var total = 0;
       for (var i = 0; i < items.length; i += 1) {
          total += items[i].price;
       }
       return total;
    }

    this.addItem = function (item) {
        items.push(item);
    }

    this.checkOut = function () {
        var serializedItems = JSON.strigify(items);
        //send request to the server...
    }
}

var cart = new ShoppingCart();
cart.addItem({ price: 10, type: 'T-shirt' });
cart.addItem({ price: 20, type: 'Pants' });
console.log(cart.getPrice()); //30

Еще одним примером преимуществ области JavaScript является Module Pattern. В шаблоне модуля вы можете имитировать конфиденциальность, используя локальную функциональную область JavaScript. При таком подходе вы можете иметь как частные свойства, так и методы. Вот пример:

var module = (function {

    var privateProperty = 42;

    function privateMethod() {
        console.log('I\'m private');
    }
    return {

       publicMethod: function () {
           console.log('I\'m public!');
           console.log('I\'ll call a private method!');
           privateMethod();
       },

       publicProperty: 1.68,

       getPrivateProperty: function () {
           return privateProperty;
       },

       usePublicProperty: function () {
           console.log('I\'ll get a public property...' + this.publicProperty);
       }

    }
}());

module.privateMethod(); //TypeError
module.publicProperty(); //1.68
module.usePublicProperty(); //I'll get a public property...1.68
module.getPrivateProperty(); //42
module.publicMethod(); 
/*
 * I'm public!
 * I'll call a private method!
 * I'm private
 */

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


Я надеюсь, что с помощью this:-) информации я помог вам понять несколько основных тем JavaScript.

Ответ 3

function Foo() {
  this.bar = 0;
  this.getBar = function () { return this.bar };
}

Когда вы вызываете функцию выше с ключевым словом new - вот так...

var foo = new Foo();

... - происходит несколько вещей:

1) создается объект

2) функция выполняется с ключевым словом this, ссылающимся на этот объект.
3) этот объект возвращается.

foo, тогда этот объект становится следующим:

{
    bar: 0,
    getBar: function () { return this.bar; }
};

Почему бы просто не сделать это:

var foo = {
    bar: 0,
    getBar: function () { return this.bar; }
};

Если бы это был просто один простой объект.

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

См. в javascript все функции создаются с использованием свойства prototype [object], и все объекты, созданные с помощью этой функции (путем вызова его с новым ключевым словом), связаны с этим объектом прототипа. Вот почему это так здорово - вы можете хранить все распространенные методы (и свойства, если хотите) в объекте прототипа, и сэкономить много памяти. Вот как это работает:

function Foo( bar, bob ) {
   this.bar = bar;
   this.bob = bob;
}

Foo.prototype.calculate = function () {
  // 'this' points not to the 'prototype' object 
  // as you could've expect, but to the objects
  // created by calling Foo with the new keyword.
  // This is what makes it work.
  return this.bar - this.bob;  
};

var foo1 = new Foo(9, 5);
var foo2 = new Foo(13, 3);
var result1 = foo1.calculate();
var result2 = foo2.calculate();

console.log(result1); //logs 4
console.log(result2); //logs 10

Что это!

Ответ 4

Чтобы приблизиться к ООП в JavaScript, вы можете взглянуть на шаблон проектирования модуля (например, описанный здесь).

На основе эффекта закрытия этот шаблон позволяет эмулировать частные свойства в ваших объектах.

С помощью свойств 'private' вы можете ссылаться на них напрямую по его идентификатору (т.е. без this ключевого слова, как в конструкторах).

Но в любом случае, замыкания и шаблоны проектирования в JS - продвинутая тема. Итак, ознакомьтесь с основами (также объясненными в упомянутой ранее книге).

Ответ 5

В javascript this всегда ссылается на объект владельца функции. Например, если вы определяете свою функцию foo() на странице, тогда владелец - это объект javascript windows; или если вы определяете foo() на элементе html <body>, то владелец является телом элемента html; и аналогично, если вы определяете функцию onclick элемента <a>, то владелец является якорем.

В вашем случае вы назначаете свойство bar объекту "владелец" в начале и пытаетесь вернуть локальную переменную bar.

Поскольку вы никогда не определяли локальный varialbe bar, он дает вам, как bar undefined.

В идеале ваш код должен был определить переменную как var bar;, если вы хотите вернуть нулевое значение.

Ответ 6

это похоже на модификатор объектов общего доступа (переменные или функции), а var - модификатор частного доступа

Пример

var x = {}; 
x.hello = function(){
    var k = 'Hello World'; 
   this.m = 'Hello JavaScript'; 
}

var t = new x.hello(); 
console.log(t.k); //undefined
console.log(t.m); //Hello JavaScript