【问题标题】:Difference between methods of defining JavaScript 'classes'定义 JavaScript“类”的方法之间的区别
【发布时间】:2012-09-05 15:11:42
【问题描述】:

这两种在 JavaScript 中定义“类”的方法有什么区别?

方法一

在构造函数中定义方法:

function MyClass()
{
    this.foo = function() { console.log('hello world!'); };
}

方法二

在原型上定义方法:

function MyClass()
{}

MyClass.prototype.foo = function() { console.log('hello world!'); };

【问题讨论】:

    标签: javascript prototype function-prototypes


    【解决方案1】:

    第一个将在您的对象的每个实例化上创建一个新的函数对象,第二个将为每个实例分配一个原型方法的引用。简而言之:第二种效率更高,因为所有实例都将共享一个函数对象。

    这只是原型链的逻辑,你可以尝试并通过任何对象访问任何东西:

    var objLiteral = {foo:'bar'};
    

    在访问objLiteral.foo时,JS会首先查看对象本身已经定义的属性,如果找到则返回值。如果 JS 找不到对象本身的属性,它会检查对象的原型,因此:

    objLiteral.valueOf();//method defined @Object.prototype
    objLiteral.valueOf === Object.prototype.valueOf //true
    

    但是当你使用第一种方法时:

    function SomeConstructor()
    {
        this.methd = function()
        {
            return true;
        }
    }
    var f = new SomeConstructor();
    var g = new SomeConstructor();
    f.methd === g.methd;//FALSE!
    

    这表明我们正在处理 2 个独立的函数对象。将函数定义移动到原型中,f.methd === g.methd; 将为真:

    function SomeConstructor()
    {
    }
    SomeConstructor.prototype.methd = function()
    {
        return true;
    }
    var f = new SomeConstructor();
    var g = new SomeConstructor();
    f.methd === g.methd;//true!
    

    回应您的评论:

    在原型级别定义方法允许您更改特定任务的方法,然后 “重置” 将其恢复为默认行为。假设您在创建 AJAX 请求的函数中:

    someObject.toString = function(){ return JSON.stringify(this);}
    //when concatinating this object you'll get its json string
    //do a lot of stuff
    delete (someObject.toString);
    

    JS 将再次检查对象是否具有定义的toString 属性其自身。所以 JS 会删除你分配给 toString 属性的函数。下次调用toString 时,JS 将重新开始扫描原型链,并使用第一次出现的方法(在原型中)。让我们澄清一下:

    function SomeConstructor()
    {
    }
    SomeConstructor.prototype.methd = function()
    {
        return true;
    }
    var f = new SomeConstructor();
    var g = new SomeConstructor();
    f.methd = function(){return false;};
    g.methd();//returns true, still <-- method is gotten from the prototype
    f.methd();//returns false <-- method is defined @ instance level
    delete (f.methd);
    f.methd();//returns true, f doesn't have the method, but the prototype still does, so JS uses that.
    

    或者更好的是,您甚至可以用另一个原型中的方法替换实例的方法:

    f.methd = Object.prototype.valueOf;//for as long as you need
    

    最后一个例子没有意义,因为 f 已经有了 valueOf 方法:它的继承链看起来像这样:var f ---&gt; SomeConstructor ---&gt; Object,让您也可以访问所有 Object.prototype 方法!整洁,不是吗?

    这些只是虚拟示例,但我希望您能看到这是使 JS 成为一种极其灵活(我必须承认有时过于灵活)和富有表现力的语言的特性之一。

    【讨论】:

    • 感谢您的出色回答。除了性能/内存差异之外,如果要在两种方法之间进行转换,是否存在任何可能导致问题的功能差异?
    • 不是真的,事实上还有额外的好处,我会编辑我的答案来解释
    • @DrewNoakes:完成,我希望我没有太热情和混乱,但这是一个很好的问题(顺便说一句+1),因为它涉及 JS 的一个重要特性,但是这经常被很多人误解。
    • 您的编辑也非常棒 Elias ——它有助于巩固语言的一个基本方面,我希望这将使某些事情在未来看起来不那么神秘/混乱。我希望答案会这么有趣!
    • 哦,是的:当使用for...in 循环时,原型方法出现,但当您检查instanceVar.hasOwnProperty(loopVar) 时不会出现,而是方法名称(hasOwnProperty ) 有点说明了一切,不是吗。顺便说一句,该方法是从 Object.prototype 继承的,这是原型继承如何工作的另一个示例 ;-) - 感谢 Speransky Danil 提醒我那个。感谢 DrewNoakes 的称赞,总是乐于提供帮助。快乐编码
    【解决方案2】:

    在第一种情况下,将为每个实例创建函数并将其设置为对象中的foo 属性。在第二种情况下,它是共享功能。当你调用obj.prop 时,它会在对象本身中查找它,如果它不存在,则它会在proto 对象中查找它等等,它被称为chain of prototypes

    例如此代码提供foo:

    function MyClass() {
      this.foo = function () {};
    }
    
    var myVariable = new MyClass();
    for (var i in myVariable) if (myVariable.hasOwnProperty(i)) console.log(i);
    

    但这不是:

    function MyClass() {
    }
    
    MyClass.prototype.foo = function () {};
    
    var myVariable = new MyClass();
    for (var i in myVariable) if (myVariable.hasOwnProperty(i)) console.log(i);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-06-23
      • 2011-03-26
      • 1970-01-01
      • 2012-03-31
      • 1970-01-01
      • 2020-08-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多