Gần đây tôi đã đọc rất nhiều Javascript và tôi đã nhận thấy rằng toàn bộ tệp được gói như sau trong các tệp .js sẽ được nhập.
(function() {
...
code
...
})();
Lý do để làm điều này hơn là một tập hợp các hàm xây dựng đơn giản là gì?
Gần đây tôi đã đọc rất nhiều Javascript và tôi đã nhận thấy rằng toàn bộ tệp được gói như sau trong các tệp .js sẽ được nhập.
(function() {
...
code
...
})();
Lý do để làm điều này hơn là một tập hợp các hàm xây dựng đơn giản là gì?
Câu trả lời:
Nó thường là không gian tên (xem sau) và kiểm soát mức độ hiển thị của các hàm thành viên và / hoặc các biến. Hãy nghĩ về nó giống như một định nghĩa đối tượng. Tên kỹ thuật của nó là Biểu thức hàm được gọi ngay lập tức (IIFE). Các plugin jQuery thường được viết như thế này.
Trong Javascript, bạn có thể lồng các hàm. Vì vậy, sau đây là hợp pháp:
function outerFunction() {
function innerFunction() {
// code
}
}
Bây giờ bạn có thể gọi outerFunction()
, nhưng độ nhớt của innerFunction()
giới hạn trong phạm vi outerFunction()
, có nghĩa là nó là riêng tư outerFunction()
. Về cơ bản, nó tuân theo nguyên tắc giống như các biến trong Javascript:
var globalVariable;
function someFunction() {
var localVariable;
}
Tương ứng:
function globalFunction() {
var localFunction1 = function() {
//I'm anonymous! But localFunction1 is a reference to me!
};
function localFunction2() {
//I'm named!
}
}
Trong trường hợp trên, bạn có thể gọi globalFunction()
từ bất cứ đâu, nhưng bạn không thể gọi localFunction1
hoặc localFunction2
.
Những gì bạn đang làm khi bạn viết (function() { ... })()
, là bạn đang tạo mã bên trong bộ dấu ngoặc đơn đầu tiên một hàm theo nghĩa đen (có nghĩa là toàn bộ "đối tượng" thực sự là một hàm). Sau đó, bạn tự gọi hàm (cuối cùng ()
) mà bạn vừa xác định. Vì vậy, ưu điểm chính của điều này như tôi đã đề cập trước đây, là bạn có thể có các phương thức / hàm và thuộc tính riêng tư:
(function() {
var private_var;
function private_function() {
//code
}
})();
Trong ví dụ đầu tiên, bạn sẽ gọi một cách rõ ràng globalFunction
bằng tên để chạy nó. Đó là, bạn sẽ chỉ làm globalFunction()
để chạy nó. Nhưng trong ví dụ trên, bạn không chỉ xác định hàm; bạn đang xác định và gọi nó trong một lần. Điều này có nghĩa là khi tệp JavaScript của bạn được tải, nó sẽ được thực thi ngay lập tức. Tất nhiên, bạn có thể làm:
function globalFunction() {
// code
}
globalFunction();
Hành vi phần lớn sẽ giống nhau ngoại trừ một điểm khác biệt đáng kể: bạn tránh gây ô nhiễm phạm vi toàn cầu khi bạn sử dụng IIFE (do đó, điều đó cũng có nghĩa là bạn không thể gọi hàm nhiều lần vì nó không có tên, nhưng vì chức năng này chỉ có nghĩa là được thực thi khi nó thực sự không phải là vấn đề).
Điều thú vị với IIFE là bạn cũng có thể xác định những thứ bên trong và chỉ để lộ những phần bạn muốn ra thế giới bên ngoài (ví dụ về không gian tên để bạn có thể tạo thư viện / plugin của riêng mình):
var myPlugin = (function() {
var private_var;
function private_function() {
}
return {
public_function1: function() {
},
public_function2: function() {
}
}
})()
Bây giờ bạn có thể gọi myPlugin.public_function1()
, nhưng bạn không thể truy cập private_function()
! Vì vậy, khá giống với một định nghĩa lớp. Để hiểu rõ hơn về điều này, tôi khuyên bạn nên sử dụng các liên kết sau để đọc thêm:
BIÊN TẬP
Tôi đã quên không đề cập. Trong trận chung kết đó ()
, bạn có thể vượt qua bất cứ điều gì bạn muốn bên trong. Ví dụ: khi bạn tạo các trình cắm jQuery, bạn chuyển vào jQuery
hoặc $
tương tự như vậy:
(function(jQ) { ... code ... })(jQuery)
Vì vậy, những gì bạn đang làm ở đây là xác định một hàm có một tham số (được gọi là jQ
biến cục bộ và chỉ được biết đến với hàm đó). Sau đó, bạn tự gọi hàm và truyền vào một tham số (còn được gọi jQuery
, nhưng hàm này là từ thế giới bên ngoài và tham chiếu đến chính jQuery thực tế). Không cần phải làm điều này, nhưng có một số lợi thế:
Trước đó tôi đã mô tả làm thế nào các chức năng này chạy tự động khi khởi động, nhưng nếu chúng chạy tự động thì ai sẽ chuyển qua các đối số? Kỹ thuật này giả định rằng tất cả các tham số bạn cần đã được xác định là biến toàn cục. Vì vậy, nếu jQuery chưa được định nghĩa là biến toàn cục thì ví dụ này sẽ không hoạt động. Như bạn có thể đoán, một điều jquery.js thực hiện trong quá trình khởi tạo của nó là xác định biến toàn cầu 'jQuery', cũng như biến toàn cục '$' nổi tiếng hơn của nó, cho phép mã này hoạt động sau khi jQuery được đưa vào.
;(function(jQ) { ... code ... })(jQuery);
Bằng cách này nếu ai đó bỏ dấu chấm phẩy trong tập lệnh của họ, nó sẽ không phá vỡ tập lệnh của bạn, đặc biệt nếu bạn dự định thu nhỏ và ghép tập lệnh của bạn với tập lệnh khác.
(function (context) { ..... })(this)
sau đó cho phép bạn đính kèm bất cứ điều gì bạn thích vào bối cảnh cha mẹ do đó phơi bày nó.
Ở dạng đơn giản nhất, kỹ thuật này nhằm mục đích bọc mã bên trong một phạm vi chức năng .
Nó giúp giảm cơ hội:
Nó không phát hiện khi tài liệu đã sẵn sàng - nó không phải là một loại document.onload
cũng khôngwindow.onload
Nó thường được gọi là một Immediately Invoked Function Expression (IIFE)
hoặc Self Executing Anonymous Function
.
var someFunction = function(){ console.log('wagwan!'); };
(function() { /* function scope starts here */
console.log('start of IIFE');
var myNumber = 4; /* number variable declaration */
var myFunction = function(){ /* function variable declaration */
console.log('formidable!');
};
var myObject = { /* object variable declaration */
anotherNumber : 1001,
anotherFunc : function(){ console.log('formidable!'); }
};
console.log('end of IIFE');
})(); /* function scope ends */
someFunction(); // reachable, hence works: see in the console
myFunction(); // unreachable, will throw an error, see in the console
myObject.anotherFunc(); // unreachable, will throw an error, see in the console
Trong ví dụ trên, bất kỳ biến nào được xác định trong hàm (nghĩa là được khai báo sử dụng var
) sẽ là "riêng tư" và có thể truy cập trong phạm vi hàm CHỈ (như Vivin Paliath đặt nó). Nói cách khác, các biến này không thể nhìn thấy / có thể truy cập bên ngoài hàm. Xem bản demo trực tiếp .
Javascript có chức năng phạm vi. "Các tham số và biến được định nghĩa trong hàm không hiển thị bên ngoài hàm và rằng một biến được xác định ở bất kỳ đâu trong hàm có thể nhìn thấy ở mọi nơi trong hàm." (từ "Javascript: Bộ phận tốt").
Cuối cùng, mã được đăng trước đó cũng có thể được thực hiện như sau:
var someFunction = function(){ console.log('wagwan!'); };
var myMainFunction = function() {
console.log('start of IIFE');
var myNumber = 4;
var myFunction = function(){ console.log('formidable!'); };
var myObject = {
anotherNumber : 1001,
anotherFunc : function(){ console.log('formidable!'); }
};
console.log('end of IIFE');
};
myMainFunction(); // I CALL "myMainFunction" FUNCTION HERE
someFunction(); // reachable, hence works: see in the console
myFunction(); // unreachable, will throw an error, see in the console
myObject.anotherFunc(); // unreachable, will throw an error, see in the console
Một ngày nọ, có lẽ ai đó đã nghĩ rằng "phải có một cách để tránh đặt tên 'myMainFunction', vì tất cả những gì chúng ta muốn là thực hiện nó ngay lập tức."
Nếu bạn quay lại những điều cơ bản, bạn sẽ thấy rằng:
expression
: một cái gì đó đánh giá đến một giá trị. I E3+11/x
statement
: dòng (s) mã đang làm gì đó NHƯNG nó không đánh giá thành giá trị. I Eif(){}
Tương tự, các biểu thức hàm đánh giá một giá trị. Và một hậu quả (tôi giả sử?) Là chúng có thể được gọi ngay lập tức:
var italianSayinSomething = function(){ console.log('mamamia!'); }();
Vì vậy, ví dụ phức tạp hơn của chúng tôi trở thành:
var someFunction = function(){ console.log('wagwan!'); };
var myMainFunction = function() {
console.log('start of IIFE');
var myNumber = 4;
var myFunction = function(){ console.log('formidable!'); };
var myObject = {
anotherNumber : 1001,
anotherFunc : function(){ console.log('formidable!'); }
};
console.log('end of IIFE');
}();
someFunction(); // reachable, hence works: see in the console
myFunction(); // unreachable, will throw an error, see in the console
myObject.anotherFunc(); // unreachable, will throw an error, see in the console
Bước tiếp theo là suy nghĩ "tại sao lại có var myMainFunction =
nếu chúng ta thậm chí không sử dụng nó!?".
Câu trả lời rất đơn giản: hãy thử loại bỏ điều này, chẳng hạn như dưới đây:
function(){ console.log('mamamia!'); }();
Nó sẽ không hoạt động vì "khai báo hàm không thể xâm phạm" .
Thủ thuật là bằng cách loại bỏ var myMainFunction =
chúng ta đã chuyển đổi biểu thức hàm thành khai báo hàm . Xem các liên kết trong "Tài nguyên" để biết thêm chi tiết về điều này.
Câu hỏi tiếp theo là "tại sao tôi không thể giữ nó như một biểu thức chức năng với cái gì khác hơn var myMainFunction =
?
Câu trả lời là "bạn có thể", và thực tế có nhiều cách bạn có thể làm điều này: thêm a +
, a !
, a -
hoặc có thể gói trong một cặp dấu ngoặc đơn (như bây giờ được thực hiện theo quy ước), và tôi tin hơn. Ví dụ như:
(function(){ console.log('mamamia!'); })(); // live demo: jsbin.com/zokuwodoco/1/edit?js,console.
hoặc là
+function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wuwipiyazi/1/edit?js,console
hoặc là
-function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wejupaheva/1/edit?js,console
Vì vậy, một khi sửa đổi có liên quan được thêm vào cái đã từng là "Mã thay thế" của chúng tôi, chúng tôi sẽ quay lại mã chính xác giống như mã được sử dụng trong ví dụ "Giải thích mã"
var someFunction = function(){ console.log('wagwan!'); };
(function() {
console.log('start of IIFE');
var myNumber = 4;
var myFunction = function(){ console.log('formidable!'); };
var myObject = {
anotherNumber : 1001,
anotherFunc : function(){ console.log('formidable!'); }
};
console.log('end of IIFE');
})();
someFunction(); // reachable, hence works: see in the console
myFunction(); // unreachable, will throw an error, see in the console
myObject.anotherFunc(); // unreachable, will throw an error, see in the console
Đọc thêm về Expressions vs Statements
:
Một điều người ta có thể tự hỏi là "điều gì xảy ra khi bạn KHÔNG định nghĩa biến 'đúng' bên trong hàm - tức là thực hiện một phép gán đơn giản?"
(function() {
var myNumber = 4; /* number variable declaration */
var myFunction = function(){ /* function variable declaration */
console.log('formidable!');
};
var myObject = { /* object variable declaration */
anotherNumber : 1001,
anotherFunc : function(){ console.log('formidable!'); }
};
myOtherFunction = function(){ /* oops, an assignment instead of a declaration */
console.log('haha. got ya!');
};
})();
myOtherFunction(); // reachable, hence works: see in the console
window.myOtherFunction(); // works in the browser, myOtherFunction is then in the global scope
myFunction(); // unreachable, will throw an error, see in the console
Về cơ bản, nếu một biến không được khai báo trong phạm vi hiện tại của nó được gán một giá trị, thì "việc tra cứu chuỗi phạm vi xảy ra cho đến khi tìm thấy biến hoặc chạm vào phạm vi toàn cầu (tại đó nó sẽ tạo ra nó)".
Khi ở trong môi trường trình duyệt (so với môi trường máy chủ như nodejs), phạm vi toàn cục được xác định bởi window
đối tượng. Do đó chúng ta có thể làm window.myOtherFunction()
.
Mẹo "Thực hành tốt" của tôi về chủ đề này là luôn luôn sử dụng var
khi xác định bất cứ điều gì : cho dù đó là số, đối tượng hoặc chức năng, và ngay cả khi trong phạm vi toàn cầu. Điều này làm cho mã đơn giản hơn nhiều.
Ghi chú:
block scope
(Cập nhật: chặn các biến cục bộ phạm vi được thêm vào trong ES6 .)function scope
& global scope
( window
phạm vi trong môi trường trình duyệt)Đọc thêm về Javascript Scopes
:
Khi bạn có được IIFE
khái niệm này , nó sẽ dẫn đến module pattern
, thường được thực hiện bằng cách tận dụng mẫu IIFE này. Chúc vui vẻ :)
Javascript trong trình duyệt chỉ thực sự có một vài phạm vi hiệu quả: phạm vi chức năng và phạm vi toàn cầu.
Nếu một biến không nằm trong phạm vi chức năng, thì đó là trong phạm vi toàn cầu. Và các biến toàn cục nói chung là xấu, vì vậy đây là một cấu trúc để giữ các biến của thư viện cho chính nó.
Đó gọi là đóng cửa. Về cơ bản, nó niêm phong mã bên trong hàm để các thư viện khác không can thiệp vào nó. Nó tương tự như tạo một không gian tên trong các ngôn ngữ được biên dịch.
Thí dụ. Giả sử tôi viết:
(function() {
var x = 2;
// do stuff with x
})();
Bây giờ các thư viện khác không thể truy cập vào biến x
tôi đã tạo để sử dụng trong thư viện của mình.
(function(){ ... return { publicProp1: 'blah' }; })();
. Rõ ràng không hoàn toàn song song với không gian tên, nhưng nó có thể giúp nghĩ về nó theo cách đó.
Bạn cũng có thể sử dụng các hàm đóng như dữ liệu trong các biểu thức lớn hơn, như trong phương pháp xác định hỗ trợ trình duyệt này cho một số đối tượng html5.
navigator.html5={
canvas: (function(){
var dc= document.createElement('canvas');
if(!dc.getContext) return 0;
var c= dc.getContext('2d');
return typeof c.fillText== 'function'? 2: 1;
})(),
localStorage: (function(){
return !!window.localStorage;
})(),
webworkers: (function(){
return !!window.Worker;
})(),
offline: (function(){
return !!window.applicationCache;
})()
}
Ngoài việc giữ các biến cục bộ, một cách sử dụng rất tiện dụng là khi viết thư viện bằng biến toàn cục, bạn có thể đặt cho nó một tên biến ngắn hơn để sử dụng trong thư viện. Nó thường được sử dụng trong việc viết các trình cắm jQuery, vì jQuery cho phép bạn vô hiệu hóa biến $ trỏ đến jQuery, sử dụng jQuery.noConflict (). Trong trường hợp nó bị vô hiệu hóa, mã của bạn vẫn có thể sử dụng $ và không bị hỏng nếu bạn chỉ cần làm:
(function($) { ...code...})(jQuery);
Chúng ta cũng nên sử dụng 'userict' trong hàm scope để đảm bảo rằng mã phải được thực thi trong "chế độ nghiêm ngặt". Mã mẫu được hiển thị dưới đây
(function() {
'use strict';
//Your code from here
})();