JavaScript: Class.method与Class.prototype.method的区别

535

以下两个声明之间有什么区别?

Class.method = function () { /* code */ }
Class.prototype.method = function () { /* code using this.values */ }

把第一条语句看作静态方法的声明,第二条语句看作实例方法的声明,这样理解是否合适?

5个回答

738

是的,第一个函数与构造函数的对象实例没有关系,你可以将其视为'静态方法'

在JavaScript中,函数是一等公民对象,这意味着您可以像处理任何对象一样处理它们,在这种情况下,您只是向函数对象添加属性。

第二个函数,由于您正在扩展构造函数原型,它将对使用new关键字创建的所有对象实例可用,并且该函数内部的上下文(this关键字)将引用您调用它的实际对象实例。

考虑以下示例:

// constructor function
function MyClass () {
  var privateVariable; // private member only available within the constructor fn

  this.privilegedMethod = function () { // it can access private members
    //..
  };
}

// A 'static method', it's just like a normal function 
// it has no relation with any 'MyClass' object instance
MyClass.staticMethod = function () {};

MyClass.prototype.publicMethod = function () {
  // the 'this' keyword refers to the object instance
  // you can access only 'privileged' and 'public' members
};

var myObj = new MyClass(); // new object instance

myObj.publicMethod();
MyClass.staticMethod();

1
但是为什么 Function.prototype.method == Function.method 呢? - Raghavendra
1
@Raghavendra 这不是。 - Zorgatone
1
@Menda,你的链接失效了。 - EugenSunic

32

是的,第一个是被称为静态方法类方法的方法,而第二个是实例方法

考虑以下示例,以更详细地了解它。

在ES5中

function Person(firstName, lastName) {
   this.firstName = firstName;
   this.lastName = lastName;
}

Person.isPerson = function(obj) {
   return obj.constructor === Person;
}

Person.prototype.sayHi = function() {
   return "Hi " + this.firstName;
}
在上述代码中,isPerson是一个静态方法,而sayHiPerson的实例方法。

以下是如何从Person构造函数创建对象。 var aminu = new Person("Aminu", "Abubakar"); 使用静态方法isPersonPerson.isPerson(aminu); //将返回true 使用实例方法sayHiaminu.sayHi(); //将返回"Hi Aminu" 在ES6中
class Person {
   constructor(firstName, lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
   }

   static isPerson(obj) {
      return obj.constructor === Person;
   }

   sayHi() {
      return `Hi ${this.firstName}`;
   }
}

观察如何使用static关键字声明静态方法isPerson

创建一个Person类对象。

const aminu = new Person("Aminu", "Abubakar");

使用静态方法isPerson

Person.isPerson(aminu); // 将返回true

使用实例方法sayHi

aminu.sayHi(); // 将返回"Hi Aminu"

注意:这两个示例本质上是相同的,JavaScript仍然是一种无类别的语言。在ES6中引入的class主要是现有基于原型的继承模型的一种语法糖。


在ES6中,你只是描述了一种语法糖。这不是"ES2015"(请大家停止使用ES6,使用正确的术语ES2015)的做法。这只是另一种做法,在我看来是不正确的。 - basickarl
3
@KarlMorrison Aminu并没有写下“way of doing it”,这是你自己写的并且对此感到不满。关于ES6与ES2015的观点,你可能说得很有道理,但在日常对话中,人们通常为了效率而采用更短的约定,因此我认为从写作中删除它是不可能的,也不建议这样做。 - wuliwong
感谢您在回答中提到ES6部分,这解释了很多问题,特别是与上面两个“公共+特权”答案结合起来。然而,我对您的obj.constructor === Persontrue的例子感到非常困惑...什么?一个类实例的构造函数怎么可能等于类本身呢...?(这就像说一个集合的子集是集合本身等等...) - Andrew
哦...这是否意味着在一天结束时,构造函数就是JS类的全部内容?其他所有内容都要么堆积在构造函数中,要么完全是静态结构,与类隔离,除了名字/概念之外(显然提供了一个隐含的“this”)?(因此,我原以为是集合的子集实际上并不是子集。) - Andrew
@Andrew 或许这种超现实的感觉来自于 JavaScript 中 类和构造函数是同一件事。顺便说一下,我一直回到 这个图表。它很奇怪,但值得研究。最终,它真正为我解开了 JavaScript 如何定义类或者假装定义类的谜团。理解构造函数属性的关键在于 John Sonderson 的评论:b.constructor 像任何类属性一样解析为 b.__proto__.constructor,从而指向 Foo - Bob Stein
显示剩余2条评论

20
当您创建多个 MyClass 实例时,publicMethod 方法在内存中仍然只有一个实例,但是特权方法 privilegedMethod 将导致创建许多实例,并且静态方法 staticMethod 与对象实例无关。因此原型可以节省内存。
此外,如果更改父对象的属性,则子对象对应的属性未更改,则其将被更新。

此外,如果您更改了父对象的属性,如果子对象对应的属性没有被更改,它将被更新。不确定您的意思是什么。这只有在更改父对象的原型属性时才会发生吗? - Peter Out

19

对于视觉学习者来说,在不使用 .prototype 的情况下定义函数

ExampleClass = function(){};
ExampleClass.method = function(customString){
             console.log((customString !== undefined)? 
                          customString : 
                          "called from func def.");}
ExampleClass.method(); // >> output: `called from func def.`  

var someInstance = new ExampleClass();
someInstance.method('Called from instance');
    // >> error! `someInstance.method is not a function`  

使用相同的代码,如果添加了.prototype

ExampleClass.prototype.method = function(customString){
             console.log((customString !== undefined)? 
                          customString : 
                          "called from func def.");}
ExampleClass.method();  
      // > error! `ExampleClass.method is not a function.`  

var someInstance = new ExampleClass();
someInstance.method('Called from instance');
                 // > output: `Called from instance`
为了更清晰明了,
ExampleClass = function(){};
ExampleClass.directM = function(){}  //M for method
ExampleClass.prototype.protoM = function(){}

var instanceOfExample = new ExampleClass();

ExampleClass.directM();     ✓ works
instanceOfExample.directM();   x Error!

ExampleClass.protoM();     x Error!
instanceOfExample.protoM();  ✓ works

请注意,上面的示例中,一些实例方法someInstance.method()将不会被执行,因为ExampleClass.method()会导致错误并且无法继续执行。
但是为了说明和便于理解,我保留了这个顺序。

Chrome开发者控制台JS Bin生成结果。
单击上面的jsbin链接以逐步执行代码。
使用ctrl+/切换注释部分。


4

A. 静态方法:

      Class.method = function () { /* code */ }
  1. method() 是一个函数属性,添加到了另一个函数(这里是类Class)中。
  2. 你可以通过类/函数名直接访问方法(method())。Class.method();
  3. 无需创建任何对象/实例new Class())来访问方法(method())。因此,你可以将其称为静态方法

B. 原型方法(在所有实例之间共享):

     Class.prototype.method = function () { /* code using this.values */ }
  1. method() 在这里是一个函数属性,添加到另一个函数原型中(这里是 Class.prototype)。
  2. 您可以通过类名对象/实例new Class()直接访问它。
  3. 附加优势-使用这种方法()定义方式将在内存中创建仅一个副本的方法(),并且将在从Class创建的所有对象/实例之间共享

C. 类方法(每个实例都有自己的副本):

   function Class () {
      this.method = function () { /* do something with the private members */};
   }
  1. method() 是在另一个函数(这里是类)内部定义的方法。
  2. 无法通过类/函数名称直接访问 method()。 Class.method();
  3. 您需要创建一个对象/实例(new Class())来访问 method()。
  4. 这种 method() 定义方式将为使用构造函数(new Class())创建的每个对象创建一个唯一副本的方法()
  5. 附加优点-由于 method() 的作用域,它完全有权访问在构造函数(这里是Class)中声明的局部成员(也称为私有成员)。

示例:

    function Class() {
        var str = "Constructor method"; // private variable
        this.method = function () { console.log(str); };
    }
    Class.prototype.method = function() { console.log("Prototype method"); };
    Class.method = function() { console.log("Static method"); };

    new Class().method();     // Constructor method
    // Bcos Constructor method() has more priority over the Prototype method()

    // Bcos of the existence of the Constructor method(), the Prototype method 
    // will not be looked up. But you call it by explicity, if you want.
    // Using instance
    new Class().constructor.prototype.method(); // Prototype method

    // Using class name
    Class.prototype.method(); // Prototype method

    // Access the static method by class name
    Class.method();           // Static method

A. "不需要创建任何对象/实例(new Class())来访问方法(method())。因此,您可以将其称为静态方法。" 是否可以从实例中访问静态方法?如果可以,如何访问?最好消除这种不确定性。B. "您可以通过类名直接访问,也可以通过对象/实例(new Class())访问。" 我认为添加一个通过类名访问的示例(Class.prototype.method())会很有帮助,以澄清这一点。这最初使我感到困惑,因为我知道Class.method()在原型方法中不起作用。你的回答真的帮助了我的理解,非常感谢。 - Peter Out
这个答案与@Aminu Kano的答案在什么是类方法的定义上存在矛盾...无论如何,我认为“类方法”本身就是一个非常糟糕的名称,因为它引起了很多混淆。 - kakacii

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接