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 this
giá 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 this
khô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 f
sẽ 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.