Javascript: Mở rộng một chức năng


94

Lý do chính tại sao tôi muốn nó là tôi muốn mở rộng chức năng khởi tạo của mình.

Một cái gì đó như thế này:

// main.js

window.onload = init();
function init(){
     doSomething();
}

// extend.js

function extends init(){
    doSomethingHereToo();
}

Vì vậy, tôi muốn mở rộng một hàm giống như tôi mở rộng một lớp trong PHP.

Và tôi cũng muốn mở rộng nó từ các tệp khác, vì vậy, ví dụ như tôi có hàm init gốc trong main.jsvà hàm mở rộng trong extended.js.



Câu trả lời:


103

Với một cái nhìn rộng hơn về những gì bạn thực sự đang cố gắng làm và bối cảnh bạn đang làm điều đó, tôi chắc chắn rằng chúng tôi có thể cung cấp cho bạn câu trả lời tốt hơn câu trả lời theo nghĩa đen cho câu hỏi của bạn.

Nhưng đây là một câu trả lời theo nghĩa đen:

Nếu bạn đang gán các chức năng này cho một số thuộc tính ở đâu đó, bạn có thể bọc chức năng ban đầu và đặt chức năng thay thế của bạn vào thuộc tính đó:

// Original code in main.js
var theProperty = init;

function init(){
     doSomething();
}

// Extending it by replacing and wrapping, in extended.js
theProperty = (function(old) {
    function extendsInit() {
        old();
        doSomething();
    }

    return extendsInit;
})(theProperty);

Nếu các chức năng của bạn chưa có trên một đối tượng, bạn có thể muốn đặt chúng ở đó để tạo điều kiện thuận lợi cho việc trên. Ví dụ:

// In main.js
var MyLibrary = (function() {
    var publicSymbols = {};

    publicSymbols.init = init;
    function init() {
    }

    return publicSymbols;
})();

// In extended.js
(function() {
    var oldInit = MyLibrary.init;
    MyLibrary.init = extendedInit;
    function extendedInit() {
        oldInit.apply(MyLibrary); // Use #apply in case `init` uses `this`
        doSomething();
    }
})();

Nhưng có những cách tốt hơn để làm điều đó. Ví dụ như cung cấp phương tiện đăng ký các initchức năng.

// In main.js
var MyLibrary = (function() {
    var publicSymbols = {},
        initfunctions = [];

    publicSymbols.init = init;
    function init() {
        var funcs = initFunctions;

        initFunctions = undefined;

        for (index = 0; index < funcs.length; ++index) {
            try { funcs[index](); } catch (e) { }
        }
    }

    publicSymbols.addInitFunction = addInitFunction;
    function addInitFunction(f) {
        if (initFunctions) {
            // Init hasn't run yet, rememeber it
            initFunctions.push(f);
        }
        else {
            // `init` has already run, call it almost immediately
            // but *asynchronously* (so the caller never sees the
            // call synchronously)
            setTimeout(f, 0);
        }
    }

    return publicSymbols;
})();

(Phần lớn ở trên có thể được viết gọn hơn một chút, nhưng tôi muốn sử dụng những cái tên rõ ràng publicSymbolsthay vì pubstheo nghĩa đen hoặc đối tượng ẩn danh thông thường của tôi . Bạn có thể viết nó gọn gàng hơn rất nhiều nếu bạn muốn có các hàm ẩn danh, nhưng tôi không muốn ' không quan tâm nhiều đến các chức năng ẩn danh .)


Cảm ơn vì một câu trả lời tuyệt vời. Vấn đề của tôi với ví dụ thứ hai là tôi có thể cần kết quả từ hàm mà tôi đang mở rộng.
Gerhard Davids

64

Có một số cách để giải quyết vấn đề này, nó phụ thuộc vào mục đích của bạn là gì, nếu bạn chỉ muốn thực thi hàm và trong cùng ngữ cảnh, bạn có thể sử dụng .apply():

function init(){
  doSomething();
}
function myFunc(){
  init.apply(this, arguments);
  doSomethingHereToo();
}

Nếu bạn muốn thay thế nó bằng một cái mới hơn init, nó sẽ trông như thế này:

function init(){
  doSomething();
}
//anytime later
var old_init = init;
init = function() {
  old_init.apply(this, arguments);
  doSomethingHereToo();
};

2
Đôi khi bạn có thể muốn .callphương pháp thay vì .apply. Xem câu hỏi StackOverflow này .
MrDanA

@Nick, tôi thấy ví dụ JavaScript của bạn để mở rộng một hàm hiện có rất hữu ích, nhưng tôi tò mò muốn biết điều tương tự sẽ được thực hiện như thế nào thông qua jQuery?
Sunil

+1 Cảm ơn. Điều này thực sự tiện dụng nếu bạn muốn vá một số plugin của bên thứ 3 mà không cần sửa đổi js gốc.
GFoley83

1
Không chắc chắn cách sử dụng nó với các hàm mong đợi tham số và giá trị trả về.
Gerfried

5

Các phương pháp khác rất tuyệt nhưng chúng không bảo toàn bất kỳ hàm nguyên mẫu nào được gắn vào init. Để giải quyết vấn đề đó, bạn có thể làm như sau (lấy cảm hứng từ bài đăng của Nick Craver).

(function () {
    var old_prototype = init.prototype;
    var old_init = init;
    init = function () {
        old_init.apply(this, arguments);
        // Do something extra
    };
    init.prototype = old_prototype;
}) ();

5

Một tùy chọn khác có thể là:

var initial = function() {
    console.log( 'initial function!' );
}

var iWantToExecuteThisOneToo = function () {
    console.log( 'the other function that i wanted to execute!' );
}

function extendFunction( oldOne, newOne ) {
    return (function() {
        oldOne();
        newOne();
    })();
}

var extendedFunction = extendFunction( initial, iWantToExecuteThisOneToo );

0

Điều này rất đơn giản và thẳng về phía trước. Nhìn vào mã. Cố gắng nắm bắt khái niệm cơ bản đằng sau phần mở rộng javascript.

Đầu tiên, hãy để chúng tôi mở rộng hàm javascript.

function Base(props) {
    const _props = props
    this.getProps = () => _props

    // We can make method private by not binding it to this object. 
    // Hence it is not exposed when we return this.
    const privateMethod = () => "do internal stuff" 

    return this
}

Bạn có thể mở rộng hàm này bằng cách tạo hàm con theo cách sau

function Child(props) {
    const parent = Base(props)
    this.getMessage = () => `Message is ${parent.getProps()}`;

    // You can remove the line below to extend as in private inheritance, 
    // not exposing parent function properties and method.
    this.prototype = parent
    return this
}

Bây giờ bạn có thể sử dụng chức năng Con như sau,

let childObject = Child("Secret Message")
console.log(childObject.getMessage())     // logs "Message is Secret Message"
console.log(childObject.getProps())       // logs "Secret Message"

Chúng ta cũng có thể tạo Hàm Javascript bằng cách mở rộng các lớp Javascript, như thế này.

class BaseClass {
    constructor(props) {
        this.props = props
        // You can remove the line below to make getProps method private. 
        // As it will not be binded to this, but let it be
        this.getProps = this.getProps.bind(this)
    }

    getProps() {
        return this.props
    }
}

Hãy để chúng tôi mở rộng lớp này với hàm Con như thế này,

function Child(props) {
    let parent = new BaseClass(props)
    const getMessage = () => `Message is ${parent.getProps()}`;
    return { ...parent, getMessage} // I have used spread operator. 
}

Một lần nữa, bạn có thể sử dụng hàm Con như sau để nhận được kết quả tương tự,

let childObject = Child("Secret Message")
console.log(childObject.getMessage())     // logs "Message is Secret Message"
console.log(childObject.getProps())       // logs "Secret Message"

Javascript là ngôn ngữ rất dễ sử dụng. Chúng tôi có thể làm hầu hết mọi thứ. Chúc mừng JavaScripting ... Hy vọng tôi có thể cho bạn một ý tưởng để sử dụng trong trường hợp của bạn.


-1

Sử dụng expandFunction.js

init = extendFunction(init, function(args) {
  doSomethingHereToo();
});

Nhưng trong trường hợp cụ thể của bạn, việc mở rộng chức năng tải trên toàn cầu sẽ dễ dàng hơn:

extendFunction('onload', function(args) {
  doSomethingHereToo();
});

Tôi thực sự rất thích câu hỏi của bạn, nó khiến tôi suy nghĩ về các trường hợp sử dụng khác nhau.

Đối với các sự kiện javascript, bạn thực sự muốn thêm và xóa các trình xử lý - nhưng đối với expandFunction, làm cách nào để xóa chức năng sau này ? Tôi có thể dễ dàng thêm phương thức .revert vào các hàm mở rộng, vì vậy init = init.revert()sẽ trả về hàm ban đầu. Rõ ràng điều này có thể dẫn đến một số mã khá tệ, nhưng có lẽ nó cho phép bạn hoàn thành công việc mà không cần chạm vào phần ngoại lai của cơ sở mã.

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.