ภาคผนวก

การสืบทอดต้นแบบ

ข้อมูลดั้งเดิมแต่ละประเภทจะมีต้นแบบ ซึ่งเป็น 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" และ "คิวเรียกกลับ" (บางครั้งเรียกว่า "คิวข้อความ")

เมื่อเรียกใช้งานแบบไม่พร้อมกันในเทรดหลัก บริบทการดำเนินการของฟังก์ชันการเรียกกลับจะอยู่ในคิวโค้ดเรียกกลับ ไม่ใช่ด้านบนของสแต็กการเรียกใช้ ลูปเหตุการณ์เป็นรูปแบบที่บางครั้งเรียกว่าตัวดำเนินการ ซึ่งจะตรวจสอบสถานะของสแต็กการเรียกใช้และคิวการเรียกกลับอย่างต่อเนื่อง หากมีงานในคิวเรียกกลับและลูปเหตุการณ์ระบุว่าชุดรายการการเรียกว่างเปล่า ระบบจะพุชงานจากคิวการเรียกกลับไปยังสแต็กทีละรายการเพื่อดำเนินการ