强制上下文

Forcing the context

本文关键字:上下文      更新时间:2023-09-26

我有这样一个类,其中我有一个私有属性和一个用于访问的公共方法:

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

我的class变成了:

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

not

f().apply(scope);

f.apply(scope);

(f后无())您希望在函数f对象上使用apply函数,而不是调用函数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也允许您curry参数,这在本实现中没有实现)。setScope是一个特别不幸的名字,因为它不设置作用域,它设置上下文

说了这么多,没有理由在Person构造函数中需要setScope。你可以这样做:

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对象都有自己的访问器函数的副本。如果有很多这样的对象,那可能是内存问题。如果只有几个,那也没关系。

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

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

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

很难理解你想要达到的目的。但是,如果我猜你正试图创建一个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"

不需要将任何上下文绑定到此方法,因此您可能会寻找其他方法。如果你能说明你的需要,我很乐意修改我的答案。