ฉันได้อ่านจาวาสคริปต์จำนวนมากเมื่อเร็ว ๆ นี้และฉันได้สังเกตเห็นว่าไฟล์ทั้งหมดถูกห่อเหมือนไฟล์ต่อไปนี้ในไฟล์. js ที่จะนำเข้า
(function() {
...
code
...
})();
อะไรคือเหตุผลในการทำสิ่งนี้มากกว่าฟังก์ชั่นคอนสตรัคเตอร์ที่เรียบง่าย?
ฉันได้อ่านจาวาสคริปต์จำนวนมากเมื่อเร็ว ๆ นี้และฉันได้สังเกตเห็นว่าไฟล์ทั้งหมดถูกห่อเหมือนไฟล์ต่อไปนี้ในไฟล์. js ที่จะนำเข้า
(function() {
...
code
...
})();
อะไรคือเหตุผลในการทำสิ่งนี้มากกว่าฟังก์ชั่นคอนสตรัคเตอร์ที่เรียบง่าย?
คำตอบ:
โดยปกติจะเป็นเนมสเปซ (ดูในภายหลัง) และควบคุมการแสดงผลของฟังก์ชันสมาชิกและ / หรือตัวแปร คิดว่ามันเหมือนคำนิยามของวัตถุ ชื่อทางเทคนิคของมันคือนิพจน์ฟังก์ชันที่เรียกใช้ทันที (IIFE) ปลั๊กอิน jQuery มักเขียนเช่นนี้
ใน Javascript คุณสามารถซ้อนฟังก์ชันได้ ดังนั้นสิ่งต่อไปนี้ถูกต้องตามกฎหมาย:
function outerFunction() {
function innerFunction() {
// code
}
}
ตอนนี้คุณสามารถโทรหาouterFunction()
แต่การแสดงของinnerFunction()
จะถูก จำกัด ขอบเขตของความหมายมันคือส่วนตัวถึงouterFunction()
outerFunction()
มันเป็นไปตามหลักการเดียวกันกับตัวแปรใน Javascript:
var globalVariable;
function someFunction() {
var localVariable;
}
ที่สอดคล้องกัน:
function globalFunction() {
var localFunction1 = function() {
//I'm anonymous! But localFunction1 is a reference to me!
};
function localFunction2() {
//I'm named!
}
}
ในสถานการณ์ดังกล่าวข้างต้นคุณสามารถโทรglobalFunction()
ได้จากทุกที่ แต่คุณไม่สามารถเรียกหรือlocalFunction1
localFunction2
สิ่งที่คุณทำเมื่อคุณเขียน(function() { ... })()
คือคุณกำลังสร้างโค้ดภายในวงเล็บชุดแรกเป็นตัวอักษรฟังก์ชั่น (หมายถึง "วัตถุทั้งหมด" เป็นจริงฟังก์ชัน) หลังจากนั้นคุณจะเรียกใช้ฟังก์ชัน (ตัวสุดท้าย()
) ที่คุณเพิ่งกำหนดเอง ดังนั้นข้อได้เปรียบที่สำคัญของสิ่งนี้ตามที่ฉันได้กล่าวไว้ก่อนหน้าคือคุณสามารถมีวิธี / ฟังก์ชันและคุณสมบัติส่วนตัว:
(function() {
var private_var;
function private_function() {
//code
}
})();
ในตัวอย่างแรกคุณจะต้องเรียกใช้globalFunction
ชื่ออย่างชัดเจนเพื่อเรียกใช้ นั่นคือคุณจะทำglobalFunction()
เพื่อเรียกใช้ แต่ในตัวอย่างข้างต้นคุณไม่เพียงแค่นิยามฟังก์ชัน คุณกำลังกำหนดและเรียกมันในครั้งเดียว ซึ่งหมายความว่าเมื่อโหลดไฟล์ JavaScript ของคุณแล้วจะมีการเรียกใช้งานทันที แน่นอนคุณสามารถทำได้:
function globalFunction() {
// code
}
globalFunction();
พฤติกรรมส่วนใหญ่จะเหมือนกันยกเว้นความแตกต่างที่สำคัญอย่างหนึ่ง: คุณหลีกเลี่ยงการสร้างมลภาวะระดับโลกเมื่อคุณใช้ IIFE (ดังนั้นมันก็หมายความว่าคุณไม่สามารถเรียกใช้ฟังก์ชันได้หลายครั้งเนื่องจากไม่มีชื่อ แต่เนื่องจาก ฟังก์ชั่นนี้มีขึ้นเพื่อให้ทำงานเท่านั้นเมื่อไม่มีปัญหา)
สิ่งที่ประณีตด้วย IIFEs คือคุณสามารถกำหนดสิ่งต่าง ๆ ภายในและเปิดเผยเฉพาะส่วนที่คุณต้องการกับโลกภายนอกดังนั้น (ตัวอย่างของการตั้งชื่อเพื่อให้คุณสามารถสร้างไลบรารี่ / ปลั๊กอินของคุณเอง):
var myPlugin = (function() {
var private_var;
function private_function() {
}
return {
public_function1: function() {
},
public_function2: function() {
}
}
})()
ตอนนี้คุณสามารถโทรmyPlugin.public_function1()
แต่คุณไม่สามารถเข้าถึงได้private_function()
! ค่อนข้างคล้ายกับนิยามของคลาส เพื่อความเข้าใจที่ดีขึ้นนี้ฉันขอแนะนำลิงก์ต่อไปนี้สำหรับการอ่านเพิ่มเติม:
แก้ไข
ฉันลืมที่จะพูดถึง ในขั้นตอนสุดท้าย()
คุณสามารถผ่านสิ่งที่คุณต้องการภายใน ตัวอย่างเช่นเมื่อคุณสร้างปลั๊กอิน jQuery คุณจะผ่านjQuery
หรือ$
ชอบ:
(function(jQ) { ... code ... })(jQuery)
ดังนั้นสิ่งที่คุณทำที่นี่คือการกำหนดฟังก์ชั่นที่ใช้ในหนึ่งพารามิเตอร์ (เรียกว่าjQ
ตัวแปรท้องถิ่นและเป็นที่รู้จักเฉพาะฟังก์ชั่นนั้น) จากนั้นคุณจะเรียกใช้ฟังก์ชันด้วยตนเองและส่งผ่านพารามิเตอร์ (หรือที่เรียกว่าjQuery
แต่นี่มาจากโลกภายนอกและมีการอ้างอิงถึง jQuery จริง ๆ ) ไม่จำเป็นต้องกดปุ่มนี้ แต่มีข้อดีบางประการ:
ก่อนหน้านี้ฉันอธิบายว่าฟังก์ชั่นเหล่านี้ทำงานโดยอัตโนมัติอย่างไรเมื่อเริ่มต้น แต่ถ้ามันทำงานโดยอัตโนมัติใครจะผ่านการโต้แย้ง เทคนิคนี้อนุมานว่าพารามิเตอร์ทั้งหมดที่คุณต้องการมีการกำหนดไว้แล้วว่าเป็นตัวแปรทั่วโลก ดังนั้นหาก jQuery ไม่ได้ถูกนิยามเป็นตัวแปรโกลบอลตัวอย่างนี้จะไม่ทำงาน ดังที่คุณอาจคาดเดาได้สิ่งหนึ่งที่ jquery.js ทำในระหว่างการเริ่มต้นคือการกำหนดตัวแปรทั่วโลก 'jQuery' รวมถึงตัวแปรทั่วโลกที่มีชื่อเสียง '$' ซึ่งอนุญาตให้โค้ดนี้ทำงานหลังจากรวม jQuery แล้ว
;(function(jQ) { ... code ... })(jQuery);
ด้วยวิธีนี้ถ้ามีคนทิ้งเซมิโคลอนไว้ในสคริปต์ของพวกเขามันจะไม่ทำให้คุณเสียโดยเฉพาะอย่างยิ่งถ้าคุณวางแผนที่จะย่อขนาดและต่อสคริปต์ของคุณกับผู้อื่น
(function (context) { ..... })(this)
ซึ่งจะช่วยให้คุณสามารถแนบสิ่งที่คุณต้องการกับบริบทหลักดังนั้นจึงเผยให้เห็น
ในรูปแบบที่ง่ายที่สุดเทคนิคนี้มีจุดมุ่งหมายที่จะตัดรหัสภายในขอบเขตฟังก์ชั่น
ช่วยลดโอกาสในการ:
มันไม่ตรวจจับเมื่อเอกสารพร้อม - ไม่ใช่document.onload
หรือwindow.onload
มันเป็นที่รู้จักกันทั่วไปว่าเป็นหรือImmediately Invoked Function Expression (IIFE)
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
ในตัวอย่างข้างต้นตัวแปรใด ๆ ที่กำหนดไว้ในฟังก์ชั่น (เช่นประกาศโดยใช้var
) จะเป็น "ส่วนตัว" และสามารถเข้าถึงได้ภายในขอบเขตฟังก์ชั่นเท่านั้น (ตามที่ Vivin Paliath ใส่ไว้) กล่าวอีกนัยหนึ่งตัวแปรเหล่านี้ไม่สามารถมองเห็น / เข้าถึงได้นอกฟังก์ชั่น ดูการสาธิตสด
Javascript มีฟังก์ชันกำหนดขอบเขต "พารามิเตอร์และตัวแปรที่กำหนดในฟังก์ชั่นไม่สามารถมองเห็นได้นอกฟังก์ชั่นและตัวแปรที่กำหนดไว้ที่ใดก็ได้ภายในฟังก์ชั่นสามารถมองเห็นได้ทุกที่ภายในฟังก์ชัน" (จาก "Javascript: The Good Parts")
ในท้ายที่สุดโค้ดที่โพสต์ก่อนหน้าสามารถทำได้ดังนี้:
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
อยู่มาวันหนึ่งบางคนอาจคิดว่า "ต้องมีวิธีที่จะหลีกเลี่ยงการตั้งชื่อ 'myMainFunction' เนื่องจากสิ่งที่เราต้องการคือการดำเนินการทันที
หากคุณกลับไปสู่พื้นฐานคุณจะพบว่า:
expression
: สิ่งที่ประเมินค่า กล่าวคือ3+11/x
statement
: บรรทัดของโค้ดที่ทำอะไรบางอย่าง แต่มันไม่ได้ประเมินค่า กล่าวคือif(){}
ในทำนองเดียวกันฟังก์ชั่นการแสดงออกประเมินเป็นค่า และสิ่งหนึ่งที่เป็นผล (ฉันสมมติว่า?) คือพวกเขาสามารถเรียกใช้ได้ทันที:
var italianSayinSomething = function(){ console.log('mamamia!'); }();
ดังนั้นตัวอย่างที่ซับซ้อนมากขึ้นของเราจะกลายเป็น:
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
ขั้นตอนต่อไปคือความคิด "ทำไมมีvar myMainFunction =
ถ้าเราไม่แม้แต่ใช้มัน!"
คำตอบนั้นง่าย: ลองลบออกเช่นด้านล่าง:
function(){ console.log('mamamia!'); }();
มันจะไม่ทำงานเพราะ"การประกาศฟังก์ชั่นไม่ได้ invokable"
เคล็ดลับคือว่าโดยการเอาvar myMainFunction =
เราเปลี่ยนการแสดงออกของฟังก์ชั่นเป็นประกาศฟังก์ชัน ดูลิงค์ใน "ทรัพยากร" สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับเรื่องนี้
คำถามต่อไปคือ "ทำไมฉันไม่สามารถเก็บมันไว้เป็นนิพจน์ฟังก์ชั่นกับสิ่งอื่นที่ไม่ใช่var myMainFunction =
?
คำตอบคือ "คุณทำได้" และมีหลายวิธีที่คุณสามารถทำได้: การเพิ่ม a +
, !
a -
, หรืออาจจะรวมอยู่ในวงเล็บคู่หนึ่ง ตัวอย่างเช่น
(function(){ console.log('mamamia!'); })(); // live demo: jsbin.com/zokuwodoco/1/edit?js,console.
หรือ
+function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wuwipiyazi/1/edit?js,console
หรือ
-function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wejupaheva/1/edit?js,console
ดังนั้นเมื่อมีการเพิ่มการแก้ไขที่เกี่ยวข้องกับสิ่งที่ครั้งหนึ่งเคยเป็น "รหัสทางเลือก" ของเราเราจะกลับไปที่รหัสเดียวกันกับที่ใช้ในตัวอย่าง "รหัสอธิบาย"
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
อ่านเพิ่มเติมเกี่ยวกับExpressions vs Statements
:
สิ่งหนึ่งที่อาจสงสัยคือ "จะเกิดอะไรขึ้นเมื่อคุณไม่นิยามตัวแปร 'อย่างถูกต้อง' ในฟังก์ชั่น - นั่นคือทำการมอบหมายอย่างง่ายแทน?
(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
โดยพื้นฐานแล้วหากตัวแปรที่ไม่ได้ประกาศในขอบเขตปัจจุบันนั้นได้รับการกำหนดค่าดังนั้น "การค้นหาห่วงโซ่ขอบเขตจะเกิดขึ้นจนกว่าจะพบตัวแปรหรือกระทบกับขอบเขตทั่วโลก
เมื่ออยู่ในสภาพแวดล้อมเบราว์เซอร์ (เทียบกับสภาพแวดล้อมเซิร์ฟเวอร์เช่น nodejs) ขอบเขตทั่วโลกจะถูกกำหนดโดยwindow
วัตถุ window.myOtherFunction()
ดังนั้นเราสามารถทำได้
เคล็ดลับ "แนวทางปฏิบัติที่ดี" ของฉันในหัวข้อนี้คือใช้เสมอvar
เมื่อกำหนดสิ่งต่าง ๆ : ไม่ว่าจะเป็นตัวเลขวัตถุหรือฟังก์ชั่น & แม้ในขอบเขตทั่วโลก ทำให้รหัสง่ายขึ้นมาก
บันทึก:
block scope
(ปรับปรุง: บล็อกขอบเขตตัวแปรท้องถิ่นเพิ่มเข้ามาในES6 .)function scope
& global scope
( window
ขอบเขตในสภาพแวดล้อมของเบราว์เซอร์)อ่านเพิ่มเติมเกี่ยวกับJavascript Scopes
:
เมื่อคุณได้รับIIFE
แนวคิดนี้มันจะนำไปสู่module pattern
ซึ่งมักทำโดยใช้ประโยชน์จากรูปแบบ IIFE นี้ มีความสุข :)
Javascript ในเบราว์เซอร์มีขอบเขตที่มีประสิทธิภาพเพียงสองอย่างเท่านั้น: ขอบเขตฟังก์ชั่นและขอบเขตทั่วโลก
หากตัวแปรไม่ได้อยู่ในขอบเขตของฟังก์ชันมันจะอยู่ในขอบเขตส่วนกลาง และตัวแปรทั่วโลกมักจะไม่ดีดังนั้นนี่คือโครงสร้างเพื่อให้ตัวแปรของไลบรารีเป็นของตัวเอง
ที่เรียกว่าการปิด โดยทั่วไปแล้วมันจะปิดผนึกรหัสภายในฟังก์ชั่นเพื่อให้ห้องสมุดอื่นไม่รบกวนมัน มันคล้ายกับการสร้างเนมสเปซในภาษาที่รวบรวม
ตัวอย่าง. สมมติว่าฉันเขียน:
(function() {
var x = 2;
// do stuff with x
})();
ตอนนี้ห้องสมุดอื่น ๆ ไม่สามารถเข้าถึงตัวแปรที่x
ฉันสร้างขึ้นเพื่อใช้ในห้องสมุดของฉัน
(function(){ ... return { publicProp1: 'blah' }; })();
แต่คุณสามารถให้ทำงานที่คล้ายกันโดยกลับวัตถุที่มีคุณสมบัติที่คุณต้องการที่จะเผยแพร่ต่อไปนี้: เห็นได้ชัดว่าไม่ขนานกับการกำหนดเนมอย่างสมบูรณ์ แต่อาจช่วยให้คิดแบบนั้นได้
คุณสามารถใช้ฟังก์ชั่นการปิดเป็นข้อมูลในนิพจน์ที่ใหญ่ขึ้นเช่นเดียวกับในวิธีนี้ในการพิจารณาการรองรับเบราว์เซอร์สำหรับวัตถุ 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;
})()
}
นอกเหนือจากการรักษาตัวแปรในตัวเครื่องแล้วการใช้งานที่สะดวกมากอย่างหนึ่งคือเมื่อเขียนไลบรารีโดยใช้ตัวแปรส่วนกลางคุณสามารถตั้งชื่อตัวแปรให้สั้นลงเพื่อใช้ภายในไลบรารี มักใช้ในการเขียนปลั๊กอิน jQuery เนื่องจาก jQuery อนุญาตให้คุณปิดการใช้งานตัวแปร $ ที่ชี้ไปที่ jQuery โดยใช้ jQuery.noConflict () ในกรณีที่มันถูกปิดใช้งานรหัสของคุณยังคงสามารถใช้ $ และไม่ทำลายถ้าคุณทำ:
(function($) { ...code...})(jQuery);
เราควรใช้ 'ใช้เข้มงวด' ในฟังก์ชันขอบเขตเพื่อให้แน่ใจว่ารหัสควรถูกเรียกใช้ใน "โหมดเข้มงวด" รหัสตัวอย่างที่แสดงด้านล่าง
(function() {
'use strict';
//Your code from here
})();