【问题标题】:Prototypal Inheritance best practices?原型继承最佳实践?
【发布时间】:2012-01-15 22:59:35
【问题描述】:

我刚刚开始接触 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();

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

【问题讨论】:

    标签: javascript prototypal-inheritance


    【解决方案1】:

    当您在原型上放置一个方法时,每个实例对象都共享对该方法的相同引用。如果您有 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.
    

    【讨论】:

    • 虽然我喜欢这个答案(这就是我的看法),但某些著名的 JavaScript 倡导者会争辩说最好有“私有”方法......
    • @pst "private" 方法如果推断出运行时惩罚就毫无用处。
    • @Raynos 是的,但如果惩罚可以忽略不计(在低实例化或其他低开销环境中),则并非没有用。视情况而定,过于简单地说方法 1 总是不好。
    • @Jed meh,它扼杀了可测试性,扼杀了可扩展性,改变的痛苦,而且没有下划线前缀属性更好。所有的负面因素,没有任何好处。
    • 函数的 this 关键字与作用域无关。
    【解决方案2】:

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

    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!");
    }
    

    【讨论】:

      【解决方案3】:

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

      但在非常具体的情况下,其他人也有优势。阅读此内容,包括 cmets: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() {}
          };
      };
      

      【讨论】:

        【解决方案4】:
        // 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,请注意 Object.create 仅从 ECMAScript 5 开始可用(尽管您可以使用 polyfill)
        【解决方案5】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-11-07
          • 2021-06-24
          • 2014-03-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-07
          • 2016-07-05
          相关资源
          最近更新 更多