Làm cách nào để viết một phương thức mở rộng trong JavaScript?


83

Tôi cần viết một vài phương thức mở rộng trong JS. Tôi chỉ biết cách thực hiện việc này trong C #. Thí dụ:

public static string SayHi(this Object name)
{
    return "Hi " + name + "!";
}

và sau đó được gọi bằng:

string firstName = "Bob";
string hi = firstName.SayHi();

Làm cách nào để làm điều gì đó như thế này trong JavaScript?

Câu trả lời:


149

JavaScript không có một tương tự chính xác cho các phương thức mở rộng của C #. JavaScript và C # là những ngôn ngữ khá khác nhau.

Điều tương tự gần nhất là cải tiến đối tượng nguyên mẫu của tất cả các đối tượng chuỗi: String.prototype. Nói chung, cách tốt nhất là không sửa đổi nguyên mẫu của các đối tượng tích hợp sẵn trong mã thư viện được kết hợp với mã khác mà bạn không kiểm soát. (Làm điều đó trong một ứng dụng mà bạn kiểm soát mã nào khác được đưa vào ứng dụng là được.)

Nếu bạn thực sự sửa đổi nguyên mẫu của một bản cài sẵn, thì tốt nhất (cho đến nay) hãy biến nó thành một thuộc tính không liệt kê bằng cách sử dụng Object.defineProperty(ES5 +, về cơ bản là bất kỳ môi trường JavaScript hiện đại nào chứ không phải IE8¹ hoặc phiên bản cũ hơn). Để phù hợp với khả năng liệt kê, khả năng ghi và khả năng cấu hình của các phương thức chuỗi khác, nó sẽ giống như sau:

Object.defineProperty(String.prototype, "SayHi", {
    value: function SayHi() {
        return "Hi " + this + "!";
    },
    writable: true,
    configurable: true
});

(Giá trị mặc định enumerablefalse.)

Nếu bạn cần hỗ trợ các môi trường lỗi thời, thì String.prototypeđặc biệt, bạn có thể bỏ qua việc tạo thuộc tính enumerable:

// Don't do this if you can use `Object.defineProperty`
String.prototype.SayHi = function SayHi() {
    return "Hi " + this + "!";
};

Đó không phải là một ý kiến ​​hay, nhưng bạn có thể bỏ qua nó. Không bao giờ làm điều đó với Array.prototypehoặc Object.prototype; tạo ra các thuộc tính có thể liệt kê trên đó là một Bad Thing ™.

Chi tiết:

JavaScript là một ngôn ngữ nguyên mẫu. Điều đó có nghĩa là mọi đối tượng đều được hỗ trợ bởi một đối tượng nguyên mẫu . Trong JavaScript, nguyên mẫu đó được chỉ định theo một trong bốn cách:

  • Bằng hàm khởi tạo cho đối tượng (ví dụ: new Footạo một đối tượng Foo.prototypelàm nguyên mẫu của nó)
  • Bởi Object.createchức năng được thêm vào trong ES5 (2009)
  • Bởi thuộc __proto__tính trình truy cập (ES2015 +, chỉ trên trình duyệt web, tồn tại trong một số môi trường trước khi nó được chuẩn hóa) hoặc Object.setPrototypeOf(ES2015 +)
  • Bằng công cụ JavaScript khi tạo một đối tượng cho nguyên thủy vì bạn đang gọi một phương thức trên đó (điều này đôi khi được gọi là "quảng cáo")

Vì vậy, trong ví dụ của bạn, vì firstNamelà một chuỗi nguyên thủy, nó được thăng cấp thành một Stringcá thể bất cứ khi nào bạn gọi một phương thức trên đó và Stringnguyên mẫu của cá thể đó là như vậy String.prototype. Vì vậy, việc thêm một thuộc tính String.prototypetham chiếu đến SayHihàm của bạn sẽ làm cho hàm đó khả dụng trên tất cả các Stringtrường hợp (và hiệu quả trên các nguyên thủy của chuỗi, vì chúng được thăng cấp).

Thí dụ:

Có một số khác biệt chính giữa phương thức này và phương thức mở rộng C #:

  • (Như DougR đã chỉ ra trong một bình luận) Các phương thức mở rộng của C # có thể được gọi trên các nulltham chiếu . Nếu bạn có một stringphương thức mở rộng, mã này:

    string s = null;
    s.YourExtensionMethod();
    

    làm. Điều đó không đúng với JavaScript; nulllà kiểu riêng của nó và bất kỳ tham chiếu thuộc tính nào trên đều nulltạo ra lỗi. (Và ngay cả khi nó không, không có nguyên mẫu nào để mở rộng cho kiểu Null.)

  • (Như ChrisW đã chỉ ra trong một bình luận) Các phương thức mở rộng của C # không mang tính toàn cầu. Chúng chỉ có thể truy cập nếu không gian tên mà chúng được xác định được mã sử dụng bằng phương thức mở rộng. (Chúng thực sự là đường cú pháp cho các cuộc gọi tĩnh, đó là lý do tại sao chúng hoạt động trên đó null.) Điều đó không đúng trong JavaScript: Nếu bạn thay đổi nguyên mẫu của một cài sẵn, thay đổi đó sẽ được nhìn thấy bởi tất cả mã trong toàn bộ lĩnh vực của bạn. làm điều đó trong (một lĩnh vực là môi trường toàn cầu và các đối tượng nội tại liên quan của nó, v.v.). Vì vậy, nếu bạn làm điều này trong một trang web, tất cả mã bạn tải trên trang đó sẽ thấy sự thay đổi. Nếu bạn làm điều này trong mô-đun Node.js, tất cảmã được tải trong cùng lĩnh vực với mô-đun đó sẽ thấy sự thay đổi. Trong cả hai trường hợp, đó là lý do tại sao bạn không làm điều này trong mã thư viện. (Các luồng nhân viên web và công nhân Node.js được tải trong lĩnh vực riêng của chúng, vì vậy chúng có môi trường toàn cầu khác và bản chất khác với chủ đề chính. Nhưng lĩnh vực đó vẫn được chia sẻ với bất kỳ mô-đun nào mà chúng tải.)


¹ IE8 có Object.defineProperty, nhưng nó chỉ hoạt động trên các đối tượng DOM, không phải các đối tượng JavaScript. String.prototypelà một đối tượng JavaScript.


@DougR: Xin lỗi, dọn dẹp bình luận tiêu chuẩn. Khi một nhận xét trở nên lỗi thời, các mod hoặc người dùng thành thạo có thể xóa nó (mod có thể thực hiện trực tiếp, người dùng thành thạo phải lập nhóm trong hàng đợi đánh giá). Tôi đã cập nhật câu trả lời để chỉ ra rằng bạn đã gắn cờ điều này. :-)
TJ Crowder

1
@Grundy: Cảm ơn, vâng, toàn bộ điểm của String s = null;phần này là sử dụng s.YourExtensionMethod()! Đánh giá cao việc đánh bắt. :-)
TJ Crowder

Cảm ơn bạn đã làm nổi bật rằng phương thức mở rộng không hoạt động đối với các thực thể rỗng.
Ram

1
Nếu bạn làm điều này trong Node.js chẳng hạn, tôi đoán điều đó sẽ ảnh hưởng (thêm thuộc tính mới vào) mọi chuỗi trong chương trình - bao gồm cả các mô-đun khác? Hai mô-đun có xung đột không nếu cả hai đều xác định thuộc tính "SayHi"?
ChrisW,

1
@ChrisW - Khá. :-) Những gì bạn có thể làm thay vào đó là tạo một lớp con Array ( class MyArray extends Array { singleOrDefault() { ... }}) nhưng điều đó có nghĩa là bạn phải làm MyArray.from(originalArray)trước khi sử dụng nó nếu originalArraylà một mảng cũ nhàm chán. Ngoài ra còn có các thư viện giống LINQ cho JavaScript ( đây là một bản tổng hợp ), tương tự liên quan đến việc tạo một đối tượng Linq từ mảng trước khi thực hiện mọi việc (tốt, LINQ sang JavaScript đã làm, tôi chưa sử dụng cái khác [và chưa sử dụng cái đó trong những năm]). (BTW, đã cập nhật câu trả lời, thx.)
TJ Crowder

0

Sử dụng tăng cường toàn cầu

declare global {
    interface DOMRect {
        contains(x:number,y:number): boolean;
    }
}
/* Adds contain method to class DOMRect */
DOMRect.prototype.contains = function (x:number, y:number) {                    
    if (x >= this.left && x <= this.right &&
        y >= this.top && y <= this.bottom) {
        return true
    }
    return false
};

điều này có hoạt động với javascript thuần túy / vani hay nó là thứ mà tôi phải tìm ra cách sử dụng typecript cho? Tôi đang hỏi vì bạn liên kết đến stylescriptlang.org chứ không nói tài liệu ES6 hay MDN
user1306322
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.