Từ khóa 'trở lại' là loại gì?


95

Chúng tôi sử dụng câu lệnh trả về một cách tùy chọn trong các hàm JavaScript. Đó là một từ khóa. Nhưng loại thực tế của returnchính nó là gì. Thực sự tôi đã bối rối, khi xem ví dụ:

function add(a, b) {
  return (
    console.log(a + b),
    console.log(arguments)
  );
}

add(2, 2);

Đầu ra:

4
[2, 2]

Vì vậy, chúng ta có thể chuyển các biểu thức được phân tách bằng dấu phẩy vào returncâu lệnh. Đây có phải là một chức năng?

Và bắt đầu với điều này, chúng ta có thể đoán rằng mọi từ khóa trong JavaScript cuối cùng đều là một hàm không?

Tôi đã viết một blog nhỏ như một ý chính của cuộc thảo luận này. Bạn có thể muốn kiểm tra nó ở đây .


106
Ngay khi bạn nghĩ rằng bạn hiểu JavaScript, một cái gì đó như thế này sẽ xuất hiện ...
FP

12
Chân đế không có nghĩa là bạn đang gọi điện thoại chức năng, ví dụ: var a = (2 + 2).
alexmac

12
Một ví dụ thích hợp hơn là var a = (1, 2)- điều này sẽ dẫn đến acó giá trị 2do cách các nhà điều hành dấu phẩy hoạt động (có, dấu phẩy là một nhà điều hành toán học như +hoặc |vv, đặc biệt, trong tính toán Lambada, các dụng cụ điều hành dấu phẩy K combinator)
slebetman

6
Sau đó, hành động chính xác là loại bỏ quan niệm sai lầm, không phải phản đối.
el.pescado

5
@FlorianPeschka không thực sự, đây là hành vi tiêu chuẩn trong bất kỳ ngôn ngữ C-flavour nào.
djechlin

Câu trả lời:


129

Nhưng bản thân kiểu 'trả lại' thực tế là gì.

Nó không có kiểu, nó không phải là giá trị.

Cố gắng typeof return;sẽ cho bạn Unexpected token return.

Vì vậy, chúng ta có thể chuyển các biểu thức được phân tách bằng dấu phẩy vào câu lệnh return. Đây có phải là một chức năng?

Không, trong khi dấu ngoặc đơn có thể được sử dụng để gọi một hàm, ở đây chúng là một toán tử nhóm chứa một vài biểu thức được phân tách bằng toán tử dấu phẩy .

Một minh chứng hữu ích hơn sẽ là:

function add(a, b) {
  return (
    (a + b),
    (a - b)
  );
}

console.log(add(2, 2));

0Kết quả nào xuất ra vì kết quả của a + bbị bỏ qua (nó nằm trên LHS của toán tử dấu phẩy) và a - bđược trả về.


Tuy nhiên, trong ví dụ trên, từng biểu thức được thực thi, từng biểu thức một. Tôi nghĩ rằng trường hợp bạn nêu khác với những gì được nêu trong câu hỏi ban đầu. Điều tôi muốn nói là ưu tiên của toán tử Nhóm được áp dụng ở đâu.
Arnab Das

3
@ArnabDas - Có, chúng được thực thi, nhưng returnkhông thể nhìn thấy chúng, đó là những gì bạn đang hỏi.
Quentin

@ArnabDas - "Điều tôi muốn nói là mức độ ưu tiên của toán tử Nhóm được áp dụng ở đó." - Trong toán tử nhóm tất nhiên.
Quentin

11
@ArnabDas - Có. a + bchỉ không có hiệu quả rõ ràng vì nó không có tác dụng phụ và giá trị bị loại bỏ (tốt, vì nó không có tác dụng thực tế, có thể một trình biên dịch tối ưu hóa có thể loại bỏ nó, nhưng điều đó không tạo ra sự khác biệt thực tế).
Quentin

1
@Roman chính xác. Cách duy nhất để "bỏ qua" một biểu thức để nó không được thực thi (ngoại trừ bên trong một biểu thức có điều kiện if) là đánh giá ngắn mạch. Toán tử dấu phẩy không ngắn mạch, vì vậy nó sẽ đánh giá cả hai bên. Sau đó, nó sẽ trả về cái cuối cùng. Đó rõ ràng là mục đích của toán tử dấu phẩy, để nhóm các biểu thức trong một câu lệnh duy nhất (thường giá trị kết quả của toàn bộ câu lệnh bị bỏ qua), thường được sử dụng nhất để khởi tạo biến:var i = 2, j = 3, k = 4;
Jason

32

Tôi hơi bị sốc khi không có ai ở đây trực tiếp tham khảo thông số kỹ thuật :

12.9 Câu lệnh return Cú pháp ReturnStatement: return; return [không có LineTerminator ở đây] Biểu thức;

Ngữ nghĩa

Một chương trình ECMAScript được coi là không chính xác về mặt cú pháp nếu nó chứa câu lệnh trả về không nằm trong FunctionBody. Câu lệnh return khiến một hàm ngừng thực thi và trả về một giá trị cho trình gọi. Nếu Biểu thức bị bỏ qua, giá trị trả về là không xác định. Nếu không, giá trị trả về là giá trị của Biểu thức.

ReturnStatement được đánh giá như sau:

Nếu Biểu thức không có mặt, hãy quay lại (return, undefined, empty). Hãy exprReflà kết quả của việc đánh giá Biểu thức. Trả lại (return, GetValue(exprRef), empty).

Vì vậy, do thông số kỹ thuật, ví dụ của bạn có nội dung:

return ( GetValue(exprRef) )

Ở đâu exprRef = console.log(a + b), console.log(arguments)

Mà theo thông số kỹ thuật về toán tử dấu phẩy ...

Ngữ nghĩa

Biểu thức sản xuất: Expression, AssignmentExpression được đánh giá như sau:

Let lref be the result of evaluating Expression.
Call GetValue(lref).
Let rref be the result of evaluating AssignmentExpression.
Return GetValue(rref).

... có nghĩa là mọi biểu thức sẽ được đánh giá cho đến mục cuối cùng trong danh sách dấu phẩy, trở thành biểu thức gán. Vì vậy, mã của bạnreturn (console.log(a + b) , console.log(arguments)) sẽ

1.) in kết quả của a + b

2.) Không còn gì để thực thi, vì vậy hãy thực thi biểu thức tiếp theo

3.) in arguments, và bởi vìconsole.log() không chỉ định câu lệnh trả về

4.) Đánh giá đến không xác định

5.) Sau đó được trả lại cho người gọi.

Vì vậy, câu trả lời chính xác là, return không có kiểu, nó chỉ trả về kết quả của một số biểu thức.

Đối với câu hỏi tiếp theo:

Vì vậy, chúng ta có thể chuyển các biểu thức được phân tách bằng dấu phẩy vào câu lệnh return. Đây có phải là một chức năng?

Không. Dấu phẩy trong JavaScript là một toán tử, được định nghĩa để cho phép bạn kết hợp nhiều biểu thức thành một dòng duy nhất và được xác định bởi đặc tả để trả về biểu thức được đánh giá của bất kỳ thứ gì cuối cùng trong danh sách của bạn.

Bạn vẫn không tin tôi?

<script>
alert(foo());
function foo(){
    var foo = undefined + undefined;
    console.log(foo);
    return undefined, console.log(1), 4;
}
</script>

Chơi với mã đó ở đây và gây rối với giá trị cuối cùng trong danh sách. Nó sẽ luôn trả về giá trị cuối cùng trong danh sách, trong trường hợp của bạn, nó chỉ xảy raundefined.

Đối với câu hỏi cuối cùng của bạn,

Và bắt đầu với điều này, chúng ta có thể đoán rằng mọi từ khóa trong JavaScript cuối cùng đều là một hàm không?

Một lần nữa, không. Các hàm có một định nghĩa rất cụ thể trong ngôn ngữ. Tôi sẽ không in lại nó ở đây vì câu trả lời này đã quá dài.


15

Kiểm tra điều gì sẽ xảy ra khi bạn trả về các giá trị trong ngoặc đơn:

function foo() {
    return (1, 2);
}

console.log(foo());

Đưa ra câu trả lời 2, vì vậy, có vẻ như danh sách giá trị được phân tách bằng dấu phẩy sẽ đánh giá phần tử cuối cùng trong danh sách.

Thực sự, dấu ngoặc đơn không liên quan ở đây, chúng đang nhóm các hoạt động thay vì biểu thị một lời gọi hàm. Tuy nhiên, điều có thể đáng ngạc nhiên là dấu phẩy hợp pháp ở đây. Tôi đã tìm thấy một bài đăng trên blog thú vị về cách xử lý dấu phẩy ở đây:

https://javascriptweblog.wordpress.com/2011/04/04/the-javascript-comma-operator/


Tôi đoán sau đó, một cái gì đó như return ([1, 2])nên in cả hai giá trị?
cst1992

1
Điều đó sẽ trả về một mảng.
Bảo hiểm Kiểm tra We Stan

console.log((1, 2))
thedayturns

9

returnkhông phải là một chức năng. Đó là sự tiếp tục của chức năng mà nó xảy ra.

Hãy nghĩ về câu lệnh alert (2 * foo(bar));đâu foolà tên của một hàm. Khi bạn đánh giá nó, bạn thấy rằng bạn phải dành phần còn lại của tuyên bố một chút thời gian để tập trung đánh giá foo(bar). Bạn có thể hình dung phần bạn đặt sang một bên như thế nào alert (2 * _), với một khoảng trống để điền vào. Khi bạn biết giá trị củafoo(bar) nó, bạn chọn nó lại.

Điều bạn đặt sang một bên là tiếp tục cuộc gọi foo(bar).

Kêu gọi return cung cấp một giá trị cho sự tiếp tục đó.

Khi bạn đánh giá một hàm bên trong foo, phần còn lại foochờ hàm đó giảm xuống một giá trị, rồi foochọn lại. Bạn vẫn còn một mục tiêu để đánh giá foo(bar), nó chỉ bị tạm dừng.

Khi bạn đánh giá returnbên trong foo, không có phần nào foochờ đợi xung quanh một giá trị. returnkhông giảm xuống một giá trị tại foonơi bạn đã sử dụng nó. Thay vào đó, nó khiến toàn bộ cuộc gọi foo(bar) giảm xuống một giá trị và mục tiêu "đánh giáfoo(bar) " được coi là hoàn thành và bị thổi bay.

Mọi người thường không nói với bạn về sự liên tục khi bạn mới làm quen với lập trình. Họ nghĩ về nó như một chủ đề nâng cao, chỉ vì có được một số thứ rất tiên tiến mà mọi người cuối cùng làm với continuations. Nhưng sự thật là bạn đang sử dụng tất cả chúng, mỗi khi bạn gọi một hàm.


Kiểu suy nghĩ này phổ biến với các ngôn ngữ chức năng, như LISP và Haskell, nhưng tôi chưa bao giờ thấy nó được sử dụng trong các ngôn ngữ thủ tục như javascript. Người ta thường không nghĩ về chúng như là sự liên tục bởi vì hầu hết các ngôn ngữ thủ tục coi chúng như một trường hợp đặc biệt. Ví dụ: bạn không thể lưu phần tiếp theo alert(2 * _)để sử dụng sau này trong Javascript (có một ký hiệu khác để làm điều đó).
Cort Ammon

1
@CortAmmon Mặc dù tôi đồng ý với bạn, lưu ý rằng Javascript có một nền tảng chức năng khá mạnh (ban đầu nó được dự định là triển khai một tập hợp con Đề án với cú pháp giống C) và mặc dù nó không có bất kỳ chức năng tiêu chuẩn nào cho continuations thao tác (ví dụ như cuộc gọi / cc) nó thực tế phổ biến để tiếp tục sử dụng thông qua phong cách trong nhiều thư viện javascript, vì vậy việc hiểu các khái niệm ở giai đoạn đầu là khá hữu ích cho các lập trình JS.
Jules

Đúng là Javascript không thực hiện liên tục hạng nhất . Các returntừ bên trong fookhông phải là một giá trị; bạn không thể đóng gói nó trong một cấu trúc dữ liệu, gán nó cho một biến, đợi một lúc rồi nạp thứ gì đó cho nó sau - và đặc biệt là bạn không thể nạp nó nhiều lần. Nhưng, như tôi đã nói, các chủ đề nâng cao.
Nathan Ellis Rasmussen

Tương tự như vậy, triển khai một cấu trúc điều khiển mới bằng cách sử dụng tính liên tục: nâng cao. Nhưng cố gắng triển khai một my_ifchức năng hoạt động giống như được tích hợp sẵn if()then{}else{}và không làm được như vậy ngay cả khi bạn cho phép mình sử dụng chức năng được tích hợp sẵn bên trong nó - không quá nâng cao và rất có tính hướng dẫn, đặc biệt nếu bạn sắp kết thúc lên trong mã chuyển xung quanh các cuộc gọi lại.
Nathan Ellis Rasmussen

6

những returnđây là một cá trích đỏ. Có lẽ thú vị là sự biến đổi sau đây:

function add(a, b) {
  return (
    console.log(a + b),
    console.log(arguments)
  );
}

console.log(add(2, 2));

đầu ra là dòng cuối cùng

undefined

vì hàm thực sự không trả về bất cứ thứ gì. (Nó sẽ trả về giá trị trả về của giây console.log, nếu có).

Vì nó là, mã hoàn toàn giống với

function add(a, b) {
    console.log(a + b);
    console.log(arguments);
}

console.log(add(2, 2));

1

Một cách thú vị để hiểu câu lệnh return là thông qua toán tử void Hãy xem đoạn mã này

var console = {
    log: function(s) {
      document.getElementById("console").innerHTML += s + "<br/>"
    }
}

function funReturnUndefined(x,y) {
   return ( void(x+y) );
}

function funReturnResult(x,y) {
   return ( (x+y) );
}

console.log( funReturnUndefined(2,3) );
console.log( funReturnResult(2,3) );
<div id="console" />

returncâu lệnh nhận một đối số là [[expression]]và trả lại điều này cho người gọi trên ngăn xếp, tức là arguments.callee.callernó sẽ thực thi void(expression)và sau đó trả về undefinedđó là đánh giá của toán tử void.

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.