วิธีการเรียนกับสแตติกใน JavaScript


262

ฉันรู้ว่ามันใช้งานได้:

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 ของคุณ?



1
@downvoterstepintothelight Foo.walk = function() {}จะไม่มีผลกับอินสแตนซ์ของมันเนื่องจากไม่ได้อยู่ในเชนต้นแบบ มีวิธีการข้ามเบราว์เซอร์ที่จะทำให้ฟังก์ชั่น[[prototype]]ชี้ไปที่มันprototypeหรือไม่?
lostyzd

3
อาจฉันไม่มีความคิดสิ่งที่คุณต้องการเพราะวิธีการเรียนไม่ส่งผลกระทบต่อกรณีตามคำนิยาม
เพิ่มประสิทธิภาพก่อนกำหนด

@downvoterstepintothelight ฉันสงสัยว่าวิธีการในภาษาเช่นหลามอินสแตนซ์สามารถเรียกวิธีการเรียนของมันความแตกต่างคือthisตัวชี้
lostyzd

คำตอบ:


410

ปิดครั้งแรกจำได้ว่า JavaScript เป็นหลักภาษา prototypalมากกว่าภาษาระดับตาม 1 Fooไม่ใช่คลาส แต่เป็นฟังก์ชันซึ่งเป็นวัตถุ คุณสามารถยกตัวอย่างวัตถุจากฟังก์ชั่นนั้นโดยใช้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's Foo.prototypeต้นแบบ

สิ่งที่คุณสามารถทำได้คือตั้งค่าฟังก์ชั่นเพิ่มเติมใน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เป็น "คำสงวนในอนาคต" ในข้อกำหนด ECMAScript 5แต่ ES6 แนะนำความสามารถในการกำหนดคลาสโดยใช้classคำหลัก

2: โดยพื้นฐานแล้วคลาสอินสแตนซ์ที่สร้างโดยนวกรรมิก แต่มีความแตกต่างที่เหมาะสมหลายอย่างที่ฉันไม่ต้องการให้คุณเข้าใจผิด

3: ค่าดั้งเดิม - ซึ่งรวมถึงundefined, nullบูลีน, ตัวเลขและสตริง - ไม่ใช่วัตถุในทางเทคนิคเพราะเป็นภาษาที่ใช้ในระดับต่ำ บูลีน, ตัวเลขและสายอักขระยังคงมีปฏิสัมพันธ์กับห่วงโซ่ต้นแบบราวกับว่าพวกมันเป็นวัตถุดังนั้นสำหรับจุดประสงค์ของคำตอบนี้มันง่ายกว่าที่จะพิจารณาว่าพวกเขา "วัตถุ" แม้ว่าพวกเขาจะไม่ได้ค่อนข้าง


1
@lostyzd - Foo.talk()ดีที่พวกเขาสามารถเข้าถึงได้ผ่านทาง คุณสามารถกำหนดว่าในการสร้างถ้าคุณต้องการ: this.talk = Foo.talk- Foo.prototype.talk = Foo.talkหรือตามที่คุณทราบโดยการกำหนด แต่ฉันไม่แน่ใจว่านี่เป็นความคิดที่ดี - ตามหลักการแล้ววิธีการอินสแตนซ์ควรมีความเฉพาะเจาะจงกับอินสแตนซ์
nrabinowitz

2
@Doug Avery Foo.talk()เป็นเพียงการเรียกฟังก์ชั่น namespaced คุณจะใช้มันในสถานการณ์ที่คล้ายคลึงกับวิธีการคงที่ที่เรียกว่าในภาษา OOP เช่น Java / C # Array.isArray()ตัวอย่างที่ดีของกรณีการใช้งานจะเป็นฟังก์ชั่นเช่น
zzzzBov

7
PS null เป็นประเภทวัตถุของ null == 'object'
mvladk

1
จุดพื้นฐานที่คุณขาดหายไปก็คือวิธีการคงที่ได้รับการสืบทอด Foo.talk = function ()...จะไม่สามารถใช้ได้กับคลาสย่อยในชื่อคลาสของตัวเอง สิ่งนี้สามารถแก้ไขได้ด้วยคลาสย่อย "ขยาย" แต่ฉันยังคงมองหาวิธีที่หรูหรากว่า

1
@nus มีเพียงบางภาษาเท่านั้นที่อนุญาตให้ใช้เมธอดแบบสแตติก หากต้องการสืบทอดคุณไม่ควรใช้วิธีการเริ่มต้น
zzzzBov

67

คุณสามารถบรรลุดังต่อไปนี้:

function Foo() {};

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

ตอนนี้คุณสามารถเรียกใช้ฟังก์ชั่น "พูดคุย" ได้ดังนี้:

Foo.talk();

คุณสามารถทำเช่นนี้เพราะใน JavaScript ฟังก์ชั่นเป็นวัตถุเช่นกัน


37

เรียกวิธีการคงที่จากอินสแตนซ์:

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


13
นี่ไม่ใช่การโทรคงที่ var obj = Clazz ใหม่ (); สร้างใหม่อินสแตนซ์ของ Clazz อย่างไรก็ตาม Clazz.staticMethod () รับผลลัพธ์โดยไม่มีสิ่งอื่นทั้งหมด
mpemburn

5
@mpemburn: Eduardo ถูกต้องในคำตอบของเขาเช่นกัน สิ่งที่เขาแสดงให้คุณไม่เพียง แต่คุณสามารถเรียกวิธีการคงที่จาก "นอก" ผ่านทางClazz.staticMethodแต่เขาแสดงวิธีการเชื่อมโยงไปยังวิธีการคงที่เหล่านี้จากภายในวัตถุที่ยกตัวอย่าง สิ่งนี้มีประโยชน์โดยเฉพาะอย่างยิ่งในสภาพแวดล้อมเช่น Node.js โดยที่คุณจำเป็นต้องใช้คุณอาจไม่สามารถเข้าถึงตัวสร้างดั้งเดิมได้โดยตรง สิ่งเดียวที่ฉันจะเพิ่มคือthis.constructor.staticMethod.apply(this, arguments);
Mauvis Ledford

1
ยอดเยี่ยมอย่างแน่นอนแม้ทำงานในตัวสร้างสคริปต์กาแฟ: constructor: (a) -> @constructor.add @(เกือบแล้ว)
Orwellophile

31

นี่เป็นตัวอย่างที่ดีในการแสดงให้เห็นว่า 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นี้อาจจะแปลกไม่ให้มีการเข้าถึงฟังก์ชั่นแบบคงที่ผ่าน
TrapII

ขอขอบคุณสำหรับการแก้ปัญหานี้คือสิ่งที่ฉันถูกมองในสถานการณ์ที่จะมีการเข้าถึงของthisคำหลัก
Santhosh

30

นอกจากนี้ตอนนี้มันเป็นไปได้ที่จะทำกับclassและstatic

'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()ไม่ทำงาน คำตอบที่ได้รับการยอมรับบอกว่าโซ่ของต้นแบบควรจะหาใช่ไหม? แต่ไม่ใช่กรณี
Pynchia

11

ฉันใช้เนมสเปซ:

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();

6

ES6 รองรับตอนนี้classและstaticคำหลักเหมือนเครื่องราง:

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 Mularczyk

1
@Tomasz วิธีการคงที่จะไม่ได้ตั้งค่านี้เป็นตัวอย่างของชั้นเรียน แต่ค่อนข้างชั้นเรียนของตัวเอง แน่นอนว่าวิธีการแบบสแตติกสามารถเรียกใช้วิธีการแบบอินสแตนซ์ แต่ถ้ามันมีการเข้าถึงอินสแตนซ์เช่น´staticMemoryMethod () {new Foo (). talk (); } '
JHH

3

หากคุณต้องเขียนวิธีการคงที่ใน 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/


2

เพียงบันทึกเพิ่มเติม การใช้คลาส ES6 เมื่อเราสร้างวิธีการคงที่ .. เครื่องยนต์ Javacsript ตั้งค่าแอตทริบิวต์ตัวอธิบายที่แตกต่างจากวิธีการ "คงที่" ของโรงเรียนเก่า

function Car() {

}

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

console.log(
  Object.getOwnPropertyDescriptors(Car)
);

มันตั้งค่าคุณสมบัติภายใน (คุณสมบัติคำอธิบาย) สำหรับแบรนด์ () ถึง

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

}
..

เปรียบเทียบกับ

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

console.log(
  Object.getOwnPropertyDescriptors(Car2)
);

ที่ตั้งค่าคุณลักษณะภายในสำหรับแบรนด์ () เป็น

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

..

ดูว่านับได้ถูกตั้งค่าเป็นเท็จสำหรับวิธีการคงที่ใน ES6

หมายความว่าคุณไม่สามารถใช้ for-in loop เพื่อตรวจสอบวัตถุ

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

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

วิธีการแบบคงที่ใน ES6 ได้รับการปฏิบัติเช่นเดียวกับคนอื่น ๆ ของโรงแรมระดับส่วนตัว (ชื่อ, ความยาว, คอนสตรัค) ยกเว้นวิธีการแบบคงที่ยังคงเขียนได้จึงบ่งเขียนได้มีการตั้งค่าที่แท้จริง { writable: true }มันยังหมายความว่าเราสามารถแทนที่มันได้

Car2.brand = function() {
   console.log('Toyota');
};

console.log(
  Car2.brand() // is now changed to toyota
);

1

เมื่อคุณพยายามที่จะเรียกร้องFoo.talkที่ JS พยายามที่จะค้นหาฟังก์ชั่นtalkผ่าน__proto__และแน่นอนมันไม่สามารถพบได้

Foo.__proto__Function.prototypeเป็น


1

การเรียกเมธอดแบบสแตติกทำบนคลาสโดยตรงและไม่สามารถเรียกใช้บนอินสแตนซ์ของคลาสได้ วิธีการแบบคงที่มักจะใช้เพื่อสร้างฟังก์ชั่นยูทิลิตี้

คำอธิบายที่ชัดเจนสวย

ถ่ายโดยตรงจาก mozilla.org

Foo ต้องผูกกับคลาสของคุณจากนั้นเมื่อคุณสร้างอินสแตนซ์ใหม่คุณสามารถโทร myNewInstance.foo () ถ้าคุณนำเข้าคลาสคุณสามารถเรียกเมธอดแบบสแตติกได้


0

เมื่อฉันเผชิญกับสถานการณ์เช่นนี้ฉันได้ทำสิ่งนี้:

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 () {}
};

ดังนั้นตอนนี้ฉันสามารถเรียกวิธีการข้อมูลเป็น Logger.info("my Msg", "Tag");


ฉันทำสิ่งนี้ตลอดเวลา แต่โดยทั่วไปมันเป็นเพียงการกำหนดชื่อ ไม่อนุญาตให้คุณสร้างอินสแตนซ์ที่มี vars อินสแตนซ์ใช่หรือไม่
dcsan

0

ในกรณีของคุณหากคุณต้องการ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 ขอบคุณ!
Nick Tsai

0

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.