【问题标题】:Class vs. static method in JavaScriptJavaScript 中的类与静态方法
【发布时间】:2011-12-03 09:59:23
【问题描述】:

我知道这会奏效:

function Foo() {};
Foo.prototype.talk = function () {
    alert('hello~\n');
};

var a = new Foo;
a.talk(); // 'hello~\n'

但是如果我想打电话

Foo.talk() // this will not work
Foo.prototype.talk() // this works correctly

我找到了一些让Foo.talk工作的方法,

  1. Foo.__proto__ = Foo.prototype
  2. Foo.talk = Foo.prototype.talk

还有其他方法可以做到这一点吗?我不知道这样做是否正确。您在 JavaScript 代码中使用类方法还是静态方法?

【问题讨论】:

  • Foo.talk = function ...
  • @downvoterstepintothelight Foo.walk = function() {} 不会影响它的实例,因为它不在原型链上。是否有跨浏览器使函数的[[prototype]] 指向其prototype 的方法?
  • 可能我不知道你想要什么,因为根据定义,类方法不会影响实例
  • @downvoterstepintothelight 我怀疑method,在像python这样的语言中,一个实例能够调用它的类方法,区别是this指针。

标签: javascript oop


【解决方案1】:

首先,请记住 JavaScript 主要是 prototypal language,而不是 class-based language1Foo 不是一个类,它是一个函数,它是一个对象。您可以使用new 关键字该函数实例化一个对象,这将允许您创建类似于标准 OOP 语言中的类的东西。

我建议大多数时候忽略__proto__,因为它对跨浏览器的支持很差,而是专注于了解prototype 的工作原理。

如果你有一个从函数2创建的对象的实例,并且你以任何方式访问它的成员之一(方法、属性、属性、常量等),访问将顺着原型层次结构,直到它 (a) 找到成员,或者 (b) 没有找到另一个原型。

层次结构从被调用的对象开始,然后搜索其原型对象。如果原型对象有原型,则重复,如果不存在原型,则返回undefined

例如:

foo = {bar: 'baz'};
console.log(foo.bar); // logs "baz"

foo = {};
console.log(foo.bar); // logs undefined

function Foo(){}
Foo.prototype = {bar: 'baz'};
f = new Foo();
console.log(f.bar);
// logs "baz" because the object f doesn't have an attribute "bar"
// so it checks the prototype
f.bar = 'buzz';
console.log( f.bar ); // logs "buzz" because f has an attribute "bar" set

在我看来,您至少已经对这些“基本”部分有所了解,但我需要明确说明。

在 JavaScript 中,一切都是对象3

一切都是一个对象。

function Foo(){} 不仅定义了一个新函数,它还定义了一个可以使用Foo 访问的新函数对象。

这就是为什么您可以使用Foo.prototype 访问Foo 的原型。

您还可以在Foo 上设置更多功能

Foo.talk = function () {
  alert('hello world!');
};

这个新功能可以使用:

Foo.talk();

我希望您现在已经注意到函数对象上的函数和静态方法之间的相似之处。

f = new Foo(); 视为创建一个类实例,将Foo.prototype.bar = function(){...} 视为为该类定义一个共享方法,而将Foo.baz = function(){...} 视为为该类定义一个公共静态方法。


ECMAScript 2015 为此类声明引入了多种语法糖,以使它们更易于实现,同时也更易于阅读。因此前面的例子可以写成:

class Foo {
  bar() {...}

  static baz() {...}
}

允许bar 被称为:

const f = new Foo()
f.bar()

baz 被称为:

Foo.baz()

1:class was a "Future Reserved Word" in the ECMAScript 5 specification,但 ES6 引入了使用 class 关键字定义类的能力。

2:本质上是由构造函数创建的类实例,但有很多细微差别我不想误导你

3:primitive values——包括undefinednull、布尔值、数字和字符串——在技术上不是对象,因为它们是低级语言实现。布尔值、数字和字符串仍然与原型链交互,就好像它们是对象一样,因此出于本答案的目的,即使它们不完全是,也更容易将它们视为“对象”。

【讨论】:

  • @lostyzd - 好吧,他们可以通过Foo.talk() 访问它。如果需要,您可以在构造函数中分配它:this.talk = Foo.talk - 或者,正如您所注意到的,通过分配 Foo.prototype.talk = Foo.talk。但我不确定这是一个好主意 - 原则上,实例方法应该特定于实例。
  • @Doug Avery, Foo.talk() 只是在调用一个命名空间函数。您可以在类似于在 Java/C# 等 OOP 语言中调用静态方法的情况下使用它。一个很好的用例示例是像 Array.isArray() 这样的函数。
  • P.S. null 是对象类型of null == 'object'
  • 您都缺少的基本点是静态方法被继承。 Foo.talk = function ()... 不适用于拥有自己的类名的子类。这可以通过“扩展”子类来解决,但我仍在寻找更优雅的方式。
  • @nus,只有某些语言允许继承静态方法。如果需要继承,你不应该一开始就使用静态方法。
【解决方案2】:

您可以如下实现:

function Foo() {};

Foo.talk = function() { alert('I am talking.'); };

您现在可以调用“谈话”功能如下:

Foo.talk();

您可以这样做,因为在 JavaScript 中,函数也是对象。

【讨论】:

    【解决方案3】:

    从实例调用静态方法:

    function Clazz() {};
    Clazz.staticMethod = function() {
        alert('STATIC!!!');
    };
    
    Clazz.prototype.func = function() {
        this.constructor.staticMethod();
    }
    
    var obj = new Clazz();
    obj.func(); // <- Alert's "STATIC!!!"
    

    简单的 Javascript 类项目:https://github.com/reduardo7/sjsClass

    【讨论】:

    • 这不是静态调用。 var obj = new Clazz();创建一个新的 Clazz 实例。然而,Clazz.staticMethod() 在没有其他所有东西的情况下实现了结果。
    • @mpemburn:Eduardo 的回答也是正确的。他向您展示的不仅是您可以通过Clazz.staticMethod 从“外部”调用静态方法,而且还向您展示了如何从实例化对象中链接到这些静态方法。这在像 Node.js 这样的环境中特别有用,在这些环境中,使用 require,您可能无法直接访问原始构造函数。我唯一要补充的是this.constructor.staticMethod.apply(this, arguments);
    • 太棒了,甚至可以在咖啡脚本构造函数中工作:constructor: (a) -&gt; @constructor.add @(差不多,反正)
    【解决方案4】:

    这是一个很好的例子来演示 Javascript 如何与静态/实例变量和方法一起工作。

    function Animal(name) {
        Animal.count = Animal.count+1||1;// static variables, use function name "Animal"
        this.name = name; //instance variable, using "this"
    }
    
    Animal.showCount = function () {//static method
        alert(Animal.count)
    }
    
    Animal.prototype.showName=function(){//instance method
        alert(this.name);
    }
    
    var mouse = new Animal("Mickey");
    var elephant = new Animal("Haddoop");
    
    Animal.showCount();  // static method, count=2
    mouse.showName();//instance method, alert "Mickey"
    mouse.showCount();//Error!! mouse.showCount is not a function, which is different from  Java
    

    【讨论】:

    • 好点:不能通过this访问静态函数可能很奇怪。
    • 感谢您的解决方案,这是我正在寻找的解决方案,在哪种情况下可以访问 this 关键字
    【解决方案5】:

    此外,现在可以使用classstatic

    'use strict'
    
    class Foo {
     static talk() {
         console.log('talk')
     };
    
     speak() {
         console.log('speak')
     };
    
    };
    

    会给

    var a = new Foo();
    Foo.talk();  // 'talk'
    a.talk();    // err 'is not a function'
    a.speak();   // 'speak'
    Foo.speak(); // err 'is not a function'
    

    【讨论】:

    • 这是最好的答案,因为例子值一千字。但是,它没有解释为什么 a.talk() 不起作用。公认的答案说原型链应该找到它,对吗?但事实并非如此
    【解决方案6】:

    我使用命名空间:

    var Foo = {
         element: document.getElementById("id-here"),
    
         Talk: function(message) {
                alert("talking..." + message);
         },
    
         ChangeElement: function() {
                this.element.style.color = "red";
         }
    };
    

    并使用它:

    Foo.Talk("Testing");
    

    或者

    Foo.ChangeElement();
    

    【讨论】:

      【解决方案7】:

      ES6 现在支持 classstatic 关键字就像一个魅力:

      class Foo {
          constructor() {}
      
          talk() {
              console.log("i am not static");
          }
      
          static saying() {
              console.log(this.speech);
          }
      
          static get speech() {
              return "i am static method";
          }
      
      }
      

      【讨论】:

      • 我正在寻找这样的答案。静态方法可以调用非静态方法/变量吗?
      • @Tomasz 静态方法不会将“this”设置为类的任何实例,而是设置类本身。所以当然,静态方法可以调用实例方法,但前提是它以某种方式可以访问实例,例如 'static staticMethod() { new Foo().talk(); }´
      【解决方案8】:

      如果你必须在 ES5 中编写静态方法,我找到了一个很棒的教程:

      //Constructor
      var Person = function (name, age){
      //private properties
      var priv = {};
      
      //Public properties
      this.name = name;
      this.age = age;
      
      //Public methods
      this.sayHi = function(){
          alert('hello');
      }
      }
      
      
      // A static method; this method only 
      // exists on the class and doesn't exist  
      // on child objects
      Person.sayName = function() {
         alert("I am a Person object ;)");  
      };
      

      见@https://abdulapopoola.com/2013/03/30/static-and-instance-methods-in-javascript/

      【讨论】:

        【解决方案9】:

        只是附加说明。使用 ES6 类,当我们创建静态方法时..Javacsript 引擎将描述符属性设置为与老式的“静态”方法有点不同

        function Car() {
        
        }
        
        Car.brand = function() {
          console.log('Honda');
        }
        
        console.log(
          Object.getOwnPropertyDescriptors(Car)
        );
        

        它将brand()的内部属性(描述符属性)设置为

        ..
        brand: [object Object] {
            configurable: true,
            enumerable: true,
            value: ..
            writable: true
        
        }
        ..
        

        相比

        class Car2 {
           static brand() {
             console.log('Honda');
           }
        }
        
        console.log(
          Object.getOwnPropertyDescriptors(Car2)
        );
        

        将brand()的内部属性设置为

        ..
        brand: [object Object] {
            configurable: true,
            enumerable: false,
            value:..
            writable: true
          }
        
        ..
        

        看到 enumerable 设置为 false 用于 ES6 中的静态方法。

        这意味着你不能使用for-in循环来检查对象

        for (let prop in Car) {
          console.log(prop); // brand
        }
        
        for (let prop in Car2) {
          console.log(prop); // nothing here
        }
        

        ES6 中的静态方法被视为其他类的私有属性(名称、长度、构造函数),只是静态方法仍然是可写的,因此描述符 writable 设置为 true { writable: true }。这也意味着我们可以覆盖它

        Car2.brand = function() {
           console.log('Toyota');
        };
        
        console.log(
          Car2.brand() // is now changed to toyota
        );
        

        【讨论】:

          【解决方案10】:

          当您尝试调用Foo.talk 时,JS 会尝试搜索函数talk__proto__,当然,找不到。

          Foo.__proto__Function.prototype

          【讨论】:

            【解决方案11】:

            静态方法调用直接在类上进行,不能在类的实例上调用。静态方法通常用于 创建效用函数

            很清楚的描述

            Taken Directly from mozilla.org

            Foo 需要绑定到你的类 然后,当您创建一个新实例时,您可以调用 myNewInstance.foo() 如果你导入你的类,你可以调用一个静态方法

            【讨论】:

              【解决方案12】:

              当我遇到这种情况时,我做了这样的事情:

              Logger = {
                  info: function (message, tag) {
                      var fullMessage = '';        
                      fullMessage = this._getFormatedMessage(message, tag);
                      if (loggerEnabled) {
                          console.log(fullMessage);
                      }
                  },
                  warning: function (message, tag) {
                      var fullMessage = '';
                      fullMessage = this._getFormatedMessage(message, tag);
                      if (loggerEnabled) {
                          console.warn(fullMessage);`enter code here`
                      }
                  },
                  _getFormatedMessage: function () {}
              };
              

              所以现在我可以将 info 方法称为 Logger.info("my Msg", "Tag");

              【讨论】:

              • 我一直这样做,但它基本上只是命名空间。它不允许您使用实例变量创建实例?
              【解决方案13】:

              在你的情况下,如果你想Foo.talk()

              function Foo() {};
              // But use Foo.talk would be inefficient
              Foo.talk = function () {
                  alert('hello~\n');
              };
              
              Foo.talk(); // 'hello~\n'
              

              但这是一种低效的实现方式,使用prototype 更好。


              另一种方式,我的方式定义为静态类:

              var Foo = new function() {
                this.talk = function () {
                  alert('hello~\n');
                  };
              };
              
              Foo.talk(); // 'hello~\n'
              

              上面的静态类不需要使用prototype,因为它只会被构造一次作为静态使用。

              https://github.com/yidas/js-design-patterns/tree/master/class

              【讨论】:

              • @jvitoroc 谢谢!
              【解决方案14】:

              Javascript 没有实际的类,而是使用原型继承系统,其中对象通过其原型链从其他对象“继承”。这最好通过代码本身来解释:

              function Foo() {};
              // creates a new function object
              
              Foo.prototype.talk = function () {
                  console.log('hello~\n');
              };
              // put a new function (object) on the prototype (object) of the Foo function object
              
              var a = new Foo;
              // When foo is created using the new keyword it automatically has a reference 
              // to the prototype property of the Foo function
              
              // We can show this with the following code
              console.log(Object.getPrototypeOf(a) === Foo.prototype); 
              
              a.talk(); // 'hello~\n'
              // When the talk method is invoked it will first look on the object a for the talk method,
              // when this is not present it will look on the prototype of a (i.e. Foo.prototype)
              
              // When you want to call
              // Foo.talk();
              // this will not work because you haven't put the talk() property on the Foo
              // function object. Rather it is located on the prototype property of Foo.
              
              // We could make it work like this:
              Foo.sayhi = function () {
                  console.log('hello there');
              };
              
              Foo.sayhi();
              // This works now. However it will not be present on the prototype chain 
              // of objects we create out of Foo

              【讨论】:

                【解决方案15】:

                在函数或类对象以及它们的实例上实现方法和属性的树形方式。

                1. 在类(或函数)本身:Foo.method()Foo.prop。这些是静态方法或属性
                2. 在它的原型上Foo.prototype.method()Foo.prototype.prop。创建后,实例将通过原型女巫继承这些对象{method:function(){...}, prop:...}。因此,foo 对象将接收 Foo.prototype 对象的副本作为原型。
                3. 在实例本身:方法或属性被添加到对象本身。 foo={method:function(){...}, prop:...}

                this 关键字将根据上下文来表示和执行不同的操作。在静态方法中,它将代表类本身(witch 毕竟是 Function 的一个实例:class Foo {} 完全等同于 let Foo = new Function({})

                使用 ECMAScript 2015,今天似乎实现得很好,可以更清楚地看到类(静态)方法和属性、实例方法和属性以及自己的方法和属性之间的区别。因此,您可以创建三个具有相同名称但不同的方法或属性,因为它们适用于不同的对象,方法中的this 关键字将分别适用于类对象本身和实例对象,由原型或它自己的。

                class Foo {
                  constructor(){super();}
                  
                  static prop = "I am static" // see 1.
                  static method(str) {alert("static method"+str+" :"+this.prop)} // see 1.
                  
                  prop="I am of an instance"; // see 2.
                  method(str) {alert("instance method"+str+" : "+this.prop)} // see 2.
                }
                
                var foo= new Foo();
                foo.prop = "I am of own";  // see 3.
                foo.func = function(str){alert("own method" + str + this.prop)} // see 3.
                

                【讨论】:

                  猜你喜欢
                  • 2021-06-04
                  • 2011-01-17
                  • 1970-01-01
                  • 2011-11-10
                  • 1970-01-01
                  • 2012-04-19
                  • 1970-01-01
                  • 2017-06-22
                  相关资源
                  最近更新 更多