Форсирование контекста - PullRequest
4 голосов
/ 13 июля 2011

У меня есть этот класс, где у меня есть частное свойство и открытый метод для доступа:

Person = function () {
    this.Name = "asd";

    var _public = new Object();
    _public.Name = function (value) {
        if (value == undefined) { //Get
            return this.Name
        } else {
            this.Name = value; //Set
        }
    };

    return _public;
};

Я хочу принудительно установить контекст в _public.Name для доступа this.Name.

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

Я нашел способ сделать это, расширить объект Функция:

Function.prototype.setScope = function (scope) {
    var f = this;

    return function () {
        f().apply(scope);
    }
}

И мойкласс становится:

Person = function () {
    this.Name = "asd";

    var _public = new Object();
    _public.Name = function (value) {
        if (value == undefined) {
            return this.Name
        } else {
            this.Name = value;
        }
    }.setScope(this);

    return _public;
};

Таким образом, я могу корректно форсировать контекст, но не могу передать value и не могу, однако, вернуть this.Name.

Ответы [ 3 ]

9 голосов
/ 13 июля 2011

Не

f().apply(scope);

просто

f.apply(scope);

(Нет () после f.) Вы хотите использовать функцию apply для функции f объект, а не вызов функции f и доступ apply к ее возвращаемому значению.

Чтобы также передать аргументы, которые получает ваша функция в setScope, добавьте:

f.apply(scope, arguments);

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

Я также хотел бы, чтобы она возвращала возвращаемое значение:

return f.apply(scope, arguments);

Итак setScope становится:

Function.prototype.setScope = function (scope) {
    var f = this;

    return function () {
        return f.apply(scope, arguments);
    }
}

Живой пример

Обратите внимание, что обычное имя для этой функции и имя, которое она имеет в новом ECMAScript5 стандарт , равен bind (раздел 15.3.4.5; ECMAScript5 bind также позволяет вам карри аргументы, чего не делает эта реализация).setScope - это особенно неудачное имя, потому что оно не устанавливает scope , оно устанавливает context .

Сказав все это, вы ни за что не будетенужно setScope в вашем Person конструкторе.Вы можете просто сделать это:

Person = function () {
    var self = this;

    this.Name = "asd";

    var _public = new Object();
    _public.Name = function (value) {
        if (value == undefined) {
            return self.Name;
        } else {
            self.Name = value;
        }
    };

    return _public;
};

Живой пример

Но использование bind (он же setScope) может быть полезным в тех местах, где вы этого не делаетенужно новое замыкание в контексте, в котором вы это делаете.


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

var p = new Person();
alert(p instanceof Person); // Expect "true", but in your case will be "false"

... потому что вы заменяете объект new, созданный для вас, но возвращаете другой объект из вашего конструктора (который переопределяет значение по умолчанию).

Вместо того, чтобы создавать новый объект и возвращать его в своем конструкторе, позвольте объекту, сконструированному для вас new, быть объектом (и, таким образом, поддерживается отношение Person), но вы все равно можете стать действительно приватнымпеременные и методы доступа:

function Person() {
    // Private variable
    var name = "asd";

    // Accessor function
    this.Name = function(value) {
        if (typeof value === "undefined") {
            return name;
        }
        name = value;
    };
}

Живой пример

Как видите, это значительно проще и сохраняет отношение instanceof.Обратите внимание, что мы вообще не определяем наши ссылки на name в Name, и поэтому мы используем локальную переменную в вызове конструктора, в котором была создана наша функция Name, которая закрывается над ней.

Я также позволил себе дать функции-конструктору имя , потому что Я не фанат анонимных функций .Мне следовало бы также дать аксессору имя:

function Person() {
    // Private variable
    var name = "asd";

    // Accessor function
    this.Name = Person_Name;
    function Person_Name(value) {
        if (typeof value === "undefined") {
            return name;
        }
        name = value;
    }
}

Не по теме 2 : в коде JavaScript преобладающим соглашением является использование начальных заглавных букв в именах функций только для функций конструктора (например, Person), но не для других видов функций (например, Name).Конечно, вы можете делать все, что захотите, но я подумал, что упомяну соглашение, поскольку другим людям будет проще читать ваш код.


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

1 голос
/ 13 июля 2011

Трудно понять, чего вы пытаетесь достичь.Но если я предполагаю, что вы пытаетесь создать класс Person с методом имени, чтобы получить / установить имя человека, вот мое предложение:

function Person() {
  this._name = undefined; // not required but is better than assigning a fake name

  return this;
}

Person.prototype.name = function( _name ) {
  if ( _name === undefined ) return this._name; // get
  return this._name = _name; // set
}

Обратите внимание, что я определил функцию имени с более низкимрегистр первой буквыЭто стандартная практика в JavaScript, где только конструкторы обычно пишутся с заглавной буквы.Чтобы использовать этот класс, вы делаете:

person = new Person();

person.name( "Ermes Enea Colella" );

alert( person.name ); // displays "Ermes Enea Colella"

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

Надеюсь, это поможет.

1 голос
/ 13 июля 2011

Во-первых, я думаю, что правильный путь для этого - метод «замыкания», поскольку синтаксис становится все проще и проще для понимания и имеет больше смысла, и большинство объектно-ориентированного кода, написанного на Javascript, написано именно так.Следует также отметить, что в вашем методе доступ к «частному» члену может быть получен извне путем доступа к Person.Name (вместо (new Person()).Name).

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

Посмотрите на исходный код Prototype.JS для полной реализации, но простая реализация этой семантики может выглядеть так:

Function.prototype.bind = function(context) {
    var callee = this;
    var args = Array.prototype.slice.call(arguments,1);
    return function() {
        var newargs = args.concat(Array.prototype.slice.call(arguments,0));
        return callee.apply(context, newargs);
    };
};            
...