Hướng dẫn javascript access global variable from callback - javascript truy cập biến toàn cầu từ gọi lại

Đối với đăng ký người dùng của tôi, tôi có

Show
const express = require ('express');
const userRouter = express.Router ();

userRouter.get ('/', function getUserList (req, res) {
    let User = require ('../models').User;
    User.find ({}, function (err, list) {
        res.json (list);
    });
});

userRouter.post ('/', function createUser (req, res) {
    let User = require ('../models').User;
    if (req.body.username && req.body.password)
        User.create (req.body, function (err, user) {
            res.json (user);
        });
});

... 3 more functions with the same `let User` ...

module.exports = userRouter;

Ở đây, tôi phải require các mô hình mô -đun hai lần. Tôi đã thử đặt biến người dùng thành biến toàn cầu ở đầu chương trình, như

const express = ..., userRouter = ...
var User = ...

Tuy nhiên, biến người dùng này vẫn không thể truy cập được bên trong các chức năng gọi lại của tôi.

Là yêu cầu mô -đun người dùng nhiều lần cách chính xác để làm điều này hay tôi đang thiếu một cái gì đó?

Chỉnh sửa: Bên trong các chức năng gọi lại, biến người dùng không được xác định.

Giới thiệu

Nếu bạn đã từng trải qua vấn đề JavaScript này, bạn không đơn độc - nó sẽ đưa ra nhiều người và có thể khó khăn, lúc đầu, để hiểu chính xác làm thế nào để sửa chữa nó. Hãy xem xét ví dụ này:

1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}

JavaScript

Mặc dù mã này trông hoàn toàn tốt, nhưng nó cho thấy sự hiểu lầm của một khái niệm JavaScript rất cơ bản. Bây giờ, nếu bạn thành thạo JavaScript, lỗi này sẽ đủ dễ dàng để phát hiện. Nhưng đối với hầu hết mọi người, điều này không phải là trường hợp - một số người thực sự có thể dành hàng giờ để cố gắng tìm ra lý do tại sao mã của họ không hoạt động.

Giải trình

Hãy nhớ những hướng dẫn đầu tiên của JavaScript bạn đã đọc, nơi nó nói rằng JavaScript không đồng bộ? Điều này có nghĩa là trong một số trường hợp mã có thể không được thực thi theo tuần tự. Đây thường là trường hợp khi sử dụng API nội bộ phụ thuộc vào một sự kiện bên ngoài. Ví dụ: xử lý phản hồi sau khi yêu cầu HTTP được hoàn thành hoặc sau khi một số xử lý khác được thực hiện.

Vì vậy, những gì xảy ra sau đó, là doSthWithCallbacks (biểu thức chung cho tất cả các hàm JavaScript sử dụng một cuộc gọi lại) lên lịch chức năng gọi lại sẽ được thực thi ở giai đoạn sau. Nhưng vòng lặp

const express = ..., userRouter = ...
var User = ...
0 không chỉ lên lịch một cuộc gọi lại. Đó là lên lịch các cuộc gọi lại giá trị
const express = ..., userRouter = ...
var User = ...
1 và chúng chắc chắn sẽ không được hoàn thành trong cùng một lần lặp vòng lặp
const express = ..., userRouter = ...
var User = ...
0. Mỗi cuộc gọi lại sẽ được thực hiện vào một thời điểm không thể đoán trước sau đó, khi nhiều lần lặp
const express = ..., userRouter = ...
var User = ...
0 đã được thực hiện, giá trị nếu
const express = ..., userRouter = ...
var User = ...
4 là khác nhau và nhiều cuộc gọi lại khác cũng đã được lên lịch.doSthWithCallbacks (general expression for all JavaScript function that use a callback) schedules the callback function to be executed at a later stage. But the
const express = ..., userRouter = ...
var User = ...
0
loop isn't just scheduling one callback. It's scheduling an
const express = ..., userRouter = ...
var User = ...
1
worth of callbacks and they most certainly won't be completed within the same
const express = ..., userRouter = ...
var User = ...
0
loop iteration. Each of those callbacks will be executed at an unpredictable time later on, when multiple
const express = ..., userRouter = ...
var User = ...
0
iterations have gone through, the value if
const express = ..., userRouter = ...
var User = ...
4
is different and multiple other callbacks have also been scheduled.

Thông thường, các cuộc gọi lại không được thực thi cho đến khi vòng lặp

const express = ..., userRouter = ...
var User = ...
0 hoàn thành, tại thời điểm đó
const express = ..., userRouter = ...
var User = ...
4 chính xác bằng
const express = ..., userRouter = ...
var User = ...
7. Vì vậy, mỗi khi bất kỳ cuộc gọi lại nào được thực thi, nó sẽ được sửa đổi giá trị cuối cùng của mảng thay vì giá trị của vòng lặp vòng lặp ____10 mà nó đã được lên lịch. Tất nhiên, như tôi đã nói, không thể đoán trước được khi các cuộc gọi lại sẽ được thực thi và phụ thuộc vào nhiều yếu tố mà trình thông dịch JavaScript đã sử dụng, chức năng gọi các cuộc gọi lại và dữ liệu đầu vào. Một ví dụ là một yêu cầu HTTP với một cuộc gọi lại thành công sẽ không được thực thi trước khi máy chủ gửi phản hồi, có thể là bất kỳ khoảng thời gian nào giữa vài mili giây và vài phút.
const express = ..., userRouter = ...
var User = ...
0
loop has completed, at which point
const express = ..., userRouter = ...
var User = ...
4
is exactly equal to
const express = ..., userRouter = ...
var User = ...
7
. So, every time any of the callbacks is executed it will be modifying the last value of the array instead of the value of the
const express = ..., userRouter = ...
var User = ...
0
loop iteration it was scheduled on. Of course, as I said, it's unpredictable when the callbacks will be executed and depends on multiple factors the JavaScript interpreter used, the function invoking the callbacks and it's input data. An example is an HTTP request with a success callback that won't be executed before the server sends a response, which could be any time interval between several milliseconds and several minutes.

Làm thế nào để làm việc xung quanh nó

Tôi sẽ trình bày cho bạn hai giải pháp về cách đi xung quanh vấn đề. Cả hai đều rất hiệu quả về mặt hiệu suất và các kết quả bộ nhớ. Phương pháp đầu tiên dễ hiểu hơn, nhưng yêu cầu định nghĩa về hàm trong phạm vi trên, điều này làm cho mã không dễ đọc hơn vì bạn phải tra cứu hàm. Giải pháp thứ hai là sở thích cá nhân của tôi nhưng khó hiểu hơn, đặc biệt là khi bạn nhìn thấy các cấu trúc như vậy lần đầu tiên. Có những giải pháp khác, nhưng những điều này tại thời điểm này là nhanh nhất và được hỗ trợ trong tất cả các trình duyệt chính và phiên dịch JavaScript.

Về cơ bản, cả hai phương pháp đều làm cùng một nhiệm vụ nhưng theo những cách khác nhau. Những gì họ làm là tạo ra một chức năng gọi lại riêng với bản sao giá trị của chính họ trong một phạm vi chỉ có sẵn cho họ.

const express = ..., userRouter = ...
var User = ...
4 in a scope only available to them.

Đóng trong một chức năng

Phương pháp này tương đối dễ hiểu và đó là lý do tại sao tôi sẽ không bao gồm nó một cách chi tiết. Đây không phải là trường hợp đóng cửa nội tuyến, vì vậy tôi sẽ đề cập đến độ sâu đó. Hàm

1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
0 trả về một hàm gọi lại cuộc gọi lại thực tế với một bản sao rõ ràng của
const express = ..., userRouter = ...
var User = ...
4 làm đối số.
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
0
function returns a function that invokes the actual callback with an explicit copy of
const express = ..., userRouter = ...
var User = ...
4
as an argument.

1var array = [ ... ]; // An array with some objects
2
3function callbackClosure(i, callback) {
4  return function() {
5    return callback(i);
6  }
7}
8
9for( var i = 0; i < array.length; ++i )
10{
11  API.doSthWithCallbacks( callbackClosure( i, function(i) {
12    array[i].something = 42;
13  }) );
14}

JavaScript

Vì mỗi hàm tuyên bố phạm vi riêng của nó và

const express = ..., userRouter = ...
var User = ...
4 có loại nguyên tử cơ sở (
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
3), nó không được truyền như một tài liệu tham khảo, mà là một bản sao (không giống như các đối tượng) đảm bảo rằng cuộc gọi lại thực tế sẽ được thực thi theo giá trị chính xác.
const express = ..., userRouter = ...
var User = ...
4
has a base atomic type (
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
3
) it is not passed as a reference, but rather as a copy (unlike objects) which ensures that the actual callback will be executed against the correct value.

Đóng cửa nội tuyến

Điều này đưa chúng ta đến hack JavaScript yêu thích nhất của tôi. Điều này được thực hiện bằng cách tuyên bố một chức năng tự ẩn danh, thường trông như thế này:

1(function() {
2  // Something declared here will only be available to the function below.
3  // Code here is executed only once upon the creation of the inner function
4  return function(callbackArguments) {
5    // Actual callback here
6  };
7})(); // The last brackets execute the outer function

JavaScript

Vì mỗi hàm tuyên bố phạm vi riêng của nó và

const express = ..., userRouter = ...
var User = ...
4 có loại nguyên tử cơ sở (
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
3), nó không được truyền như một tài liệu tham khảo, mà là một bản sao (không giống như các đối tượng) đảm bảo rằng cuộc gọi lại thực tế sẽ được thực thi theo giá trị chính xác.
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
4
which is the exact type a callback should be. So, applying this to the previous example we arrive here:

1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  API.doSthWithCallbacks( (function() {
5    var j = i; // j is a copy of i only available to the scope of the inner function
6    return function() {
7      array[j].something = 42;
8    }
9  })() );
10}

JavaScript

Vì mỗi hàm tuyên bố phạm vi riêng của nó và

const express = ..., userRouter = ...
var User = ...
4 có loại nguyên tử cơ sở (
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
3), nó không được truyền như một tài liệu tham khảo, mà là một bản sao (không giống như các đối tượng) đảm bảo rằng cuộc gọi lại thực tế sẽ được thực thi theo giá trị chính xác.
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
5
is equal to
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
6
that means that you are currently processing the last callback.

1var array = [ ... ]; // An array with some objects
2var count = 0, length = array.length;
3for( var i = 0; i < array.length; ++i )
4{
5  API.doSthWithCallbacks( (function() {
6    var j = i; // A copy of i only available to the scope of the inner function
7    return function() {
8      array[j].something = 42;
9
10      ++count;
11      if( count == length ) {
12        // Code executed only after all the processing tasks have been completed
13      }
14    }
15  })() );
16}

JavaScript

Vì mỗi hàm tuyên bố phạm vi riêng của nó và

const express = ..., userRouter = ...
var User = ...
4 có loại nguyên tử cơ sở (
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
3), nó không được truyền như một tài liệu tham khảo, mà là một bản sao (không giống như các đối tượng) đảm bảo rằng cuộc gọi lại thực tế sẽ được thực thi theo giá trị chính xác.
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
7
operation atomic? A Race Condition could occur and the code might be executed multiple times or, worse, not executed at all. Some consider something like a mutex or a semaphore. But this isn't right.

Đóng cửa nội tuyến

ES6

Điều này đưa chúng ta đến hack JavaScript yêu thích nhất của tôi. Điều này được thực hiện bằng cách tuyên bố một chức năng tự ẩn danh, thường trông như thế này:

1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
8 keyword which allows you to declare a variable scoped to the nearest enclosing block and not global like
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
9
does. Thus the closure problem can be solved simply by replacing
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
9
with
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
8
:

1var array = [ ... ]; // An array with some objects
2for( let i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}

Lưu ý rằng hàm bên ngoài chỉ được sử dụng để đóng gói hàm bên trong và tạo phạm vi biến riêng cho hàm bên trong. Ngoài ra, hàm bên ngoài trả về giá trị của loại

1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
4, đó là loại gọi lại chính xác. Vì vậy, áp dụng điều này vào ví dụ trước chúng tôi đến đây:

Ví dụ, nếu bạn phải thực hiện một số xử lý không đồng bộ, và cần có một số mã tổng hợp chỉ nên được chạy sau khi tất cả các cuộc gọi lại đã được hoàn thành trong số đó đã được hoàn thành. Nếu

1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
5 bằng
1var array = [ ... ]; // An array with some objects
2for( var i = 0; i < array.length; ++i )
3{
4  $.doSthWithCallbacks( function() {
5    array[i].something = 42;
6  });
7}
6, điều đó có nghĩa là bạn hiện đang xử lý cuộc gọi lại cuối cùng.

Bây giờ, tại thời điểm này, thật dễ dàng để bị nhầm lẫn. Là hoạt động 1var array = [ ... ]; // An array with some objects 2for( var i = 0; i < array.length; ++i ) 3{ 4 $.doSthWithCallbacks( function() { 5 array[i].something = 42; 6 }); 7}7 nguyên tử? Một điều kiện cuộc đua có thể xảy ra và mã có thể được thực thi nhiều lần hoặc tệ hơn, hoàn toàn không được thực thi. Một số người coi một cái gì đó như một mutex hoặc semaphore. Nhưng điều này không đúng.

Mặc dù JavaScript không đồng bộ, nhưng nó không được đa luồng. Trên thực tế, mặc dù không thể dự đoán khi nào một cuộc gọi lại sẽ được thực thi, nhưng có thể đảm bảo rằng một điều kiện cuộc đua sẽ không xảy ra vì JavaScript chỉ chạy trong một luồng. .

ECMAScript 6 giới thiệu từ khóa 1var array = [ ... ]; // An array with some objects 2for( var i = 0; i < array.length; ++i ) 3{ 4 $.doSthWithCallbacks( function() { 5 array[i].something = 42; 6 }); 7}8 cho phép bạn khai báo một biến được phân chia đến khối kèm theo gần nhất và không toàn cầu như 1var array = [ ... ]; // An array with some objects 2for( var i = 0; i < array.length; ++i ) 3{ 4 $.doSthWithCallbacks( function() { 5 array[i].something = 42; 6 }); 7}9. Do đó, vấn đề đóng có thể được giải quyết chỉ bằng cách thay thế 1var array = [ ... ]; // An array with some objects 2for( var i = 0; i < array.length; ++i ) 3{ 4 $.doSthWithCallbacks( function() { 5 array[i].something = 42; 6 }); 7}9 bằng 1var array = [ ... ]; // An array with some objects 2for( var i = 0; i < array.length; ++i ) 3{ 4 $.doSthWithCallbacks( function() { 5 array[i].something = 42; 6 }); 7}8:

JS

Gọn gàng, phải không? Nếu bạn có thể sử dụng ES6, mã của bạn có thể được thực hiện để trông tốt hơn rất nhiều mà không cần phải đánh giá cao nó ngay lập tức được gọi chức năng nội tuyến ẩn danh chỉ để tạo ra một phạm vi mới.

1var array = [ ... ]; // An array with some objects
2
3function callbackClosure(i, callback) {
4  return function() {
5    return callback(i);
6  }
7}
8
9for( var i = 0; i < array.length; ++i )
10{
11  API.doSthWithCallbacks( callbackClosure( i, function(i) {
12    array[i].something = 42;
13  }) );
14}
2 to
1var array = [ ... ]; // An array with some objects
2
3function callbackClosure(i, callback) {
4  return function() {
5    return callback(i);
6  }
7}
8
9for( var i = 0; i < array.length; ++i )
10{
11  API.doSthWithCallbacks( callbackClosure( i, function(i) {
12    array[i].something = 42;
13  }) );
14}
3
, crazy about efficiency and beautiful code. His favorite technologies are
1var array = [ ... ]; // An array with some objects
2
3function callbackClosure(i, callback) {
4  return function() {
5    return callback(i);
6  }
7}
8
9for( var i = 0; i < array.length; ++i )
10{
11  API.doSthWithCallbacks( callbackClosure( i, function(i) {
12    array[i].something = 42;
13  }) );
14}
4
,
1var array = [ ... ]; // An array with some objects
2
3function callbackClosure(i, callback) {
4  return function() {
5    return callback(i);
6  }
7}
8
9for( var i = 0; i < array.length; ++i )
10{
11  API.doSthWithCallbacks( callbackClosure( i, function(i) {
12    array[i].something = 42;
13  }) );
14}
5
and
1var array = [ ... ]; // An array with some objects
2
3function callbackClosure(i, callback) {
4  return function() {
5    return callback(i);
6  }
7}
8
9for( var i = 0; i < array.length; ++i )
10{
11  API.doSthWithCallbacks( callbackClosure( i, function(i) {
12    array[i].something = 42;
13  }) );
14}
6
.

Chức năng gọi lại có thể truy cập biến toàn cầu không?

Điều này có nghĩa là cuộc gọi lại là một đóng cửa.Việc đóng có quyền truy cập vào phạm vi của hàm chứa, do đó hàm gọi lại có thể truy cập các biến của các hàm chứa và thậm chí các biến từ phạm vi toàn cầu.the callback function can access the containing functions' variables, and even the variables from the global scope.

Chức năng JavaScript có thể truy cập các biến toàn cầu không?

Các biến toàn cầu có thể được truy cập từ bất cứ đâu trong chương trình JavaScript.Các biến được khai báo với VAR, LET và Const khá giống nhau khi được khai báo bên ngoài một khối.. Variables declared with var , let and const are quite similar when declared outside a block.