为什么 Object.create 在 JavaScript 中的原型继承方面没有达到我的预期

Why Object.create is not doing what I expect in terms of prototypal inheritance in JavaScript

我仍在努力弄清楚这个 JS 继承的东西是如何工作的,每次我认为我拥有它......我显然没有。

尝试 1

link: https://jsfiddle.net/jacquesvanderhoven/4qkvdn46/

代码

var baseSettings = function ()
{
    this.message = "base message";
    this.getMessage = function(){
        return  "base message from function";
    }
};

var homeSettings = function ()
{
    this.name = "name";
    this.getName = function()
    {
        return "name from function";
    };
};

homeSettings.prototype = Object.create(baseSettings);

var $log = $("#log");
var base = new baseSettings();
var home = new homeSettings();

$log.html($log.html() + "<br /> baseSettings.message: '" + base.message + "'");
$log.html($log.html() + "<br /> baseSettings.getMessage(): '" + base.getMessage() + "'");
$log.html($log.html() + "<br /> homeSettings.name: '" + home.name + "'");
$log.html($log.html() + "<br /> homeSettings.getName(): '" + home.getName() + "'");
$log.html($log.html() + "<br /> homeSettings.message: '" + home.message + "'");
$log.html($log.html() + "<br /> homeSettings.getMessage(): '" + home.getMessage() + "'");

结果

baseSettings.message: 'base message'
baseSettings.getMessage(): 'base message from function'
homeSettings.name: ''
homeSettings.getName(): 'name from function'
homeSettings.message: 'undefined'

其中两个实际上抛出 JS 异常。


尝试 2

如果我添加对超级构造函数的调用,它会稍微改变一下:
Link: https://jsfiddle.net/jacquesvanderhoven/4qkvdn46/5/

代码

var baseSettings = function ()
{
    this.message = "base message";
    this.getMessage = function(){
        return  "base message from function";
    }
};

var homeSettings = function ()
{
    baseSettings.call(this);
    this.name = "name";
    this.getName = function()
    {
        return "name from function";
    };
};

homeSettings.prototype = Object.create(baseSettings);

var $log = $("#log");
var base = new baseSettings();
var home = new homeSettings();

$log.html($log.html() + "<br /> baseSettings.message: '" + base.message + "'");
$log.html($log.html() + "<br /> baseSettings.getMessage(): '" + base.getMessage() + "'");
$log.html($log.html() + "<br /> homeSettings.name: '" + home.name + "'");
$log.html($log.html() + "<br /> homeSettings.getName(): '" + home.getName() + "'");
$log.html($log.html() + "<br /> homeSettings.message: '" + home.message + "'");
$log.html($log.html() + "<br /> homeSettings.getMessage(): '" + home.getMessage() + "'");

结果

baseSettings.message: 'base message'
baseSettings.getMessage(): 'base message from function'
homeSettings.name: '' (注意!这是空的,不应该是)
homeSettings.getName(): 'name from function'
homeSettings.message: 'base message'
homeSettings.getMessage(): 'base message from function'

大部分都可以,但我不明白为什么 homeSettings.name returns 什么都没有?

在我看来,Object.create 确实什么也没做,或者更确切地说,它需要调用超级构造函数才能有所作为,但即便如此,它也不完美。


尝试 3

删除对 Object.create 的调用并保留对超级构造函数的调用。
Link: https://jsfiddle.net/jacquesvanderhoven/4qkvdn46/8/

代码

var baseSettings = function ()
{
    this.message = "base message";
    this.getMessage = function(){
        return  "base message from function";
    }
};

var homeSettings = function ()
{
    baseSettings.call(this);
    this.name = "name";
    this.getName = function()
    {
        return "name from function";
    };
};

var $log = $("#log");
var base = new baseSettings();
var home = new homeSettings();

$log.html($log.html() + "<br /> baseSettings.message: '" + base.message + "'");
$log.html($log.html() + "<br /> baseSettings.getMessage(): '" + base.getMessage() + "'");
$log.html($log.html() + "<br /> homeSettings.name: '" + home.name + "'");
$log.html($log.html() + "<br /> homeSettings.getName(): '" + home.getName() + "'");
$log.html($log.html() + "<br /> homeSettings.message: '" + home.message + "'");
$log.html($log.html() + "<br /> homeSettings.getMessage(): '" + home.getMessage() + "'");

结果

baseSettings.message: 'base message'
baseSettings.getMessage(): 'base message from function'
homeSettings.name: 'name'
homeSettings.getName(): 'name from function'
homeSettings.message: 'base message'
homeSettings.getMessage(): 'base message from function'

结论

我正在研究我在 SO 上看到的各种示例,我不得不承认我还没有达到我理解为什么继承在 JS 中有效或无效的地步。我遇到的另一个问题是,在各种测试中 'this' 的上下文发生了变化,我知道会发生这种情况,但我似乎无法很好地理解为什么或何时发生,因此我无法确切地知道发生了什么.

JavaScript中继承的关键是原型链。每当 属性 查找发生时:

something.propertyName

运行时首先检查直接引用的对象。如果没有找到属性,则检查由原型内部属性链接的对象链(通常只是一个对象)的过程。

因此,实现继承的关键是创建具有原型链的对象。这样做的传统方法是使用 new 运算符和构造函数:

function Constructor() {}
Constructor.prototype = {
  propertyName: function() { alert("hello world"); }
};

现在可以创建对象了:

var something = new Constructor();

和 属性 引用:

something.propertyName(); // "hello world"

请注意,此构造函数什么都不做。它 可以 做一些事情,但它履行了它的作用,即通过存在并拥有该原型对象来简单地使继承工作。

Object.create() 函数只是另一种使用原型链创建对象的方法:

var something = Object.create({
  propertyName: function() { alert("hello world"); }
});

something.propertyName(); // "hello world"

也可以这么写

var something = Object.create(Constructor.prototype);

重新使用第一个示例中的原型对象。

当然,当用作原型的对象是在您的代码中创建并为此目的保留的某个对象,而不是动态对象时,Object.create() 更有意义对象如我上面的例子。要使用您的测试代码:

var baseSettings = { // plain object, not a constructor
  message: "base message",
  getMessage: function() {
    return "base message: " + this.message;
  }
};

var homeSettings = Object.create(baseSettings, {
  name: "home name",
  getName: function() {
    return "home name: " + this.name;
  }
});

现在可以使用 homeSettings 作为原型创建对象,它们将从该对象继承 namegetName,以及 messagegetMessage 来自 baseSettings 对象:

var x = Object.create(homeSettings);
console.log(x.name); // "home name"
console.log(x.getName()); // "home name: home name"
console.log(x.message); // "base message"
console.log(x.getMessage()); // "base message: base message"

这可以用构造函数而不是 Object.create():

function HomeBase() {};
HomeBase.prototype = homeSettings;

var x = new HomeBase();

和相同的 console.log() 调用序列会产生相同的结果。在任何一种情况下——通过 new 或通过 Object.create()——在这些示例中由 x 引用的对象没有自己的属性。

您代码中的主要问题是这一行:

homeSettings.prototype = Object.create(baseSettings);

Object.create 只需要一个对象时,您正在将函数构造函数传递给它。在这种情况下,它只是 return 一个空对象。

使用Object.create进行继承非常简单

//Base class
function Vehicle() {
  this.name = "vehicle " + parseInt( Math.random()*100 );
}

Vehicle.prototype.getName = function () {
  console.log( this.name );
};

//sub class
function Bike() {}

Bike.prototype = Object.create( Vehicle.prototype );
var honda = new Bike();

但是请注意,仅设置了原型引用,永远不会执行构造函数 Vehicle。因此,不会复制 base class 中定义的 public 属性。因此 Bike class 将具有启动方法但没有名称 属性.

这个问题可以在一定程度上通过调用 Base class 上下文为 subclass 的调用方法来解决,如下所示。

通过在 subclass 中调用 Base class 调用方法,我们可以将属性从 Base 复制到 Sub class。

但是,属性是按实例复制的,而不是在原型上,因此这需要更多内存。

function Scooter() {
  //runs on creating every new instance so the name property is copied to each instance not to prototype
  Vehicle.call(this);
}

Scooter.prototype = Object.create( Vehicle.prototype );
var scooty = new Scooter();

我的 github wiki page.

上有更多详细信息