การสืบทอดต้นแบบ
ข้อมูลดั้งเดิมแต่ละประเภทจะมีต้นแบบ ซึ่งเป็น Wrapper ของออบเจ็กต์ที่มอบเมธอดสำหรับการทำงานกับค่า ยกเว้น null
และ undefined
เมื่อมีการเรียกใช้การค้นหาเมธอดหรือพร็อพเพอร์ตี้ในแบบพื้นฐาน JavaScript จะตัดค่าพื้นฐานในเบื้องหลังและเรียกใช้เมธอดหรือดำเนินการค้นหาพร็อพเพอร์ตี้ในออบเจ็กต์ Wrapper แทน
ตัวอย่างเช่น ลิเทอรัลสตริงไม่มีเมธอดของตัวเอง แต่คุณเรียกใช้เมธอด .toUpperCase()
ได้ด้วยการใช้ Wrapper ออบเจ็กต์ String
ที่สอดคล้องกัน
"this is a string literal".toUpperCase();
> THIS IS A STRING LITERAL
ซึ่งเรียกว่าการสืบทอดต้นแบบ โดยจะสืบทอดคุณสมบัติและเมธอดจากตัวสร้างที่สอดคล้องกันของค่า
Number.prototype
> Number { 0 }
> constructor: function Number()
> toExponential: function toExponential()
> toFixed: function toFixed()
> toLocaleString: function toLocaleString()
> toPrecision: function toPrecision()
> toString: function toString()
> valueOf: function valueOf()
> <prototype>: Object { … }
คุณสามารถสร้างแบบพื้นฐานโดยใช้ตัวสร้างเหล่านี้ แทนที่จะระบุด้วยค่า เช่น การใช้เครื่องมือสร้าง String
จะสร้างออบเจ็กต์สตริง ไม่ใช่สตริงลิเทอรัล ซึ่งเป็นออบเจ็กต์ที่ไม่เพียงมีค่าสตริงเท่านั้น แต่ยังรวมถึงพร็อพเพอร์ตี้และเมธอดทั้งหมดที่รับค่ามาของตัวสร้างด้วย
const myString = new String( "I'm a string." );
myString;
> String { "I'm a string." }
typeof myString;
> "object"
myString.valueOf();
> "I'm a string."
โดยส่วนใหญ่แล้ว ออบเจ็กต์ที่ได้จะทำหน้าที่เป็นค่าที่เราใช้กำหนด ตัวอย่างเช่น แม้ว่าการกำหนดค่าตัวเลขโดยใช้ตัวสร้าง new Number
จะทำให้ได้ออบเจ็กต์ที่มีเมธอดและพร็อพเพอร์ตี้ทั้งหมดของต้นแบบ Number
แต่คุณก็ใช้โอเปอเรเตอร์ทางคณิตศาสตร์กับวัตถุเหล่านั้นได้เช่นเดียวกับกับลิเทอรัลจำนวน
const numberOne = new Number(1);
const numberTwo = new Number(2);
numberOne;
> Number { 1 }
typeof numberOne;
> "object"
numberTwo;
> Number { 2 }
typeof numberTwo;
> "object"
numberOne + numberTwo;
> 3
คุณแทบจะไม่ต้องใช้ตัวสร้างเหล่านี้เลย เนื่องจากการสืบทอดต้นแบบของ JavaScript ทำให้ตัวสร้างเหล่านี้ไม่มีประโยชน์ในทางปฏิบัติ การสร้างแบบพื้นฐานโดยใช้ตัวสร้างอาจทำให้เกิดผลลัพธ์ที่ไม่คาดคิดได้เช่นกัน เนื่องจากผลลัพธ์เป็นออบเจ็กต์ ไม่ใช่คำตามตัวอักษรง่ายๆ ดังนี้
let stringLiteral = "String literal."
typeof stringLiteral;
> "string"
let stringObject = new String( "String object." );
stringObject
> "object"
ซึ่งอาจทำให้การใช้โอเปอเรเตอร์การเปรียบเทียบที่เข้มงวดมีความซับซ้อน ดังนี้
const myStringLiteral = "My string";
const myStringObject = new String( "My string" );
myStringLiteral === "My string";
> true
myStringObject === "My string";
> false
การแทรกเซมิโคลอนอัตโนมัติ (ASI)
ขณะแยกวิเคราะห์สคริปต์ ล่ามของ JavaScript สามารถใช้ฟีเจอร์ที่เรียกว่าการแทรกเซมิโคลอนอัตโนมัติ (ASI) เพื่อพยายามแก้ไขอินสแตนซ์ของเครื่องหมายเซมิโคลอนที่ข้ามไป หากโปรแกรมแยกวิเคราะห์ JavaScript พบโทเค็นที่ไม่ได้รับอนุญาต โปรแกรมจะพยายามเพิ่มเครื่องหมายเซมิโคลอนหน้าโทเค็นนั้นเพื่อแก้ไขข้อผิดพลาดทางไวยากรณ์ที่อาจเกิดขึ้น ตราบใดที่เงื่อนไขต่อไปนี้อย่างน้อย 1 ข้อเป็นจริง
- โทเค็นดังกล่าวจะแยกออกจากโทเค็นก่อนหน้าโดยการขึ้นบรรทัดใหม่
- โทเค็นดังกล่าวคือ
}
- โทเค็นก่อนหน้าคือ
)
และเครื่องหมายเซมิโคลอนที่แทรกจะเป็นเครื่องหมายอัฒภาคสิ้นสุดของคำสั่งdo
...while
ดูข้อมูลเพิ่มเติมได้ในกฎ ASI
เช่น การละเว้นเครื่องหมายเซมิโคลอนหลังข้อความต่อไปนี้จะไม่ทำให้เกิดข้อผิดพลาดเกี่ยวกับไวยากรณ์เนื่องจาก ASI
const myVariable = 2
myVariable + 3
> 5
แต่ ASI พิจารณาหลายใบแจ้งยอดในบรรทัดเดียวกันไม่ได้ หากคุณเขียนคำสั่งมากกว่า 1 รายการในบรรทัดเดียวกัน โปรดแยกแต่ละคำสั่งด้วยเครื่องหมายอัฒภาค ดังนี้
const myVariable = 2 myVariable + 3
> Uncaught SyntaxError: unexpected token: identifier
const myVariable = 2; myVariable + 3;
> 5
ASI คือความพยายามในการแก้ไขข้อผิดพลาด ไม่ใช่ความยืดหยุ่นแบบไวยากรณ์ที่มีอยู่ใน JavaScript อย่าลืมใช้เครื่องหมายเซมิโคลอนตามความเหมาะสม เพื่อจะได้ไม่ใช้เครื่องหมายขีดคั่นในการสร้างโค้ดที่ถูกต้อง
โหมดจำกัด
มาตรฐานที่ควบคุมการเขียน JavaScript พัฒนาไปไกลกว่าสิ่งที่พิจารณาในช่วงการออกแบบภาษาช่วงแรกๆ การเปลี่ยนแปลงลักษณะการทำงานใหม่ๆ ที่ JavaScript ควรคาดหวังจะต้องหลีกเลี่ยงทำให้เกิดข้อผิดพลาดในเว็บไซต์เก่าๆ
ES5 ช่วยแก้ปัญหาที่มีมาอย่างยาวนานด้วยความหมายของ JavaScript โดยไม่ทำให้การใช้งานที่มีอยู่เสียหายโดยการเปิดตัว "โหมดที่เข้มงวด" ซึ่งเป็นวิธีเลือกใช้ชุดกฎภาษาที่เข้มงวดมากขึ้นสำหรับสคริปต์ทั้งหมดหรือฟังก์ชันใดฟังก์ชันหนึ่ง หากต้องการเปิดใช้โหมดจำกัด ให้ใช้สตริงตามตัวอักษร "use strict"
ตามด้วยเครื่องหมายเซมิโคลอนในบรรทัดแรกของสคริปต์หรือฟังก์ชัน ดังนี้
"use strict";
function myFunction() {
"use strict";
}
โหมดเข้มงวดจะป้องกันการดำเนินการที่ "ไม่ปลอดภัย" หรือฟีเจอร์ที่เลิกใช้งานแล้ว แสดงข้อผิดพลาดอย่างชัดเจนแทนที่รายการที่ "เงียบ" ทั่วไป และไม่อนุญาตให้ใช้ไวยากรณ์ที่อาจขัดแย้งกับฟีเจอร์ภาษาในอนาคต ตัวอย่างเช่น การตัดสินใจออกแบบขอบเขตตัวแปรแต่เนิ่นๆ ทำให้นักพัฒนาซอฟต์แวร์มีแนวโน้มที่จะ "สร้างมลพิษ" ให้ขอบเขตรวมโดยไม่ได้ตั้งใจเมื่อประกาศตัวแปร โดยไม่คำนึงถึงบริบทที่มีอยู่ โดยละเว้นคีย์เวิร์ด var
ต่อไปนี้
(function() {
mySloppyGlobal = true;
}());
mySloppyGlobal;
> true
รันไทม์ของ JavaScript สมัยใหม่ไม่สามารถแก้ไขลักษณะการทำงานนี้ได้โดยไม่เสี่ยงต่อการทำให้เว็บไซต์ที่จำเป็นต้องใช้ JavaScript นั้นเสียหาย ไม่ว่าจะผิดพลาดหรือโดยเจตนา แต่ JavaScript แบบใหม่จะปิดกั้นไม่ให้นักพัฒนาแอปเลือกใช้โหมดเข้มงวดในการทำงานใหม่ และเปิดใช้โหมดเข้มงวดโดยค่าเริ่มต้นเฉพาะในบริบทของฟีเจอร์ภาษาใหม่ ซึ่งจะไม่ทำให้การใช้งานเดิมหยุดชะงัก
(function() {
"use strict";
mySloppyGlobal = true;
}());
> Uncaught ReferenceError: assignment to undeclared variable mySloppyGlobal
คุณต้องเขียน "use strict"
เป็นสตริงลิเทอรัล
เทมเพลตลิเทอรัล
(use strict
) จะไม่ทำงาน และยังต้องใส่ "use strict"
ก่อนโค้ดสั่งการในบริบทที่กำหนดด้วย ไม่เช่นนั้น ล่ามจะไม่สนใจคำสั่งนั้น
(function() {
"use strict";
let myVariable = "String.";
console.log( myVariable );
sloppyGlobal = true;
}());
> "String."
> Uncaught ReferenceError: assignment to undeclared variable sloppyGlobal
(function() {
let myVariable = "String.";
"use strict";
console.log( myVariable );
sloppyGlobal = true;
}());
> "String." // Because there was code prior to "use strict", this variable still pollutes the global scope
ตามการอ้างอิง ตามค่า
ตัวแปรทั้งหมด รวมถึงพร็อพเพอร์ตี้ของออบเจ็กต์ พารามิเตอร์ฟังก์ชัน และองค์ประกอบในอาร์เรย์ set หรือแมป อาจมีค่าพื้นฐานหรือค่าอ้างอิง
เมื่อกำหนดค่าพื้นฐานจากตัวแปรหนึ่งไปยังอีกตัวแปรหนึ่ง เครื่องมือ JavaScript จะสร้างสำเนาของค่านั้นและกำหนดให้กับตัวแปรนั้น
เมื่อคุณกำหนดออบเจ็กต์ (อินสแตนซ์ของคลาส อาร์เรย์ และฟังก์ชัน) ให้กับตัวแปร แทนที่จะสร้างสำเนาใหม่ของออบเจ็กต์นั้น ตัวแปรจะมีการอ้างอิงตำแหน่งที่จัดเก็บของออบเจ็กต์ในหน่วยความจำ ดังนั้น การเปลี่ยนออบเจ็กต์ที่ตัวแปรอ้างอิงจะเปลี่ยนออบเจ็กต์ที่อ้างอิงอยู่ ไม่ใช่แค่ค่าที่ตัวแปรนั้นมีอยู่ ตัวอย่างเช่น หากคุณเริ่มต้นตัวแปรใหม่ด้วยตัวแปรที่มีการอ้างอิงออบเจ็กต์ ให้ใช้ตัวแปรใหม่เพื่อเพิ่มพร็อพเพอร์ตี้ไปยังออบเจ็กต์นั้น ระบบจะเพิ่มพร็อพเพอร์ตี้และค่าในออบเจ็กต์ต้นฉบับ ดังนี้
const myObject = {};
const myObjectReference = myObject;
myObjectReference.myProperty = true;
myObject;
> Object { myProperty: true }
วิธีนี้ไม่เพียงสำคัญต่อการปรับเปลี่ยนวัตถุเท่านั้น แต่ยังใช้สำหรับการเปรียบเทียบอย่างเข้มงวดด้วย เนื่องจากความเท่าเทียมกันที่เข้มงวดระหว่างออบเจ็กต์กำหนดให้ตัวแปรทั้ง 2 ตัวอ้างอิงวัตถุเดียวกันเพื่อประเมินไปยัง true
โดยจะอ้างอิงวัตถุที่แตกต่างกันไม่ได้ แม้ว่าวัตถุเหล่านั้นจะมีโครงสร้างเหมือนกันก็ตาม
const myObject = {};
const myReferencedObject = myObject;
const myNewObject = {};
myObject === myNewObject;
> false
myObject === myReferencedObject;
> true
การจัดสรรหน่วยความจำ
JavaScript ใช้การจัดการหน่วยความจำอัตโนมัติ ซึ่งหมายความว่าคุณไม่จำเป็นต้องจัดสรรหรือจัดสรรหน่วยความจำอย่างชัดเจนระหว่างการพัฒนา แม้ว่ารายละเอียดของวิธีการจัดการหน่วยความจำของเครื่องมือ JavaScript จะอยู่นอกเหนือขอบเขตของโมดูลนี้ แต่การทำความเข้าใจวิธีจัดสรรหน่วยความจำจะเป็นบริบทที่มีประโยชน์สำหรับการทำงานกับค่าอ้างอิง
หน่วยความจำจะมี 2 "พื้นที่" คือ "สแต็ก" และ "ฮีป" สแต็กจะเก็บข้อมูลแบบคงที่ (ค่าพื้นฐานและการอ้างอิงไปยังออบเจ็กต์) เนื่องจากระบบสามารถจัดสรรพื้นที่ที่ต้องใช้เพื่อจัดเก็บข้อมูลนี้ได้ตามจำนวนที่กำหนดก่อนที่สคริปต์จะทำงาน ออบเจ็กต์ร้านค้าฮีปซึ่งต้องใช้พื้นที่ที่จัดสรรแบบไดนามิกเนื่องจากขนาดของออบเจ็กต์อาจเปลี่ยนแปลงในระหว่างการดำเนินการ หน่วยความจำจะได้รับการปลดปล่อยโดยกระบวนการที่เรียกว่า "การรวบรวมขยะ" ซึ่งจะนำวัตถุที่ไม่มีการอ้างอิงออกจากหน่วยความจำ
เทรดหลัก
JavaScript คือภาษาแบบชุดข้อความเดียวโดยพื้นฐานที่มีโมเดลการดำเนินการแบบ "ซิงโครนัส" ซึ่งหมายความว่าจะดำเนินการกับงานได้ครั้งละ 1 รายการเท่านั้น บริบทการดำเนินการตามลำดับนี้เรียกว่าเทรดหลัก
งานอื่นๆ ของเบราว์เซอร์แชร์เทรดหลัก เช่น การแยกวิเคราะห์ HTML, การแสดงผลและการแสดงผลบางส่วนของหน้าเว็บอีกครั้ง, การเรียกใช้ภาพเคลื่อนไหว CSS และการจัดการการโต้ตอบของผู้ใช้ ตั้งแต่เรื่องง่ายๆ (เช่น การไฮไลต์ข้อความ) ไปจนถึงขั้นตอนที่ซับซ้อน (เช่น การโต้ตอบกับองค์ประกอบแบบฟอร์ม) ผู้ให้บริการเบราว์เซอร์พบวิธีต่างๆ ในการเพิ่มประสิทธิภาพงานที่ดำเนินการโดยเทรดหลัก แต่สคริปต์ที่ซับซ้อนมากขึ้นจะยังคงใช้ทรัพยากรของเทรดหลักมากเกินไปและส่งผลต่อประสิทธิภาพโดยรวมของหน้าเว็บได้
งานบางอย่างจะดำเนินการได้ในชุดข้อความเบื้องหลังที่เรียกว่า Web Workers โดยมีข้อจำกัดบางอย่างดังนี้
- เทรดผู้ปฏิบัติงานจะดำเนินการกับไฟล์ JavaScript แบบสแตนด์อโลนได้เท่านั้น
- ผู้ใช้ได้ลดสิทธิ์เข้าถึงหน้าต่างและ UI ของเบราว์เซอร์หรือเข้าถึงไม่ได้อย่างมาก
- และมีข้อจำกัดเกี่ยวกับวิธีสื่อสารกับเทรดหลัก
ข้อจำกัดเหล่านี้ทำให้เหมาะสำหรับงานที่มีการโฟกัสและต้องใช้ทรัพยากรจำนวนมากซึ่งอาจมีการใช้เทรดหลัก
สแต็กการเรียกใช้
โครงสร้างข้อมูลที่ใช้จัดการ "บริบทการดำเนินการ" ซึ่งเป็นโค้ดที่กำลังดำเนินการอยู่เป็นรายการที่เรียกว่าสแต็กการเรียกใช้ (มักเรียกว่า "สแต็ก") เมื่อเรียกใช้สคริปต์ครั้งแรก เครื่องมือตีความ JavaScript จะสร้าง "บริบทการดำเนินการส่วนกลาง" และพุชสคริปต์ไปยังสแต็กการเรียกใช้ โดยคำสั่งภายในบริบทส่วนกลางนั้นจะประมวลผลทีละรายการจากบนลงล่าง เมื่อล่ามพบการเรียกใช้ฟังก์ชันขณะเรียกใช้บริบทส่วนกลาง ล่ามจะพุช "บริบทการเรียกใช้ฟังก์ชัน" สำหรับการเรียกใช้นั้นไปที่ด้านบนของสแต็ก หยุดบริบทการดำเนินการส่วนกลางชั่วคราว และเรียกใช้บริบทการเรียกใช้ฟังก์ชัน
ทุกครั้งที่มีการเรียกใช้ฟังก์ชัน บริบทการดำเนินการของฟังก์ชันสำหรับการเรียกใช้นั้นจะพุชไปที่ด้านบนของสแต็กเหนือบริบทการดำเนินการปัจจุบัน Call Stack ทำงานโดยใช้พื้นฐาน "ก่อนเป็นอันดับแรก" ซึ่งหมายความว่าการเรียกใช้ฟังก์ชันล่าสุดซึ่งอยู่สูงสุดในสแต็กนั้นจะทำงานและดำเนินต่อไปจนกว่าจะสิ้นสุด เมื่อฟังก์ชันดังกล่าวเสร็จสมบูรณ์ ล่ามจะนำฟังก์ชันออกจากสแต็กการเรียกใช้ และบริบทการดำเนินการที่มีการเรียกใช้ฟังก์ชันดังกล่าวจะกลายเป็นรายการสูงสุดในสแต็กอีกครั้งและกลับมาดำเนินการต่อ
บริบทการดำเนินการเหล่านี้จะบันทึกค่าที่จำเป็นต่อการดำเนินการ นอกจากนี้ยังสร้างตัวแปรและฟังก์ชันที่ใช้ได้ภายในขอบเขตของฟังก์ชันตามบริบทของฟังก์ชันหลัก ตลอดจนกำหนดและกำหนดค่าของคีย์เวิร์ด this
ในบริบทของฟังก์ชันดังกล่าว
ลูปเหตุการณ์และคิวเรียกกลับ
การดำเนินการตามลำดับนี้หมายความว่างานแบบไม่พร้อมกันที่มีฟังก์ชันการติดต่อกลับ เช่น การดึงข้อมูลจากเซิร์ฟเวอร์ การตอบสนองการโต้ตอบของผู้ใช้ หรือกำลังรอตัวจับเวลาที่ตั้งไว้ด้วย setTimeout
หรือ setInterval
อาจบล็อกเทรดหลักจนกว่างานนั้นจะเสร็จสมบูรณ์ หรือขัดจังหวะบริบทการดำเนินการปัจจุบันโดยไม่คาดคิดเมื่อมีการเพิ่มบริบทการดำเนินการของฟังก์ชันเรียกกลับลงในสแต็ก เพื่อแก้ไขปัญหานี้ JavaScript จะจัดการงานที่ไม่พร้อมกันโดยใช้ "โมเดลการเกิดขึ้นพร้อมกัน" ที่ขับเคลื่อนด้วยเหตุการณ์ ซึ่งประกอบด้วย "Event Loop" และ "คิวเรียกกลับ" (บางครั้งเรียกว่า "คิวข้อความ")
เมื่อเรียกใช้งานแบบไม่พร้อมกันในเทรดหลัก บริบทการดำเนินการของฟังก์ชันการเรียกกลับจะอยู่ในคิวโค้ดเรียกกลับ ไม่ใช่ด้านบนของสแต็กการเรียกใช้ ลูปเหตุการณ์เป็นรูปแบบที่บางครั้งเรียกว่าตัวดำเนินการ ซึ่งจะตรวจสอบสถานะของสแต็กการเรียกใช้และคิวการเรียกกลับอย่างต่อเนื่อง หากมีงานในคิวเรียกกลับและลูปเหตุการณ์ระบุว่าชุดรายการการเรียกว่างเปล่า ระบบจะพุชงานจากคิวการเรียกกลับไปยังสแต็กทีละรายการเพื่อดำเนินการ