使用原型`prototype`而不是`this`的原因

6
我正在使用Lightbox2。
我不理解为什么Lightbox的所有内部成员都是原型(Lightbox.prototype.init),而不是简单的成员(Lightbox.init)?
如果它们针对每个lightbox实例是特定的,那么使用this.init不是更容易吗?
链接:https://github.com/lokesh/lightbox2/blob/master/js/lightbox.js

1
每个被创建的实例都从原型中继承方法。这与使用this不同,并且明显不同于将方法分配给构造函数。仅在构造函数内部将实例特定数据分配给this。应共享的数据分配给原型。 - Felix Kling
@Felix Kling,我特别询问lightbox的结构,我知道thisprototype之间的一般区别。但我不明白在这种情况下使用prototype有什么作用。 - ilyo
2
在任何其他情况下都有相同的优点。例如,.init() 应该在所有实例之间共享,因此它被分配给 prototype。将其分配给 Lightbox 是没有意义的,因为实例不会继承这些方法。在构造函数内部将其分配给 this 是可能的,但是每个实例都有自己的方法副本,这会增加内存使用量。当然,每个通用的理由都更适用于特定情况下的 prototype 而不是 this。在某些情况下选择它并不需要额外的理由。 - Felix Kling
2个回答

8

困惑?不要紧...

可以这样理解:

  1. Lightbox is your class definition, but it's not yet an instance.

  2. Whatever you put directly on the class is like a static member:

    Lightbox.staticFunc = function() {
        // "this" will not point to instance object
    };
    
  3. Whatever you put on its prototype is a shared instance member:

    Lightbox.prototype.instanceFunc = function() {
        // "this" will point to object instance so members can be accessed
    };
    
  4. When you create an instance of a class, all instance members are accessible throught this keyword, but static ones through class definition:

    var someData = Lightbox.staticFunc();
    var l = new Lightbox();
    l.instanceFunc();
    

这是否使您对原型成员有了更清晰的理解?

然后是Lightbox代码

您一直在查看的代码意味着:

// this is a constructor that accesses instance properties (using "this")
// ------
// since properties are accessed via "this.something" means that they are
//    not shared between instances but are part of one particular instance
// ------
function Lightbox(options) {
    this.options = options;
    this.album = [];
    this.currentImageIndex = void 0;
    this.init();
}

// adding an instance method that will be accessible to lightbox object instance
//    that's why it can also access instance members (using "this")
// ------
// all functions that are defined on the prototype are shared between
//    all instances so they consume less resources because not every
//    object instance created them separately.
// ------
Lightbox.prototype.init = function() {
    this.enable();
    return this.build();
};

但是这段代码的某些部分有点令人困惑,例如:

LightboxOptions = (function() {

    function LightboxOptions() {
        this.fileLoadingImage = 'images/loading.gif';
        this.fileCloseImage = 'images/close.png';
        this.resizeDuration = 700;
        this.fadeDuration = 500;
        this.labelImage = "Image";
        this.labelOf = "of";
    }

    return LightboxOptions;

})();

LightboxOptions类虽然没有定义任何私有数据,但它仍包含在函数闭包中。所以,在这个例子中,外部立即执行的函数可以省略而产生相同的结果:

LightboxOptions = function() {
    this.fileLoadingImage = 'images/loading.gif';
    this.fileCloseImage = 'images/close.png';
    this.resizeDuration = 700;
    this.fadeDuration = 500;
    this.labelImage = "Image";
    this.labelOf = "of";
};

当然可以在构造函数中使用this来定义这些函数,但是这样它们就不会在实例之间共享,因此每个对象实例都会定义相同的函数,从而消耗更多的资源。因此,尽管从执行点看起来相同,但这并不相同:

CustomClass = function() {
    this.prop = true;
};
CustomClass.prototype.method = function() { alert("I'm shared."); };

与“稍有不同”:

CustomClass = function() {
    this.prop = true;
    this.method = function() { alert("I'm duplicated in every instance."); };
};

后一种方法会消耗更多的资源,因为每个对象实例都要定义一个函数。

...还有一些内容需要完全理解这件事

假设我们有以下类定义:
var C = function() {
    this.prop = true;
    this.method = function() { console.log("Per instance method"); };
}
C.prototype.method = function() { console.log("Shared instance method"); };

如果我们调用这些代码行,会发生什么?
var a = new C();
var b = new C();
a.method();
b.method();
delete a.method;
a.method();
b.method();

你认为输出结果会是什么?在删除之后会发生什么,你应该有点困惑。哪个方法将被删除?每个实例吗?共享的吗?嗯,按照应该是每个实例的方法将在对象实例a上被删除,这就是为什么之后它报告共享方法已被调用但只在a上。b仍然有自己的每个实例方法。
因此,不再拖延,输出结果如下:
Per instance method      // a.method
Per instance method      // b.method
Shared instance method   // a.method
Per instance method      // b.method

原型属性怎么办

原型属性与众不同。当您创建一个对象实例时,所有这些属性都会被复制到每个对象中,并且不共享。因此,在特定对象的范围内对它们进行的任何操作都不会反映到其他对象上。

如果您在特定对象上删除这样的属性,它仍将以初始值的形式可用,就像在对象实例化时一样。

var C = new function() {};
C.prototype.prop = 1;

var a = new C();
var b = new C();

a.prop = 10;   // does not change the value of "b.prop"
delete a.prop; // "a.prop" is now back to 1

1
@IlyaD:我添加了更多与属性相关的内容,以及它们的行为方式。这将进一步澄清事情。 - Robert Koritnik
还有一件事我不太明白:为什么 function Lightbox(options) { 使用 options 而不是 LightboxOptions,而且 LightboxOptions 又是怎么变成了 options 的呢? - ilyo
1
@IlyaD:你可以随意命名参数。它们只是函数内部的名称。LightboxOptions 的一个实例作为参数传递给构造函数 在这一行 - Felix Kling
@IlyaD:继续Felix的回答...LightboxOptions第347行中变成了options,尽管为了可读性,在结尾处应使用括号:options = new LightboxOptions()这在使用构造函数实例化对象时是非常奇怪和不寻常的。 - Robert Koritnik

4
如果它们针对每个lightbox实例都是具体的,那么使用this.init不是更容易吗?但这样做是不正确的,所以他们将所有东西放在prototype对象中。当你使用prototype时,所有方法仍然对你可用,只是它们不成为实例成员。JavaScript使用原型链工作,当它看到一个方法时,它会搜索整个原型链直到找到指定的方法,如果在中间没有找到,则这个过程一直进行到最终的Object对象。你应该只创建你认为合理或需要的实例成员(通过this),因为如果你使用this关键字添加不必要的方法,它会增加开销(计算浪费)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接