Tại sao thuộc tính argument.callee.caller không được dùng trong JavaScript?


214

Tại sao arguments.callee.callertài sản không được dùng trong JavaScript?

Nó đã được thêm vào và sau đó không dùng nữa trong JavaScript, nhưng nó đã bị bỏ qua hoàn toàn bởi ECMAScript. Một số trình duyệt (Mozilla, IE) đã luôn hỗ trợ nó và không có bất kỳ kế hoạch nào trên bản đồ để xóa hỗ trợ. Những người khác (Safari, Opera) đã chấp nhận hỗ trợ cho nó, nhưng hỗ trợ trên các trình duyệt cũ hơn là không đáng tin cậy.

Có một lý do tốt để đưa chức năng có giá trị này trong limbo?

(Hoặc cách khác, có cách nào tốt hơn để nắm lấy chức năng gọi điện không?)


2
Nó được hỗ trợ bởi các trình duyệt khác bởi vì bất kỳ tính năng nào được sử dụng rộng rãi sẽ trở thành lỗi tương thích cho các trình duyệt khác. Nếu một trang web sử dụng một tính năng chỉ tồn tại trong một trình duyệt, thì trang đó sẽ bị hỏng ở tất cả các trang khác và thông thường người dùng nghĩ rằng đó là trình duyệt bị hỏng.
olliej

4
(Hầu hết tất cả các trình duyệt đã thực hiện việc này lúc này hay lúc khác, ví dụ: tính năng này (và chính bản thân JS) đến từ Netscape, XHR có nguồn gốc từ IE, Canvas trong Safari, v.v. theo thời gian (js, canvas, xhr đều là ví dụ), một số (.callee) thì không.
olliej

@olliej Nhận xét của bạn về việc hỗ trợ nó vì nó được sử dụng chứ không phải vì nó là một tiêu chuẩn (hoặc thậm chí mặc dù nó không được chấp nhận trong tiêu chuẩn) là rất đúng! Đây là lý do tại sao tôi bắt đầu bỏ qua các tiêu chuẩn bất cứ khi nào tôi cảm thấy chúng không giúp tôi. Chúng tôi là nhà phát triển có thể định hình hướng của các tiêu chuẩn bằng cách sử dụng những gì hoạt động và không phải những gì thông số kỹ thuật nói rằng chúng tôi nên làm. Đây là cách chúng tôi đã nhận <b><i>quay lại (vâng, những điều đó đã bị phản đối tại một thời điểm).
Stijn de Witt

Câu trả lời:


252

Các phiên bản ban đầu của JavaScript không cho phép các biểu thức hàm được đặt tên và do đó chúng tôi không thể tạo biểu thức hàm đệ quy:

 // This snippet will work:
 function factorial(n) {
     return (!(n>1))? 1 : factorial(n-1)*n;
 }
 [1,2,3,4,5].map(factorial);


 // But this snippet will not:
 [1,2,3,4,5].map(function(n) {
     return (!(n>1))? 1 : /* what goes here? */ (n-1)*n;
 });

Để khắc phục điều này, arguments.calleeđã được thêm vào để chúng tôi có thể làm:

 [1,2,3,4,5].map(function(n) {
     return (!(n>1))? 1 : arguments.callee(n-1)*n;
 });

Tuy nhiên, đây thực sự là một giải pháp thực sự tồi tệ vì điều này (kết hợp với các vấn đề khác, các vấn đề về callee và người gọi) làm cho đệ quy nội tuyến và đuôi không thể thực hiện được trong trường hợp chung (bạn có thể đạt được nó trong các trường hợp được chọn thông qua truy tìm, v.v. là tối ưu phụ do kiểm tra sẽ không cần thiết). Vấn đề chính khác là cuộc gọi đệ quy sẽ nhận được một thisgiá trị khác , ví dụ:

var global = this;
var sillyFunction = function (recursed) {
    if (!recursed)
        return arguments.callee(true);
    if (this !== global)
        alert("This is: " + this);
    else
        alert("This is the global");
}
sillyFunction();

Dù sao đi nữa, EcmaScript 3 đã giải quyết các vấn đề này bằng cách cho phép các biểu thức hàm được đặt tên, ví dụ:

 [1,2,3,4,5].map(function factorial(n) {
     return (!(n>1))? 1 : factorial(n-1)*n;
 });

Điều này có rất nhiều lợi ích:

  • Hàm này có thể được gọi như bất kỳ hàm nào khác từ bên trong mã của bạn.

  • Nó không gây ô nhiễm không gian tên.

  • Giá trị của thiskhông thay đổi.

  • Đó là hiệu suất cao hơn (truy cập đối tượng đối số là tốn kém).

Rất tiếc

Chỉ cần nhận ra rằng ngoài tất cả mọi thứ khác, câu hỏi là về arguments.callee.caller, hoặc cụ thể hơn Function.caller.

Tại bất kỳ thời điểm nào bạn cũng có thể tìm thấy người gọi sâu nhất của bất kỳ chức năng nào trên ngăn xếp, và như tôi đã nói ở trên, nhìn vào ngăn xếp cuộc gọi có một tác dụng chính duy nhất: Nó làm cho một số lượng lớn tối ưu hóa không thể, hoặc khó khăn hơn nhiều.

Ví dụ. nếu chúng ta không thể đảm bảo rằng một hàm fsẽ không gọi một hàm không xác định, thì nó không thể nội tuyến f. Về cơ bản, điều đó có nghĩa là bất kỳ trang web cuộc gọi nào có thể bị vô hiệu hóa đều tích lũy được một số lượng lớn bảo vệ, hãy:

 function f(a, b, c, d, e) { return a ? b * c : d * e; }

Nếu trình thông dịch js không thể đảm bảo rằng tất cả các đối số được cung cấp là các số tại điểm mà cuộc gọi được thực hiện, thì nó cần phải chèn kiểm tra cho tất cả các đối số trước mã được in, hoặc nó không thể nội tuyến hàm.

Bây giờ trong trường hợp cụ thể này, một thông dịch viên thông minh sẽ có thể sắp xếp lại các kiểm tra để tối ưu hơn và không kiểm tra bất kỳ giá trị nào sẽ không được sử dụng. Tuy nhiên, trong nhiều trường hợp điều đó là không thể và do đó nó trở nên không thể thực hiện được.


12
Bạn đang nói nó bị lỗi chỉ vì nó khó tối ưu hóa? Điều đó hơi ngớ ngẩn.
Thomas Eding

11
Không, tôi đã liệt kê một số lý do, ngoài ra nó còn khó tối ưu hóa (mặc dù trong lịch sử nói chung đã chỉ ra rằng những thứ khó tối ưu hóa cũng có ngữ nghĩa mà mọi người gặp khó khăn khi theo dõi)
olliej

17
Các này đối số là một chút giả mạo, giá trị của nó có thể được thiết lập bởi các cuộc gọi nếu điều quan trọng. Thông thường nó không được sử dụng (ít nhất, tôi chưa bao giờ gặp vấn đề với nó trong các hàm đệ quy). Gọi hàm theo tên có cùng một vấn đề thisvì vậy tôi nghĩ nó không liên quan đến việc callee là tốt hay xấu. Ngoài ra, calleengười gọi chỉ "không dùng nữa" trong chế độ nghiêm ngặt (ECMAscript ed 5, tháng 12 năm 2009), nhưng tôi đoán rằng điều đó không được biết đến vào năm 2008 khi olliej đăng.
RobG

8
) Tôi vẫn không thấy logic. Trong bất kỳ ngôn ngữ nào có chức năng hạng nhất, có giá trị rõ ràng trong việc có thể xác định một cơ thể chức năng có thể tự tham chiếu mà không cần phải biết i
Mark Reed

8
RobG đã chỉ ra điều này, nhưng tôi không nghĩ nó đã rõ ràng: việc đệ quy bằng cách sử dụng hàm có tên sẽ chỉ bảo toàn giá trị của thisif thislà phạm vi toàn cầu. Trong tất cả các trường hợp khác, giá trị của this sẽ thay đổi sau cuộc gọi đệ quy đầu tiên, vì vậy tôi nghĩ rằng các phần trong câu trả lời của bạn ám chỉ việc giữ gìn thiskhông thực sự hợp lệ.
JLRishe

89

arguments.callee.callerkhông bị phản đối, mặc dù nó làm cho việc sử dụng tài sản. ( sẽ chỉ cung cấp cho bạn một tham chiếu đến chức năng hiện tại)Function.callerarguments.callee

  • Function.caller, mặc dù không chuẩn theo ECMA3, được triển khai trên tất cả các trình duyệt chính hiện tại .
  • arguments.caller được phản đối ủng hộ , và không được thực hiện trong một số trình duyệt chính hiện tại (ví dụ như Firefox 3).Function.caller

Vì vậy, tình huống ít hơn lý tưởng, nhưng nếu bạn muốn truy cập chức năng gọi trong Javascript trên tất cả các trình duyệt chính, bạn có thể sử dụng thuộc tính, hoặc được truy cập trực tiếp trên tham chiếu chức năng được đặt tên hoặc từ trong một chức năng ẩn danh thông qua thuộc tính.Function.callerarguments.callee


5
Đây là lời giải thích tốt nhất về những gì đang và không được phản đối, rất hữu ích. Để biết ví dụ hay về những gì Function.caller không thể làm (lấy dấu vết ngăn xếp của các hàm đệ quy), hãy xem developer.mozilla.org/en/JavaScript/Reference/Global_Objects/
trộm

1
Mặc dù, arguments.calleebị cấm trong chế độ nghiêm ngặt. Nó cũng làm tôi buồn, nhưng tốt hơn hết là không sử dụng nó nữa.
Gras Double

1
Siêu liên kết argument.callee mà bạn có MDN nói rằng nó bị xóa trong chế độ nghiêm ngặt. Điều đó không giống như bị phản đối?
styfle

1
Lưu ý rằng arguments.callee.callerkhông được dùng trong chế độ nghiêm ngặt ES5: "Một tính năng khác không được dùng nữa là argument.callee.caller, hay cụ thể hơn là Function.caller." ( Nguồn )
thdoan

29

Nó là tốt hơn để sử dụng các chức năng được đặt tên hơn argument.callee:

 function foo () {
     ... foo() ...
 }

tốt hơn

 function () {
     ... arguments.callee() ...
 }

Hàm được đặt tên sẽ có quyền truy cập vào trình gọi của nó thông qua thuộc tính người gọi :

 function foo () {
     alert(foo.caller);
 }

cái nào tốt hơn

 function foo () {
     alert(arguments.callee.caller);
 }

Sự phản đối là do các nguyên tắc thiết kế ECMAScript hiện tại .


2
Bạn có thể mô tả tại sao sử dụng chức năng được đặt tên là tốt hơn. Có bao giờ có nhu cầu sử dụng callee trong một chức năng ẩn danh?
AnthonyWJones

27
Nếu bạn đang sử dụng callee trong một chức năng ẩn danh, thì bạn có một chức năng không nên ẩn danh.
Prestaul

3
đôi khi cách dễ nhất để gỡ lỗi là với .caller (). Trong các trường hợp như vậy, các hàm được đặt tên sẽ không giúp ích - bạn đang cố gắng tìm ra chức năng nào đang thực hiện cuộc gọi.
Samoody

6
Xác định tốt hơn. Ví dụ, IE6-8 đã đặt tên các quirks của hàm trong khi argument.callee hoạt động.
cmc

1
Ngoài các quirks IE6-8, nó cũng làm cho mã được liên kết chặt chẽ. Nếu tên của các đối tượng và / hoặc hàm bị mã hóa cứng, thì như ardsasd và rsk82 đề cập, có những nguy cơ tái cấu trúc chính, chỉ tăng khi kích thước cơ sở mã tăng. Các bài kiểm tra đơn vị là một biện pháp phòng thủ và tôi sử dụng chúng, nhưng chúng vẫn chưa phải là một câu trả lời thực sự làm tôi hài lòng về vấn đề mã hóa cứng này.
Jasmine Hegman

0

Chỉ là một phần mở rộng. Giá trị của "cái này" thay đổi trong quá trình đệ quy. Trong ví dụ (đã sửa đổi) sau đây, factorial lấy đối tượng {foo: true}.

[1,2,3,4,5].map(function factorial(n) {
  console.log(this);
  return (!(n>1))? 1 : factorial(n-1)*n;
},     {foo:true}     );

giai thừa được gọi lần đầu tiên có được đối tượng, nhưng điều này không đúng với các cuộc gọi đệ quy.


1
Bởi vì bạn đang làm sai. Nếu thiscần được duy trì, hãy viết factorial.call(this, n-1)Tôi thực sự đã tìm thấy bằng cách viết mã đệ quy, thường là không có thishoặc thisđề cập đến một nút nào đó trong cây và thực sự rất tốt khi nó thay đổi.
Stijn de Witt
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.