Prototypal Inheritance最佳实践?

时间:2012-01-15 22:59:36

标签: javascript prototypal-inheritance

我刚刚进入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();

所有这些似乎都具有相同的效果(除非我遗漏了什么)。那么一种方法优于另一种方法吗?你是怎么做到的?

5 个答案:

答案 0 :(得分:12)

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

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

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

所以使用原型是首选,因为它占用的空间更少(当然,除非你想要的是这样)。

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

在方法3中,您一次构建一个分配原型。

我更喜欢方法3。即在我的构造函数中,我设置了属性值

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.

答案 1 :(得分:3)

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

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属性)。这是因为该方法继承自prototype。在JavaScript中,当您尝试访问对象的属性时,将首先在对象本身上搜索该属性。如果在那里找不到它,我们会查看prototype(依此类推,直到我们到达prototype属性为null的对象为止的原型链上。)

你的第三个例子就像我做的那样。应在prototype上声明实例之间共享的方法。您可能希望在构造函数中设置的name等属性可以基于每个实例声明:

function Rabbit(rabbitName) {
    this.name = rabbitName;
}
Rabbit.prototype.hop = function() {
    console.log("Hopping!");
}

答案 2 :(得分:1)

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

但在非常具体的情况下,其他人也有优势。阅读本文,包括评论:http://net.tutsplus.com/tutorials/javascript-ajax/stop-nesting-functions-but-not-all-of-them/

有时上面的方法1有帮助,使您具有“私有”可读/可写属性和方法。虽然这通常不值得在重度实例化的对象中牺牲,对于仅实例化一次或几次的对象,或者没有很多内部任务,或者如果您处于具有许多不同技能水平和敏感度的开发团队环境中,可以提供帮助。

一些开发者采用了另一种良好的策略,试图弥合其他人的一些缺点。那就是:

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

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

答案 3 :(得分:1)

// option 4
var Rabbit {
    constructor: function () {
        this.name = "Hoppy";
        return this;
    },

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

var rabbit = Object.create(Rabbit).constructor();
console.log("rabbit.name = " + rabbit.name);        
rabbit.hop();

使用new进行原型OO时,构造函数是完全可选的。

正如已经指出的那样,如果你可以通过原型分享一些东西。原型在内存方面更有效,而且在实例化时间方面更便宜。

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

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

这里扩展的“实例”具有“原型”的属性。真正的原型OO的唯一优势是它是一个实时链接,这意味着对原型的更改反映了所有实例。

答案 4 :(得分:0)

进行一些性能测试(声明大约1百万个兔子变量)。第一种方法将耗费大部分时间和内存。