Ghi đè hàm JavaScript trong khi tham chiếu bản gốc


160

Tôi có một chức năng, a()mà tôi muốn ghi đè, nhưng cũng có bản gốc a()được thực hiện theo thứ tự tùy thuộc vào ngữ cảnh. Ví dụ: đôi khi khi tôi tạo một trang, tôi sẽ muốn ghi đè như thế này:

function a() {
    new_code();
    original_a();
}

và đôi khi như thế này:

function a() {
    original_a();
    other_new_code();
}

Làm thế nào để tôi có được điều đó original_a()từ bên trong quá sức a()? Nó thậm chí có thể?

Xin đừng đề xuất các lựa chọn thay thế cho việc đạp xe quá mức theo cách này, tôi biết nhiều người. Tôi đang hỏi về cách này cụ thể.

Câu trả lời:


179

Bạn có thể làm một cái gì đó như thế này:

var a = (function() {
    var original_a = a;

    if (condition) {
        return function() {
            new_code();
            original_a();
        }
    } else {
        return function() {
            original_a();
            other_new_code();
        }
    }
})();

Khai báo original_abên trong một hàm ẩn danh giữ cho nó không làm lộn xộn không gian tên toàn cầu, nhưng nó có sẵn trong các hàm bên trong.

Giống như Nerdmaster đã đề cập trong các bình luận, hãy chắc chắn bao gồm ()phần cuối. Bạn muốn gọi hàm ngoài và lưu kết quả (một trong hai hàm bên trong) vào a, không lưu chính hàm ngoài vào a.


25
Đối với bất kỳ kẻ ngốc nào ngoài đó như tôi - hãy chú ý đến "()" ở cuối - không có điều đó, nó trả về hàm bên ngoài, không phải các chức năng bên trong :)
Nerdmaster

@Nerdmaster Cảm ơn bạn đã chỉ ra điều đó. Tôi đã thêm một lưu ý để hy vọng giúp mọi người chú ý.
Matthew Crumley

5
Wow, tôi đã cố gắng làm điều này mà không có không gian tên và tôi đã có một ngăn xếp tràn, lol.
gosukiwi

tôi nghĩ rằng kết quả sẽ giống nhau nếu dấu ngoặc đầu tiên và cuối cùng bị xóa khỏi chức năng. Giống như từ đây (functi..}). Chỉ cần làm }();sẽ tạo ra kết quả tương tự, vì (function{})()tập hợp dấu ngoặc đơn thứ 1 được sử dụng vì bạn không thể chỉ khai báo, ẩn danh, biểu thức hàm bạn phải gán nó. Nhưng bằng cách làm () bạn không xác định bất cứ điều gì chỉ trả về một hàm.
Muhammad Umer

@MuhammadUmer Bạn nói đúng rằng dấu ngoặc đơn xung quanh biểu thức hàm là không cần thiết. Tôi bao gồm chúng bởi vì không có chúng, nếu bạn chỉ nhìn vào vài dòng đầu tiên, có vẻ như (ít nhất là với tôi) giống như bạn đang gán trực tiếp hàm ngoài a, không gọi nó và lưu trữ giá trị trả về. Các dấu ngoặc đơn cho tôi biết có điều gì khác đang diễn ra.
Matthew Crumley

88

Mẫu Proxy có thể giúp bạn:

(function() {
    // log all calls to setArray
    var proxied = jQuery.fn.setArray;
    jQuery.fn.setArray = function() {
        console.log( this, arguments );
        return proxied.apply( this, arguments );
    };
})();

Ở trên kết thúc mã của nó trong một hàm để ẩn "proxy" có thể thay đổi. Nó lưu phương thức setArray của jQuery trong một bao đóng và ghi đè lên nó. Proxy sau đó ghi lại tất cả các cuộc gọi đến phương thức và ủy thác cuộc gọi đến bản gốc. Sử dụng áp dụng (điều này, đối số) đảm bảo rằng người gọi sẽ không thể nhận thấy sự khác biệt giữa phương thức gốc và phương thức ủy quyền.


10
thật. việc sử dụng 'áp dụng' và 'đối số' làm cho điều này mạnh mẽ hơn nhiều so với các câu trả lời khác
Robert Levy

Lưu ý rằng mẫu này không yêu cầu jQuery, họ chỉ sử dụng một trong các hàm của jQuery làm ví dụ.
Kev

28

Cảm ơn mọi người, mẫu proxy thực sự có ích ..... Thật ra tôi muốn gọi một hàm toàn cầu là foo .. Trong một số trang nhất định tôi cần làm một số kiểm tra. Vì vậy, tôi đã làm như sau.

//Saving the original func
var org_foo = window.foo;

//Assigning proxy fucnc
window.foo = function(args){
    //Performing checks
    if(checkCondition(args)){
        //Calling original funcs
        org_foo(args);
    }
};

Thnx điều này thực sự đã giúp tôi ra


3
Khi theo mẫu proxy , trong một số trường hợp, điều quan trọng là phải triển khai chi tiết này không có trong mã Người trả lời này: Thay vì org_foo(args), hãy gọi org_foo.call(this, args). Điều đó duy trì thisnhư đã từng xảy ra khi window.foo gọi bình thường (không phải ủy nhiệm). Xem câu trả lời này
cellepo

10

Bạn có thể ghi đè một hàm bằng cách sử dụng cấu trúc như:

function override(f, g) {
    return function() {
        return g(f);
    };
}

Ví dụ:

 a = override(a, function(original_a) {
      if (condition) { new_code(); original_a(); }
      else { original_a(); other_new_code(); }
 });

Chỉnh sửa: Đã sửa lỗi chính tả.


+1 Điều này rất dễ đọc hơn các ví dụ mẫu proxy khác!
Mu Tâm

1
... nhưng nếu tôi không nhầm, điều này bị giới hạn ở các hàm không có đối số hoặc ít nhất là một số lượng đối số được xác định trước. Có một số cách để khái quát nó thành số lượng đối số tùy ý mà không mất khả năng đọc?
Mu Tâm

@MuMind: có, nhưng sử dụng đúng mẫu proxy - xem câu trả lời của tôi .
Dan Dascalescu

4

Vượt qua các đối số tùy ý:

a = override(a, function(original_a) {
    if (condition) { new_code(); original_a.apply(this, arguments) ; }
    else { original_a.apply(this, arguments); other_new_code(); }
});

1
không tranh luận đề cập đến original_amặc dù?
Jonathan.

2

Câu trả lời mà @Matthew Crumley cung cấp là sử dụng các biểu thức hàm được gọi ngay lập tức, để đóng hàm 'a' cũ hơn vào ngữ cảnh thực thi của hàm được trả về. Tôi nghĩ rằng đây là câu trả lời tốt nhất, nhưng cá nhân tôi, tôi thích chuyển hàm 'a' làm đối số cho IIFE. Tôi nghĩ nó dễ hiểu hơn.

   var a = (function(original_a) {
        if (condition) {
            return function() {
                new_code();
                original_a();
            }
        } else {
            return function() {
                original_a();
                other_new_code();
            }
        }
    })(a);

1

Các ví dụ trên không áp dụng đúng thishoặc chuyển argumentschính xác cho ghi đè hàm. Dấu gạch dưới _.wrap () bao bọc các hàm hiện có, áp dụng thisvà chuyển argumentschính xác. Xem: http://underscorejs.org/#wrap


0

Tôi đã có một số mã được viết bởi người khác và muốn thêm một dòng vào một chức năng mà tôi không thể tìm thấy trong mã. Vì vậy, như một cách giải quyết, tôi muốn ghi đè lên nó.

Không có giải pháp nào làm việc cho tôi mặc dù.

Đây là những gì đã làm việc trong trường hợp của tôi:

if (typeof originalFunction === "undefined") {
    originalFunction = targetFunction;
    targetFunction = function(x, y) {
        //Your code
        originalFunction(a, b);
        //Your Code
    };  
}

0

Tôi đã tạo một trình trợ giúp nhỏ cho một kịch bản tương tự vì tôi thường cần ghi đè các hàm từ một số thư viện. Trình trợ giúp này chấp nhận một "không gian tên" (vùng chứa hàm), tên hàm và hàm ghi đè. Nó sẽ thay thế chức năng ban đầu trong không gian tên được giới thiệu bằng cái mới.

Hàm mới chấp nhận hàm ban đầu làm đối số đầu tiên và các hàm đối số ban đầu là phần còn lại. Nó sẽ bảo tồn bối cảnh mọi lúc. Nó cũng hỗ trợ các hàm void và non-void.

function overrideFunction(namespace, baseFuncName, func) {
    var originalFn = namespace[baseFuncName];
    namespace[baseFuncName] = function () {
        return func.apply(this, [originalFn.bind(this)].concat(Array.prototype.slice.call(arguments, 0)));
    };
}

Cách sử dụng ví dụ với Bootstrap:

overrideFunction($.fn.popover.Constructor.prototype, 'leave', function(baseFn, obj) {
    // ... do stuff before base call
    baseFn(obj);
    // ... do stuff after base call
});

Tôi đã không tạo ra bất kỳ thử nghiệm hiệu suất mặc dù. Nó có thể có thể thêm một số chi phí không mong muốn có thể hoặc không thể là một vấn đề lớn, tùy thuộc vào các kịch bản.


0

Theo tôi, các câu trả lời hàng đầu không thể đọc / duy trì được và các câu trả lời khác không liên kết đúng ngữ cảnh. Đây là một giải pháp dễ đọc bằng cú pháp ES6 để giải quyết cả hai vấn đề này.

const orginial = someObject.foo;
someObject.foo = function() {
  if (condition) orginial.bind(this)(...arguments);
};

Nhưng sử dụng cú pháp ES6 làm cho nó khá hạn chế sử dụng. Bạn có phiên bản nào hoạt động trong ES5 không?
eitch

0

Vì vậy, câu trả lời của tôi cuối cùng là một giải pháp cho phép tôi sử dụng biến _this trỏ đến đối tượng ban đầu. Tôi tạo một phiên bản mới của "Hình vuông" tuy nhiên tôi ghét cách "Hình vuông" tạo ra kích thước của nó. Tôi nghĩ rằng nó nên theo nhu cầu cụ thể của tôi. Tuy nhiên, để làm như vậy, tôi cần hình vuông có chức năng "GetSize" được cập nhật với các phần bên trong của hàm đó gọi các hàm khác đã tồn tại trong hình vuông như this.height, this.GetVolume (). Nhưng để làm được như vậy tôi cần phải làm điều này mà không có bất kỳ vụ hack điên rồ nào. Vì vậy, đây là giải pháp của tôi.

Một số hàm khởi tạo đối tượng hoặc hàm trợ giúp khác.

this.viewer = new Autodesk.Viewing.Private.GuiViewer3D(
  this.viewerContainer)
var viewer = this.viewer;
viewer.updateToolbarButtons =  this.updateToolbarButtons(viewer);

Chức năng trong đối tượng khác.

updateToolbarButtons = function(viewer) {
  var _viewer = viewer;
  return function(width, height){ 
blah blah black sheep I can refer to this.anything();
}
};

0

Không chắc nó có hoạt động trong mọi trường hợp hay không, nhưng trong trường hợp của chúng tôi, chúng tôi đã cố gắng ghi đè describehàm trong Jest để chúng tôi có thể phân tích tên và bỏ qua toàn bộ describekhối nếu nó đáp ứng một số tiêu chí.

Đây là những gì làm việc cho chúng tôi:

function describe( name, callback ) {
  if ( name.includes( "skip" ) )
    return this.describe.skip( name, callback );
  else
    return this.describe( name, callback );
}

Hai điều quan trọng ở đây:

  1. Chúng tôi không sử dụng chức năng mũi tên () =>.

    Các hàm mũi tên thay đổi tham chiếu đến thisvà chúng ta cần đó là tệp this.

  2. Việc sử dụng this.describethis.describe.skipthay vì chỉ describedescribe.skip.

Một lần nữa, không chắc nó có giá trị với bất kỳ ai, nhưng ban đầu chúng tôi đã cố gắng thoát khỏi câu trả lời xuất sắc của Matthew Crumley nhưng cần phải làm cho phương thức của chúng tôi trở thành chức năng và chấp nhận các thông số để phân tích chúng trong điều kiện.

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.