Есть ли способ создать объекты JavaScript, которые ведут себя как С++ RValues?

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

AFAIK, следующие коды являются эквивалентными в С++ и Javascript:

С++

// Class definition
template <typename T> class foo
{
public:
    // Constructor
    foo(T value) { this->value = value; }
    // Public function
    T twice() { return this->value + this->value; }

private:
    // Private function
    void bar() { }
    // Private member
    T value;
};

JavaScript

// "Class" definition and constructor
function foo(value)
{
    // "Private" member
    this.value = value;

    // "Private" function
    this.bar = function() { };
}

// Public function
foo.prototype.twice = function() { return this.value + this.value; };

Использование обоих классов тоже похоже:

С++ живая демонстрация

foo<int> f1(1);
foo<std::string> f2("1");

std::cout << f1.twice() << '\n'; // output: 2
std::cout << f2.twice() << '\n'; // output: 11

JavaScript живая демонстрация

var f1 = new foo(1);
var f2 = new foo('1');

print(f1.twice()); // output: 2
print(f2.twice()); // output: 11

Но есть вещь, которая не может быть выполнена с классом JavaScript и возможно сделать с классом С++: использование временного RValue для выполнения задачи:

С++

std::cout << foo<float>(3.14f).twice() << '\n'; // output: 6.28

JavaScript

print(foo(3.14).twice()); // Uncaught TypeError: undefined is not a function

Я думаю, что ошибка в версии JavaScript связана с тем, что foo является функцией и ничего не возвращает (undefined), поэтому сначала я думал об изменении конструктора с помощью кода ниже:

JavaScript

// "Class" definition and constructor
function foo(value)
{
    // "Private" member
    this.value = value;

    // "Private" function
    this.bar = function() { };

    return this; // <----- new code!
}

Но это не работает вообще; объект, возвращаемый командой return this;, не имеет тип foo (foo(3.14) instanceof foo is false).

При отладке в Chrome 35.0.1916.114 тип this в инструкции return this; равен foo, но тип изменяется на window в этой ситуации:

var x = foo(3.14); // x is typeof window

После того, как вы сделали ввод, здесь возникают вопросы:

  • Почему тип this foo внутри конструктора и window при захвате снаружи?
    • Потому что оператор new не используется?
  • Есть ли способ создать объекты JavaScript, которые ведут себя как С++ RValues?

Ответы

Ответ 1

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

В отличие от этого ключевого слова new контекст this устанавливается в глобальную область, которая для браузеров - это объект window, а возвращаемое значение может быть записано любым его именем.

Возможно создать что-то вроде вашего примера на С++, в котором вам не понадобится ключевое слово new, и оно все равно возвращает новый объект.

Fiddle

function foo(value) {
    function foo(value) {
        this.value = value;

        this.bar = function () {};
        this.twice = function() { return this.value + this.value; }
    }

    return new foo(value);
}

console.log( foo(3.14).twice() ); // 6.28

Объяснение:

внешняя функция foo ведет себя как нормальная функция и предназначена для вызова без ключевого слова new. Внутри находится внутренний foo, который должен быть похож на класс, называемый ключевым словом new. Внешний foo создает новый экземпляр внутреннего foo и возвращает его. Таким образом, его можно использовать как пример С++. Нет необходимости объявлять класс-функцию во внешней функции, это просто зависит от того, хотите ли вы инкапсулировать ее во внешнюю.


Видимость

Два примера в вопросе не совсем эквивалентны, потому что пример JavaScript использует все общедоступные свойства и методы, тогда как пример С++ имеет bar и value как закрытый.

Ниже приведена версия, которая ближе к версии С++:

Fiddle

function foo(value) {
    function foo(value) {
        var value = value;

        function bar(){}
        this.twice = function() { return value + value; }
    }

    return new foo(value);
}

console.log( foo(3.14).twice() ); // 6.28 
console.log( foo(3.14).value ); // undefined because it private
console.log( foo(3.14).bar() ); // undefined because it private

Как видно из тестовых примеров, value и bar не являются общедоступными/видимыми. Это достигается за счет использования префикса this и объявления value с использованием ключевого слова var (который определяет локальную переменную). Функция объявляется как выражение вместо выражения. В JavaScript нет формального способа объявления/дифференциации общедоступных и частных свойств или методов, подобных языкам ООП.

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