javascript: hàm ẩn danh đệ quy?


120

Giả sử tôi có một hàm đệ quy cơ bản:

function recur(data) {
    data = data+1;
    var nothing = function() {
        recur(data);
    }
    nothing();
}

Làm cách nào tôi có thể làm điều này nếu tôi có một chức năng ẩn danh như ...

(function(data){
    data = data+1;
    var nothing = function() {
        //Something here that calls the function?
    }
    nothing();
})();

Tôi muốn một cách để gọi hàm đã gọi hàm này ... Tôi đã thấy các đoạn mã ở đâu đó (tôi không nhớ ở đâu) có thể cho bạn biết tên của một hàm được gọi, nhưng tôi không thể nhớ bất kỳ thông tin đó ngay bây giờ.


Có lý do gì bạn cần điều này hay bạn chỉ tò mò? Dường như với tôi nó sẽ được rõ ràng hơn chỉ đơn giản là cung cấp cho nó một cái tên ...
rfunduk

1
@thenduks: Vì lý do tương tự tại sao người ta sử dụng hàm ẩn danh. Chỉ là đôi khi đệ quy là cần thiết.
poke

5
Thật đáng tiếc là arguments.calleetồn tại và functnio này không làm được gì hữu ích. Tôi đã nhìn lên Y Combinator :P . Chết tiệt, thứ mà sẽ không bao giờ có được hữu ích ...
Kobi

1
Đúng, như Kobi đã liên kết, sử dụng một tổ hợp điểm cố định chẳng hạn như Y để thực hiện các hàm đệ quy ẩn danh mà không có đối số.callee.
nồi hấp 25

1
Xem w3future.com/weblog/stories/2002/02/22/… để biết ví dụ về bộ tổ hợp Y trong JS.
nồi hấp 25

Câu trả lời:


145

Bạn có thể đặt tên cho hàm, ngay cả khi bạn đang tạo hàm dưới dạng giá trị chứ không phải câu lệnh "khai báo hàm". Nói cách khác:

(function foo() { foo(); })();

là một hàm đệ quy thổi chồng. Nói chung, bạn thể không muốn làm điều này vì có một số vấn đề kỳ lạ với các triển khai khác nhau của Javascript. ( lưu ý - đó là một nhận xét khá cũ; một số / nhiều / tất cả các vấn đề được mô tả trong bài đăng trên blog của Kangax có thể được khắc phục trong các trình duyệt hiện đại hơn.)

Khi bạn đặt một cái tên như vậy, tên đó sẽ không hiển thị bên ngoài hàm (tốt, nó không phải vậy; đó là một trong những điều kỳ lạ). Nó giống như "letrec" trong Lisp.

Về phần arguments.callee, điều đó không được phép ở chế độ "nghiêm ngặt" và thường được coi là một điều xấu, vì nó làm cho một số tối ưu hóa khó khăn. Nó cũng chậm hơn nhiều so với những gì người ta có thể mong đợi.

chỉnh sửa - Nếu bạn muốn có tác dụng của một hàm "ẩn danh" có thể gọi chính nó, bạn có thể làm như thế này (giả sử bạn đang chuyển hàm dưới dạng gọi lại hoặc tương tự như vậy):

asyncThingWithCallback(params, (function() {
  function recursive() {
    if (timeToStop())
      return whatever();
    recursive(moreWork);
  }
  return recursive;
})());

Điều đó làm là xác định một hàm với một câu lệnh khai báo hàm đẹp, an toàn, không bị hỏng trong IE , tạo một hàm cục bộ có tên sẽ không gây ô nhiễm không gian tên chung. Hàm wrapper (thực sự ẩn danh) chỉ trả về hàm cục bộ đó.


Chúng ta có thể tránh làm ô nhiễm không gian tên toàn cầu theo cách khác với ES5 sctrict (Tôi chưa đọc sâu về ES5) không?
Incognito

@pointy, bạn có thể vui lòng nhìn vào nhiệm vụ này. stackoverflow.com/questions/27473450/…
Gladson Robinson

Tôi đoán nó không thể sử dụng (() => { call_recursively_self_here() })()và gọi chính nó đệ quy, phải không? Tôi phải đặt tên cho nó.
Qwerty

1
@Qwerty tốt bạn có thể làm điều gì đó giống như ví dụ cuối cùng trong câu trả lời của tôi. Liên kết hàm mũi tên với một biến cục bộ trong một hàm trình bao bọc để hàm mũi tên của bạn có thể tham chiếu đến chính nó với tên biến. Sau đó, trình bao bọc sẽ trả về biến (tham chiếu đến hàm mũi tên).
Pointy

1
@Pointy có thể một số tin tặc sẽ tìm thấy ứng dụng;)
Kamil Kiełczewski

31

Mọi người nói về bộ tổ hợp Y trong các bình luận, nhưng không ai viết nó như một câu trả lời.

Bộ tổ hợp Y có thể được định nghĩa bằng javascript như sau: (cảm ơn liên kết steamer25)

var Y = function (gen) {
  return (function(f) {
    return f(f);
  }(function(f) {
    return gen(function() {
      return f(f).apply(null, arguments);
    });
  }));
}

Và khi bạn muốn chuyển chức năng ẩn danh của mình:

(Y(function(recur) {
  return function(data) {
    data = data+1;
    var nothing = function() {
      recur(data);
    }
    nothing();
  }
})());

Điều quan trọng nhất cần lưu ý về giải pháp này là bạn không nên sử dụng nó.


16
"Điều quan trọng nhất cần lưu ý về giải pháp này là bạn không nên sử dụng nó." Tại sao?
nyuszika7h

7
Nó sẽ không được nhanh chóng. Thật là xấu khi sử dụng (mặc dù đẹp về mặt khái niệm!). Bạn tránh phải đặt cho hàm của mình một thẻ hoặc tên biến (và tôi không hiểu tại sao điều đó lại là mối quan tâm), nhưng bạn vẫn đặt tên cho nó làm tham số cho hàm bên ngoài được truyền cho Y. Vì vậy, bạn không đạt được bất cứ điều gì bằng cách vượt qua tất cả những rắc rối này.
zem

Đừng quên đề cập đến chức năng này không an toàn cho ngăn xếp. Chỉ lặp lại một vài nghìn lần sẽ dẫn đến tràn ngăn xếp.
Cảm ơn bạn

Xin chào, tôi sẽ đề xuất sửa đổi "gọn gàng hơn" một chút vì .apply (null, các đối số) có vẻ xấu với tôi: var Y = function (gen) {return (function (f) {return f (f);} (function (f) {return gen (function (x) {return f (f) (x);});})); } Hoặc tương đương ((hàm (x) {return y} bằng (x => y))) bằng cách sử dụng ký hiệu mũi tên (mã js hợp lệ): var Y = gen => (f => f (f)) (f = > gen (x => f (f) (x)))
myfirstAnswer

23

U tổ hợp

Bằng cách truyền một hàm cho chính nó dưới dạng đối số, một hàm có thể lặp lại bằng cách sử dụng tham số thay vì tên của nó! Vì vậy, hàm được cho Uphải có ít nhất một tham số sẽ liên kết với hàm (chính nó).

Trong ví dụ dưới đây, chúng tôi không có điều kiện thoát, vì vậy chúng tôi sẽ chỉ lặp vô thời hạn cho đến khi xảy ra tràn ngăn xếp

const U = f => f (f) // call function f with itself as an argument

U (f => (console.log ('stack overflow imminent!'), U (f)))

Chúng ta có thể dừng đệ quy vô hạn bằng nhiều kỹ thuật khác nhau. Ở đây, tôi sẽ viết hàm ẩn danh của chúng tôi để trả về một hàm ẩn danh khác đang chờ đầu vào; trong trường hợp này, một số. Khi một số được cung cấp, nếu nó lớn hơn 0, chúng tôi sẽ tiếp tục lặp lại, nếu không thì trả về 0.

const log = x => (console.log (x), x)

const U = f => f (f)

// when our function is applied to itself, we get the inner function back
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
// returns: (x => x > 0 ? U (f) (log (x - 1)) : 0)
// where f is a reference to our outer function

// watch when we apply an argument to this function, eg 5
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0) (5)
// 4 3 2 1 0

Điều không rõ ràng ngay lập tức ở đây là hàm của chúng ta, khi lần đầu tiên được áp dụng cho chính nó bằng cách sử dụng bộ Utổ hợp, nó trả về một hàm đang chờ đầu vào đầu tiên. Nếu chúng ta đã đặt tên cho nó, có thể tạo một cách hiệu quả các hàm đệ quy bằng lambdas (hàm ẩn danh)

const log = x => (console.log (x), x)

const U = f => f (f)

const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)

countDown (5)
// 4 3 2 1 0

countDown (3)
// 2 1 0

Chỉ có điều này không phải là đệ quy trực tiếp - một hàm tự gọi nó bằng tên riêng của nó. Định nghĩa của chúng tôi về countDownkhông tự tham chiếu bên trong cơ thể của nó và vẫn có thể đệ quy

// direct recursion references itself by name
const loop = (params) => {
  if (condition)
    return someValue
  else
    // loop references itself to recur...
    return loop (adjustedParams)
}

// U combinator does not need a named reference
// no reference to `countDown` inside countDown's definition
const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)

Cách xóa tự tham chiếu khỏi một hàm hiện có bằng bộ tổ hợp U

Ở đây tôi sẽ chỉ cho bạn cách sử dụng một hàm đệ quy sử dụng một tham chiếu đến chính nó và thay đổi nó thành một hàm sử dụng bộ tổ hợp U thay cho tham chiếu tự

const factorial = x =>
  x === 0 ? 1 : x * factorial (x - 1)
  
console.log (factorial (5)) // 120

Bây giờ sử dụng bộ tổ hợp U để thay thế tham chiếu bên trong factorial

const U = f => f (f)

const factorial = U (f => x =>
  x === 0 ? 1 : x * U (f) (x - 1))

console.log (factorial (5)) // 120

Mô hình thay thế cơ bản là thế này. Hãy ghi nhớ, chúng ta sẽ sử dụng một chiến lược tương tự trong phần tiếp theo

// self reference recursion
const foo =         x => ...   foo (nextX) ...

// remove self reference with U combinator
const foo = U (f => x => ... U (f) (nextX) ...)

Bộ tổ hợp Y

liên quan: các tổ hợp U và Y được giải thích bằng cách sử dụng phép tương tự trong gương

Trong phần trước, chúng ta đã biết cách chuyển đổi đệ quy tự tham chiếu thành một hàm đệ quy không dựa vào một hàm đã đặt tên bằng cách sử dụng bộ tổ hợp U. Có một chút khó chịu khi phải nhớ luôn chuyển hàm cho chính nó làm đối số đầu tiên. Chà, bộ tổ hợp Y được xây dựng dựa trên bộ tổ hợp chữ U và loại bỏ phần tẻ nhạt đó. Đây là một điều tốt vì loại bỏ / giảm độ phức tạp là lý do chính mà chúng tôi tạo ra các hàm

Đầu tiên, hãy lấy bộ tổ hợp Y của riêng chúng ta

// standard definition
const Y = f => f (Y (f))

// prevent immediate infinite recursion in applicative order language (JS)
const Y = f => f (x => Y (f) (x))

// remove reference to self using U combinator
const Y = U (h => f => f (x => U (h) (f) (x)))

Bây giờ chúng ta sẽ xem cách sử dụng của nó so với bộ tổ hợp chữ U. Lưu ý, để lặp lại, thay vì U (f)chúng ta có thể chỉ cần gọif ()

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

Y (f => (console.log ('stack overflow imminent!'),  f ()))

Bây giờ tôi sẽ trình bày countDownchương trình bằng cách sử dụng Y- bạn sẽ thấy các chương trình gần như giống hệt nhau nhưng bộ tổ hợp Y giúp mọi thứ sạch sẽ hơn một chút

const log = x => (console.log (x), x)

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const countDown = Y (f => x => x > 0 ? f (log (x - 1)) : 0)

countDown (5)
// 4 3 2 1 0

countDown (3)
// 2 1 0

Và bây giờ chúng ta sẽ thấy factorialcũng

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const factorial = Y (f => x =>
  x === 0 ? 1 :  x * f (x - 1))

console.log (factorial (5)) // 120

Như bạn có thể thấy, ftrở thành cơ chế cho chính nó đệ quy. Để lặp lại, chúng tôi gọi nó giống như một hàm thông thường. Chúng ta có thể gọi nó nhiều lần với các đối số khác nhau và kết quả sẽ vẫn đúng. Và vì nó là một tham số hàm thông thường, chúng ta có thể đặt tên nó bất cứ thứ gì chúng ta muốn, chẳng hạn như recurbên dưới:

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const fibonacci = Y (recur => n =>
  n < 2 ? n : recur (n - 1) +  (n - 2))

console.log (fibonacci (10)) // 55


Bộ tổ hợp U và Y với nhiều hơn 1 tham số

Trong các ví dụ trên, chúng ta đã thấy cách chúng ta có thể lặp lại và truyền một đối số để theo dõi "trạng thái" tính toán của chúng ta. Nhưng nếu chúng ta cần theo dõi trạng thái bổ sung thì sao?

Chúng ta có thể sử dụng dữ liệu phức hợp như Mảng hoặc thứ gì đó ...

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const fibonacci = Y (f => ([a, b, x]) =>
  x === 0 ? a : f ([b, a + b, x - 1]))

// starting with 0 and 1, generate the 7th number in the sequence
console.log (fibonacci ([0, 1, 7])) 
// 0 1 1 2 3 5 8 13

Nhưng điều này thật tệ vì nó để lộ trạng thái bên trong (bộ đếm ab). Thật tuyệt nếu chúng ta có thể gọi điện fibonacci (7)để nhận được câu trả lời mình muốn.

Sử dụng những gì chúng ta biết về các hàm đơn phân (chuỗi các hàm một bậc (1 tham số)), chúng ta có thể đạt được mục tiêu của mình một cách dễ dàng mà không cần phải sửa đổi định nghĩa của chúng ta về Yhoặc dựa vào dữ liệu phức hợp hoặc các tính năng ngôn ngữ nâng cao.

Nhìn kỹ định nghĩa của fibonaccibên dưới. Chúng tôi ngay lập tức áp dụng 01được ràng buộc abtương ứng. Bây giờ fibonacci chỉ đơn giản là đợi đối số cuối cùng được cung cấp mà sẽ bị ràng buộc x. Khi chúng ta đệ quy, chúng ta phải gọi f (a) (b) (x)(not f (a,b,x)) vì hàm của chúng ta ở dạng curry.

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const fibonacci = Y (f => a => b => x =>
  x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)

console.log (fibonacci (7)) 
// 0 1 1 2 3 5 8 13


Loại mẫu này có thể hữu ích để xác định tất cả các loại chức năng. Dưới đây chúng ta sẽ thấy thêm hai chức năng được xác định bằng cách sử dụng Ycombinator ( rangereduce) và một dẫn xuất của reduce, map.

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const range = Y (f => acc => min => max =>
  min > max ? acc : f ([...acc, min]) (min + 1) (max)) ([])

const reduce = Y (f => g => y => ([x,...xs]) =>
  x === undefined ? y : f (g) (g (y) (x)) (xs))
  
const map = f =>
  reduce (ys => x => [...ys, f (x)]) ([])
  
const add = x => y => x + y

const sq = x => x * x

console.log (range (-2) (2))
// [ -2, -1, 0, 1, 2 ]

console.log (reduce (add) (0) ([1,2,3,4]))
// 10

console.log (map (sq) ([1,2,3,4]))
// [ 1, 4, 9, 16 ]


ĐÓ LÀ TẤT CẢ BẤT CỨ KHỔNG LỒ

Bởi vì chúng tôi đang làm việc với các hàm thuần túy ở đây, chúng tôi có thể thay thế bất kỳ hàm nào được đặt tên cho định nghĩa của nó. Xem điều gì sẽ xảy ra khi chúng ta lấy fibonacci và thay thế các hàm được đặt tên bằng các biểu thức của chúng

/* const U = f => f (f)
 *
 * const Y = U (h => f => f (x => U (h) (f) (x)))
 *
 * const fibonacci = Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)
 *
 */

/*
 * given fibonacci (7)
 *
 * replace fibonacci with its definition
 * Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
 *
 * replace Y with its definition
 * U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
//
 * replace U with its definition
 * (f => f (f)) U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
 */

let result =
  (f => f (f)) (h => f => f (x => h (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
  
console.log (result) // 13

Và bạn đã có nó - được fibonacci (7)tính toán đệ quy không sử dụng gì ngoài các hàm ẩn danh


14

Có thể đơn giản nhất là sử dụng "đối tượng ẩn danh" thay thế:

({
  do: function() {
    console.log("don't run this ...");
    this.do();
  }
}).do();

Không gian toàn cầu của bạn hoàn toàn không bị ô nhiễm. Nó khá đơn giản. Và bạn có thể dễ dàng tận dụng trạng thái không toàn cục của đối tượng.

Bạn cũng có thể sử dụng các phương thức đối tượng ES6 để làm cho cú pháp ngắn gọn hơn.

({
  do() {
    console.log("don't run this ...");
    this.do();
  }
}).do();

13

Tôi sẽ không làm điều này như một hàm nội tuyến. Nó đang đẩy lùi ranh giới của hương vị ngon và không thực sự giúp bạn được gì.

Nếu bạn thực sự phải, có arguments.calleenhư câu trả lời của Fabrizio. Tuy nhiên, điều này thường được coi là không thể sử dụng được và không được phép trong 'chế độ nghiêm ngặt' của ECMAScript Fifth Edition. Mặc dù ECMA 3 và chế độ không nghiêm ngặt sẽ không biến mất, nhưng làm việc ở chế độ nghiêm ngặt hứa hẹn nhiều khả năng tối ưu hóa ngôn ngữ hơn.

Người ta cũng có thể sử dụng một hàm nội tuyến được đặt tên:

(function foo(data){
    data++;
    var nothing = function() {
        foo(data);
    }
    nothing();
})();

Tuy nhiên, tốt nhất nên tránh các biểu thức hàm nội tuyến được đặt tên, vì JScript của IE gây ra một số điều xấu cho chúng. Trong ví dụ trên foogây ô nhiễm không chính xác phạm vi cha trong IE, và cha foolà một thể hiện riêng biệt đối với foobên trong foo.

Mục đích của việc đặt điều này trong một chức năng ẩn danh nội tuyến là gì? Nếu bạn chỉ muốn tránh làm ô nhiễm phạm vi chính, tất nhiên bạn có thể ẩn ví dụ đầu tiên của mình bên trong một chức năng tự gọi-nặc danh khác (không gian tên). Bạn có thực sự cần tạo một bản sao mới nothingmỗi lần trong vòng đệ quy không? Bạn có thể tốt hơn với một không gian tên chứa hai hàm đệ quy lẫn nhau đơn giản.


Tôi đồng ý, một hàm được đặt tên phù hợp hơn các đối số .callee không chỉ cho chế độ nghiêm ngặt ecmascript, mà còn cho vấn đề tối ưu hóa bởi vì ở mỗi lần đệ quy, anh ta cần nhận được tham chiếu đến callee (và điều này có thể làm giảm tốc độ thực thi )

+1 cho bài thơ, "pushing against the boundaries of good taste"- (tốt, và thông tin hay).
Peter Ajtai

Còn về một tiền tố / hậu tố đơn giản nếu ô nhiễm thực sự là mối quan tâm ở đây? Xem xét rằng nó không thuộc phạm vi toàn cục (ngay cả khi hàm là cấp cao nhất, anh ta nên có một hàm ẩn danh bao bọc toàn bộ mã của anh ta) thì thực sự không chắc rằng một cái tên như vậy recur_foosẽ va chạm với một hàm trong phạm vi chính (hoặc bị bệnh -đã sử dụng) .
gblazex

Rất thú vị - jsfiddle.net/hck2A - IE làm ô nhiễm phụ huynh trong trường hợp này, như bạn đã nói. Không bao giờ nhận ra điều đó.
Peter Ajtai

1
@Peter : kangax.github.com/nfe (đặc biệt là 'lỗi JScript') cho nhiều điều bạn muốn biết về chủ đề này. Cuối cùng nó đã được sửa trong IE9 (nhưng chỉ ở Chế độ tiêu chuẩn IE9).
bobince

10
(function(data){
    var recursive = arguments.callee;
    data = data+1;
    var nothing = function() {
        recursive(data)
    }
    nothing();
})();

34
Tôi hy vọng mọi người bỏ phiếu cho câu trả lời này (đúng về mặt kỹ thuật) nhận ra các vấn đề với arguments.callee: nó không được phép ở chế độ nghiêm ngặt và trong ES5.
Pointy 7/10/10

Bình chọn xuống, arguments.callee bị phản đối trong ES5
Jaime Rodriguez

Nó hoạt động trong NodeJS. Tôi không thể quan tâm hơn đến ES5 miễn là nó hoạt động theo kiểu có thể đoán trước trên một môi trường cố định.
Angad

1
Đây là một quả bom hẹn giờ. Không có cái gọi là môi trường "cố định", như nhận xét trên gợi ý. Bạn hầu như luôn luôn nâng cấp vì bất kỳ lý do nào trong số hàng ngàn lý do để làm như vậy.
sampathsris

6

Bạn có thể làm điều gì đó như:

(foo = function() { foo(); })()

hoặc trong trường hợp của bạn:

(recur = function(data){
    data = data+1;
    var nothing = function() {
        if (data > 100) return; // put recursion limit
        recur(data);
    }
    nothing();
})(/* put data init value here */ 0);

Bạn có thể làm với việc khai báo recurtrước với một varcâu lệnh. Không hiểu liệu điều đó có phá vỡ các quy tắc của câu hỏi hay không, nhưng khi bạn có nó bây giờ, nếu không có varcâu lệnh, bạn sẽ gặp lỗi trong chế độ nghiêm ngặt ECMAScript 5.
Tim Down

Nhận xét ban đầu của tôi bao gồm vartừ khóa, nhưng khi tôi kiểm tra mã này, nó đã gây ra lỗi, vì bạn không thể thực sự khai báo một biến bên trong một khối tự gọi và cách tiếp cận của tôi dựa vào khai báo tự động của một biến không xác định, và do đó @ Pointy's giải pháp là đúng hơn. Nhưng tôi vẫn bình chọn cho câu trả lời của Fabrizio Calderan;)
ArtBIT 7/10/10

Có, thực hiện (var recur = function() {...})();sẽ không hoạt động vì bây giờ nó là một câu lệnh chứ không phải là một biểu thức gán (trả về giá trị được gán). Tôi đã đề nghị var recur; (recur = function() {...})();thay thế.
Tim Down

3

Khi bạn khai báo một hàm ẩn danh như thế này:

(function () {
    // Pass
}());

Nó được coi là một biểu thức hàm và nó có một tên tùy chọn (bạn có thể sử dụng để gọi nó từ bên trong chính nó. Nhưng vì nó là một biểu thức hàm (chứ không phải một câu lệnh) nên nó vẫn ẩn danh (nhưng có một tên mà bạn có thể gọi). Vì vậy, hàm này có thể gọi chính nó:

(function foo () {
    foo();
}());
foo //-> undefined

"nó vẫn ẩn danh" - không nó không. Một chức năng ẩn danh không có tên. Tôi hiểu rằng điều foođó không được khai báo trong bối cảnh hiện tại, nhưng điều đó ít nhiều không liên quan. Một hàm có tên vẫn là một hàm được đặt tên - không ẩn danh.
Cảm ơn bạn

3

Tại sao không truyền chức năng cho chính chức năng?

    var functionCaller = function(thisCaller, data) {
        data = data + 1;
        var nothing = function() {
            thisCaller(thisCaller, data);
        };
        nothing();
    };
    functionCaller(functionCaller, data);

3

Trong một số tình huống nhất định, bạn phải dựa vào các chức năng ẩn danh. Cho là một maphàm đệ quy :

const map = f => acc => ([head, ...tail]) => head === undefined 
 ? acc
 : map (f) ([...acc, f(head)]) (tail);

const sqr = x => x * x;
const xs = [1,2,3,4,5];

console.log(map(sqr) ([0]) (xs)); // [0] modifies the structure of the array

Hãy lưu ý rằng mapkhông được sửa đổi cấu trúc của mảng. Vì vậy, bộ tích lũy acckhông cần phải tiếp xúc. mapVí dụ, chúng ta có thể bọc thành một hàm khác:

const map = f => xs => {
  let next = acc => ([head, ...tail]) => head === undefined
   ? acc
   : map ([...acc, f(head)]) (tail);

  return next([])(xs);
}

Nhưng giải pháp này là khá dài dòng. Hãy sử dụng bộ Utổ hợp được đánh giá thấp :

const U = f => f(f);

const map = f => U(h => acc => ([head, ...tail]) => head === undefined 
 ? acc
 : h(h)([...acc, f(head)])(tail))([]);

const sqr = x => x * x;
const xs = [1,2,3,4,5];

console.log(map(sqr) (xs));

Cô đọng, phải không? Ukhá đơn giản nhưng có nhược điểm là lời gọi đệ quy bị xáo trộn một chút: sum(...)trở thành h(h)(...)- chỉ có vậy.


2

Tôi không chắc liệu câu trả lời có còn được yêu cầu hay không nhưng điều này cũng có thể được thực hiện bằng cách sử dụng các đại biểu được tạo bằng function.bind:

    var x = ((function () {
        return this.bind(this, arguments[0])();
    }).bind(function (n) {
        if (n != 1) {
            return n * this.bind(this, (n - 1))();
        }
        else {
            return 1;
        }
    }))(5);

    console.log(x);

Điều này không liên quan đến các hàm hoặc đối số được đặt tên.callee.


1

Giống như bobince đã viết, chỉ cần đặt tên cho hàm của bạn.

Nhưng, tôi đoán rằng bạn cũng muốn chuyển vào một giá trị ban đầu và cuối cùng dừng chức năng của bạn!

var initialValue = ...

(function recurse(data){
    data++;
    var nothing = function() {
        recurse(data);
    }
    if ( ... stop condition ... )
        { ... display result, etc. ... }
    else
        nothing();
}(initialValue));

ví dụ jsFiddle làm việc (sử dụng data + = data cho vui)



1
+1, Đây là một câu trả lời rất hữu ích và bạn sẽ nhận được nhiều ủng hộ hơn cho nó, nhưng nó không ẩn danh.
Incognito

bạn rõ ràng đã không đọc những gì bobince đã viết: However named inline function expressions are also best avoided.. Nhưng OP bỏ lỡ điểm quá ... :)
gblazex

@Galamb - Tôi đã đọc nó. Không được phép ở chế độ nghiêm ngặt và trong ES5 không giống như việc gây ô nhiễm phạm vi chính và tạo ra các trường hợp bổ sung.
Peter Ajtai

1

tôi cần (hay đúng hơn là muốn) một hàm ẩn danh một lớp để đi lên một đối tượng xây dựng một chuỗi và xử lý nó như thế này:

var cmTitle = 'Root' + (function cmCatRecurse(cmCat){return (cmCat == root) ? '' : cmCatRecurse(cmCat.parent) + ' : ' + cmCat.getDisplayName();})(cmCurrentCat);

tạo ra một chuỗi như 'Root: foo: bar: baz: ...'


1

Với ES2015, chúng ta có thể chơi xung quanh một chút với cú pháp và lạm dụng các tham số mặc định và côn. Cái sau chỉ là các hàm mà không có bất kỳ đối số nào:

const applyT = thunk => thunk();

const fib = n => applyT(
  (f = (x, y, n) => n === 0 ? x : f(y, x + y, n - 1)) => f(0, 1, n)
);

console.log(fib(10)); // 55

// Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55...

Xin lưu ý rằng đó flà một tham số có chức năng ẩn danh (x, y, n) => n === 0 ? x : f(y, x + y, n - 1)làm giá trị mặc định của nó. Khi nào fđược gọi bởi applyTlời gọi này phải diễn ra mà không có đối số để giá trị mặc định được sử dụng. Giá trị mặc định là một hàm và do đó flà một hàm được đặt tên, có thể gọi chính nó một cách đệ quy.


0

Một câu trả lời khác không liên quan đến hàm hoặc các đối số được đặt tên.callee

var sum = (function(foo,n){
  return n + foo(foo,n-1);
})(function(foo,n){
     if(n>1){
         return n + foo(foo,n-1)
     }else{
         return n;
     }
},5); //function takes two argument one is function and another is 5

console.log(sum) //output : 15

đẹp: liên kết một hàm ẩn danh với một tham số cục bộ và sau đó gọi hàm thông qua tham số cục bộ, nhưng cũng truyền hàm cho chính nó để đệ quy.
englebart

0

Đây là một bản làm lại của câu trả lời jforjs với các tên khác nhau và một mục được sửa đổi một chút.

// function takes two argument: first is recursive function and second is input
var sum = (function(capturedRecurser,n){
  return capturedRecurser(capturedRecurser, n);
})(function(thisFunction,n){
     if(n>1){
         return n + thisFunction(thisFunction,n-1)
     }else{
         return n;
     }
},5); 

console.log(sum) //output : 15

Không cần phải giải nén đệ quy đầu tiên. Hàm nhận bản thân nó như một tham chiếu sẽ quay lại trạng thái ban đầu của OOP.


0

Đây là phiên bản câu trả lời của @ zem với các hàm mũi tên.

Bạn có thể sử dụng Ubộ Ytổ hợp. Bộ tổ hợp Y là đơn giản nhất để sử dụng.

U combinator, với điều này, bạn phải tiếp tục chuyển hàm: const U = f => f(f) U(selfFn => arg => selfFn(selfFn)('to infinity and beyond'))

Y combinator, với điều này, bạn không phải tiếp tục chuyển hàm: const Y = gen => U(f => gen((...args) => f(f)(...args))) Y(selfFn => arg => selfFn('to infinity and beyond'))


0

Tuy nhiên, một giải pháp Y-combinator khác, sử dụng liên kết mã rosetta (tôi nghĩ trước đây ai đó đã đề cập đến liên kết ở đâu đó trên stackOverflow.

Các mũi tên dành cho các hàm ẩn danh dễ đọc hơn đối với tôi:

var Y = f => (x => x(x))(y => f(x => y(y)(x)));

-1

Điều này có thể không hoạt động ở mọi nơi, nhưng bạn có thể sử dụng arguments.calleeđể tham chiếu đến chức năng hiện tại.

Vì vậy, giai thừa có thể được thực hiện như vậy:

var fac = function(x) { 
    if (x == 1) return x;
    else return x * arguments.callee(x-1);
}
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.