Eloquent Javascript 栩栩如生的 Terrarium 克隆

Eloquent Javascript LifeLikeTerrarium clone

我正在阅读 Marijn Haverbeke 的 Eloquent Javascript,但在理解 LifeLikeTerrarium 示例的一部分时遇到一些问题。具体如下代码:

function clone(object){
    function OneShotConstructor(){}
        OneShotConstructor.prototype = object;
        return new OneShotConstructor();                
}

由 LifeLikeTerrarium 的构造函数调用:

function LifeLikeTerrarium(plan){
    Terrarium.call(this, plan);
}           
LifeLikeTerrarium.prototype = clone(Terrarium.prototype);
LifeLikeTerrarium.prototype.constructor = LifeLikeTerrarium;

我的理解是 LifeLikeTerrarium 应该继承自 Terrarium,此代码正试图实现这一点。我不确定它是如何实现的。

我觉得我理解了其余的代码,但这似乎是一个非常关键的部分。真的很感激任何愿意为我分解它的人。

谢谢!

Does doing the Terrarium.call(this, plan) pass the data to the Terrarium constructor as if you had called Terrarium(plan) within the context of the inheritence?

是的。 Function#call 使用给定的 this 值和单独提供的参数调用函数。这意味着我们将被调用函数内的 this 指定为我们想要的任何对象。 Terrarium.call(this, plan) 将我们当前的 this(这将是 LifeLikeTerrarium 的一个实例)传递给对 Terrarium 的调用。如果 Terrarium 在 itslef 内部赋值或修改 this,它修改的 this 将是我们使用 call 传递给它的内容,这里是 LifeLikeTerrarium 的当前实例. call 的其余参数都作为单独的参数传递给被调用的函数。

示范:

function foo(a, b) {
    this.sum = a + b;
}

var obj = {};

foo.call(obj, 5, 7);                // obj will be the 'this' used inside foo.
                                    // 5 and 7 will be the values of foo's arguments a and b, respectively

console.log(obj);                   // et voilà

What is the purpose of calling clone(object)? How is this different from saying LifeLikeTerrarium.prototype = Terrarium.prototype?

我们不想做 LifeLikeTerrarium.prototype = Terrarium.prototype。曾经。因为 LifeLikeTerrarium 很可能也会有自己的方法 Terrarium。想象一下,我们向 LifeLikeTerrarium 的原型添加一个名为 foo 的方法。 foo 也将可供 Terrarium 使用。更糟糕的是,foo 可以取代 Terrarium 自己的方法 foo,如果它碰巧有一个:

function Terrarium() {  }
Terrarium.prototype.sayHi = function() {
    console.log("Hi! I'm Terrarium");
}

function LifeLikeTerrarium() {  }
LifeLikeTerrarium.prototype = Terrarium.prototype;    // now LifeLikeTerrarium.prototype and Terrarium.prototype are both referencing the same object, changes to one are reflected on the other
LifeLikeTerrarium.prototype.sayHi = function() {      // Terrarium.prototype.sayHi is gone for ever. May it rest in peace.
    console.log("Hi! I'm LifeLikeTerrarium");
}

var terrarium = new Terrarium();
terrarium.sayHi();                                    // wrong wrong wrong

如您所见,这非常糟糕。根据方法的作用,它甚至可能抛出错误并中断执行(例如,如果新方法使用原始对象中不存在的属性 Terrarium):

function Terrarium() {  }
Terrarium.prototype.sayHi = function() {
    console.log("Hi! I'm an abstract Terrarium that doesn't have a name");
}

function LifeLikeTerrarium(name) {
    this.name = name;
}
LifeLikeTerrarium.prototype = Terrarium.prototype;
LifeLikeTerrarium.prototype.sayHi = function() {
    console.log("Hi! I'm " + this.name.toUpperCase());  // b.. b.. but Terrarium doen't have a property called name. Let's see what will happen. Maybe it'll work 
}

var terrarium = new Terrarium();
terrarium.sayHi();                                      // Surprise! You got mail, I mean an error

除此之外,我们至少会用它甚至不需要或不使用的方法污染原始对象:

function Terrarium() {  }

function LifeLikeTerrarium() {  }
LifeLikeTerrarium.prototype = Terrarium.prototype;
LifeLikeTerrarium.prototype.sayHi = function() {      // now Terrarium has a method called sayHi although it originally didn't have one.
    console.log("Hi!");
}

var terrarium = new Terrarium();
terrarium.sayHi();                                    // Want proof? Here you go.

我们如何解决这个问题?答案是:我们不将 Terrarium.prototype 分配给 LifeLikeTerrarium.prototype,这样两个原型都引用同一个对象。不,我们分配给 LifeLikeTerrarium.prototype 一个普通的新对象,其原型设置为 Terrarium.prototype。新的普通对象(这是 new OneShotConstructor() 的结果)将充当 LifeLikeTerrarium 自己的原型。该普通对象将其原型设置为 Terrarium.prototype,这导致 Terrarium 的方法可用于 LifeLikeTerrarium,这种现象被称为 "Inheritance"。下面是两种方式的区别图:

LifeLikeTerrarium.prototype = Terrarium.prototype:

Terrarium.prototype ---------------------> {
                               |                ...
                               |                ...
                               |           }
                               |
LifeLikeTerrarium.prototype ---/

LifeLikeTerrarium.prototype = clone(Terrarium.prototype):

Terrarium.prototype ---------------------> {
                               |                ...
                               |                ...
                               |           }
                               |
                               \------------------------------\
                                                              |
LifeLikeTerrarium.prototype -------------> {                  |
                                                ...           |
                                                ...           |
                                                prototype: ---/
                                           }

如您所见,在第一个图表中,Terrarium.prototypeLifeLikeTerrarium.prototype 都引用了同一个对象。而在第二张图中,每个对象都引用了自己的对象:Terrarium.prototype 引用了自己的对象,该对象在您的代码中某处定义,而 LifeLikeTerrarium.prototype 引用了 OneShotConstructor 的实例(这是一个空的对象,因为构造函数是空的)。另一个好处是 LifeLikeTerrarium.prototype 的原型引用了 Terrarium.prototype(由于 OneShotConstructor.prototype = object; 而成为可能)。这创建了一个很好的原型链,让 Terrarium 的方法可用于 LifeLikeTerrarium 如果后者不想拥有自己的方法,如果它想要(想要拥有自己的),那将是完全没问题,因为它们不会取代 Terrarium,它们只会隐藏它们。这就是 prototype inheritence.

的本质

What is the purpose of assigning LifeLikeTerrarium's constructor to itself?

这里已经有了答案:Why is it necessary to set the prototype constructor?