Javascript 原型继承最佳实践?

Javascript 原型继承最佳实践?,javascript,prototypal-inheritance,Javascript,Prototypal Inheritance,我刚刚开始学习JavaScript,我正试图将我的头脑集中在原型继承上。似乎有多种方法可以达到相同的效果,所以我想看看是否有任何最佳实践或理由以一种方式而不是另一种方式来做事情。下面是我要说的: // Method 1 function Rabbit() { this.name = "Hoppy"; this.hop = function() { console.log("I am hopping!"); } } // Method 2 functio

我刚刚开始学习JavaScript,我正试图将我的头脑集中在原型继承上。似乎有多种方法可以达到相同的效果,所以我想看看是否有任何最佳实践或理由以一种方式而不是另一种方式来做事情。下面是我要说的:

// Method 1
function Rabbit() {
    this.name = "Hoppy";

    this.hop = function() {
        console.log("I am hopping!");
    }
}

// Method 2
function Rabbit() {}

Rabbit.prototype = {
    name: "Hoppy",

    hop: function() {
        console.log("I am hopping!");
    }
}

// Method 3
function Rabbit() {
    this.name = "Hoppy";
}

Rabbit.prototype.hop = function() {
    console.log("I am hopping!");
}

// Testing code (each method tested with others commented out)
var rabbit = new Rabbit();
console.log("rabbit.name = " + rabbit.name);        
rabbit.hop();

所有这些看起来都有相同的效果(除非我遗漏了什么)。那么,一种方法比另一种更可取吗?如何操作?

在原型上放置方法时,每个实例对象共享对该方法的相同引用。如果有10个实例,则该方法有1个副本

当您执行示例1中所做的操作时,每个实例对象都有自己的相同方法版本,因此如果您创建10个对象,则会有10个代码副本在运行

使用原型是可行的,因为javascript具有将函数执行与实例关联的机制,即它为函数的执行设置
this
属性

因此,使用原型是非常可取的,因为它使用较少的空间(当然,除非这是您想要的)

在方法2中,通过将原型设置为对象文本来设置原型。请注意,这里您正在设置一个属性,我认为您不打算这样做,因为所有实例都将获得相同的属性

在方法3中,您一次构建一个原型任务

我对所有事情都喜欢方法3。i、 在我的构造函数中,我设置了属性值

myObj = function(p1){
   this.p1; // every instance will probably have its own value anyway.
}

myObj.prototype.method1 = function(){..} // all instances share the same method, but when invoked  **this** has the right scope.

做一些性能测试(声明大约100万个兔子变量)。第一种方法将是最耗费时间和内存的

这是一个经常被误解的重要问题。这取决于你想做什么。一般来说,hvgotcode的答案是正确的。任何经常实例化的对象都应该将方法和属性附加到原型

但在非常特殊的情况下,其他人也有优势。阅读以下内容,包括评论:

在某些情况下,上述方法1会有所帮助,使您能够拥有“私有”可读/写属性和方法。虽然在大量实例化的对象中,这通常不值得牺牲,但对于仅实例化一次或几次的对象,或者没有许多内部任务,或者如果您所在的开发团队环境具有许多不同的技能水平和敏感性,这可能会有所帮助

一些开发人员采用了另一种好的策略,试图弥补其他开发人员的一些缺点。即:

var Obj = function() {
    var private_read_only = 'value';

    return {
        method1: function() {},
        method2: function() {}
    };
};

让我们一次看一个例子。第一:

function Rabbit() {
    this.name = "Hoppy";

    this.hop = function() { //Every instance gets a copy of this method...
        console.log("I am hopping!");
    }
}
var rabbit = new Rabbit();
正如您在问题中所说,上述代码将起作用。它将创建
Rabbit
类的新实例。每次创建实例时,
hop
方法的副本将存储在该实例的内存中

第二个示例如下所示:

function Rabbit() {}

Rabbit.prototype = {
    name: "Hoppy",

    hop: function() { //Now every instance shares this method :)
        console.log("I am hopping!");
    }
}
var rabbit = new Rabbit();
这一次,
Rabbit
的每个实例都将共享一个
hop
方法的副本。这样更好,因为它使用更少的内存。但是,每个
Rabbit
都将具有相同的名称(假设不在构造函数中隐藏
name
属性)。这是因为该方法继承自
原型
。在JavaScript中,当您尝试访问对象的属性时,将首先在对象本身上搜索该属性。如果在那里找不到它,我们将查看
prototype
(依此类推,沿着prototype链向上,直到找到一个
prototype
属性为
null
的对象)

你的第三个例子和我做的差不多。实例之间共享的方法应该在
原型上声明。像
name
这样的属性,您很可能希望在构造函数中设置,可以在每个实例的基础上声明:

function Rabbit(rabbitName) {
    this.name = rabbitName;
}
Rabbit.prototype.hop = function() {
    console.log("Hopping!");
}
当使用
new
和构造函数执行原型OO时,完全是可选的

如前所述,如果您可以通过原型共享某些内容,请这样做。原型在内存方面效率更高,在实例化时间方面也更便宜

然而,一个完全有效的替代方案是

function Rabbit() {
    // for some value of extend https://gist.github.com/1441105
    var r = extend({}, Rabbit);
    r.name = "Hoppy";
    return r;
}

在这里,您可以使用“原型”的属性扩展“实例”。真正的原型OO的唯一优势是它是一个实时链接,这意味着对原型的更改会反映到所有实例。

虽然我喜欢这个答案(我就是这么看的),但某些著名的JavaScript拥护者会争辩说,最好使用“私有”方法……@pst“私有”如果方法推断出运行时惩罚,则它们是无用的。@Raynos True,但如果惩罚可以忽略不计(在低实例或其他低开销环境中),则它们不是无用的。视情况而定,过于简单地说方法1总是不好的。@Jed-meh,它破坏了可测试性,破坏了可扩展性,对更改来说是一件痛苦的事情,而且它也不比下划线前缀属性好多少。所有的负面影响,没有任何好处。函数的this关键字与作用域无关。对于选项4,请注意Object.create仅从ECMAScript 5开始可用(但可以使用多填充)
function Rabbit() {
    // for some value of extend https://gist.github.com/1441105
    var r = extend({}, Rabbit);
    r.name = "Hoppy";
    return r;
}