强迫上下文

时间:2011-07-12 21:31:04

标签: javascript jquery scope this

我有这个课程,我有一个私人财产和一个公共访问方法:

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 个答案:

答案 0 :(得分:9)

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);
    }
}

Live example

请注意,此函数的常用名称及其在新ECMAScript5 standard中的名称为bind(第15.3.4.5节; ECMAScript5的bind也允许您{{3参数,这不是由这个实现完成的)。 setScope是一个特别不幸的名称,因为它没有设置范围,它设置了上下文

说了这么多,你的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;
};

curry

但是使用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;
    };
}

Live example

正如您所看到的,这非常简单,并且保留了instanceof关系。请注意,我们根本没有在name内对Name的引用进行限定,因此我们在构造函数调用中使用局部变量,我们的Name函数将关闭它,创建了。

我还冒昧地给构造函数一个 name ,因为Live example。我应该给访问者一个名字:

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 :(得分:1)

首先,我认为解决这个问题的正确方法是“闭包”方法,因为语法更容易理解,更有意义,大多数用Javascript编写的面向对象的代码都是用这种方式编写的。另外需要注意的是,在您的方法中,可以通过访问Person.Name(而不是(new Person()).Name)从外部访问“私人”成员。

话虽如此,似乎你想要像Prototype.JS's bind method这样的东西,它允许你将函数引用绑定为对特定对象的方法调用,并且还正确传递所有参数(包括允许预加载的参数) 。

查看完整实现的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);
    };
};            

答案 2 :(得分:1)

很难理解你想要实现的目标。但是,如果我猜你要创建一个带有名字方法的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
}

请注意,我已使用小写首字母定义了name函数。这是JavaScript中的标准做法,其中只有构造函数通常是大写的。要使用此课程,请执行以下操作:

person = new Person();

person.name( "Ermes Enea Colella" );

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

不需要使用此方法绑定任何上下文,因此您可能正在寻找其他内容。如果您能澄清您的需求,我将很乐意编辑我的答案。

我希望这会有所帮助。