JavaScript Prototype Chains, Scope Chains และ Performance: สิ่งที่คุณต้องรู้
เผยแพร่แล้ว: 2022-03-11JavaScript: มากกว่าที่เห็น
JavaScript อาจดูเหมือนเป็นภาษาที่เรียนรู้ได้ง่ายในตอนแรก อาจเป็นเพราะไวยากรณ์ที่ยืดหยุ่นได้ หรืออาจเป็นเพราะความคล้ายคลึงกันกับภาษาอื่นๆ ที่รู้จักกันดี เช่น Java หรืออาจเป็นเพราะมันมีประเภทข้อมูลน้อยมากเมื่อเทียบกับภาษาต่างๆ เช่น Java, Ruby หรือ .NET
แต่ในความเป็นจริง JavaScript นั้นเรียบง่ายน้อยกว่าและเหมาะสมกว่ามากเกินกว่าที่นักพัฒนาส่วนใหญ่คิดในตอนแรก แม้แต่นักพัฒนาที่มีประสบการณ์มากกว่า คุณลักษณะเด่นบางอย่างของ JavaScript ยังคงถูกเข้าใจผิดและนำไปสู่ความสับสน หนึ่งในคุณสมบัติดังกล่าวคือวิธีการดำเนินการค้นหาข้อมูล (คุณสมบัติและตัวแปร) และการแยกย่อยของประสิทธิภาพ JavaScript ที่ควรทราบ
ใน JavaScript การค้นหาข้อมูลถูกควบคุมโดยสองสิ่ง: การ สืบทอดต้นแบบ และ ขอบเขตเชน ในฐานะนักพัฒนา การทำความเข้าใจกลไกทั้งสองนี้เป็นสิ่งสำคัญ เนื่องจากการทำเช่นนี้สามารถปรับปรุงโครงสร้าง และบ่อยครั้งที่ประสิทธิภาพของโค้ดของคุณ
ค้นหาคุณสมบัติผ่านเครือข่ายต้นแบบ
เมื่อเข้าถึงพร็อพเพอร์ตี้ในภาษาที่ใช้ต้นแบบ เช่น JavaScript การค้นหาแบบไดนามิกจะเกิดขึ้นที่เกี่ยวข้องกับเลเยอร์ต่างๆ ภายในแผนผังต้นแบบของออบเจ็กต์
ใน JavaScript ทุกฟังก์ชันคืออ็อบเจ็กต์ เมื่อฟังก์ชันถูกเรียกใช้ด้วยตัวดำเนินการ new
วัตถุใหม่จะถูกสร้างขึ้น ตัวอย่างเช่น:
function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } var p1 = new Person('John', 'Doe'); var p2 = new Person('Robert', 'Doe');
ในตัวอย่างข้างต้น p1
และ p2
เป็นสองอ็อบเจ็กต์ที่แตกต่างกัน แต่ละอ็อบเจ็กต์ถูกสร้างขึ้นโดยใช้ฟังก์ชัน Person
เป็นตัวสร้าง เป็นอินสแตนซ์อิสระของ Person
ดังที่แสดงโดยข้อมูลโค้ดนี้:
console.log(p1 instanceof Person); // prints 'true' console.log(p2 instanceof Person); // prints 'true' console.log(p1 === p2); // prints 'false'
เนื่องจากฟังก์ชัน JavaScript เป็นอ็อบเจ็กต์ จึงสามารถมีคุณสมบัติได้ คุณสมบัติที่สำคัญโดยเฉพาะอย่างยิ่งที่แต่ละฟังก์ชันมีเรียกว่า prototype
prototype
ซึ่งตัวมันเองเป็นวัตถุ สืบทอดจากต้นแบบของแม่ ซึ่งสืบทอดมาจากต้นแบบของแม่ เป็นต้น นี้มักจะเรียกว่า ห่วงโซ่ต้นแบบ Object.prototype
ซึ่งอยู่ที่ส่วนท้ายของสายต้นแบบเสมอ (เช่น ที่ด้านบนสุดของแผนผังการสืบทอดต้นแบบ) มีเมธอดเช่น toString()
, hasProperty()
, isPrototypeOf()
และอื่นๆ
ต้นแบบของแต่ละฟังก์ชันสามารถขยายเพื่อกำหนดวิธีการและคุณสมบัติที่กำหนดเองได้
เมื่อคุณสร้างอินสแตนซ์อ็อบเจ็กต์ (โดยการเรียกใช้ฟังก์ชันโดยใช้โอเปอเรเตอร์ new
) อ็อบเจ็กต์จะสืบทอดคุณสมบัติทั้งหมดในต้นแบบของฟังก์ชันนั้น อย่างไรก็ตาม พึงระลึกไว้เสมอว่าอินสแตนซ์เหล่านั้นจะไม่สามารถเข้าถึงวัตถุ prototype
ได้โดยตรง แต่จะเข้าถึงได้เฉพาะคุณสมบัติของวัตถุเท่านั้น ตัวอย่างเช่น:
// Extending the Person prototype from our earlier example to // also include a 'getFullName' method: Person.prototype.getFullName = function() { return this.firstName + ' ' + this.lastName; } // Referencing the p1 object from our earlier example console.log(p1.getFullName()); // prints 'John Doe' // but p1 can't directly access the 'prototype' object... console.log(p1.prototype); // prints 'undefined' console.log(p1.prototype.getFullName()); // generates an error
มีจุดสำคัญและค่อนข้างละเอียดอ่อนอยู่ที่นี่: แม้ว่า p1
จะถูกสร้างขึ้นก่อนที่จะมีการกำหนดเมธอด getFullName
แต่ก็ยังสามารถเข้าถึงได้เนื่องจากต้นแบบของมันคือต้นแบบ Person
(เป็นที่น่าสังเกตว่าเบราว์เซอร์ยังเก็บการอ้างอิงถึงต้นแบบของอ็อบเจ็กต์ใดๆ ในคุณสมบัติ __proto__
แต่เป็นการ ฝึกฝนที่แย่มาก ในการเข้าถึงต้นแบบโดยตรงผ่านคุณสมบัติ __proto__
เนื่องจากไม่ได้เป็นส่วนหนึ่งของข้อกำหนดภาษา ECMAScript มาตรฐาน ดังนั้นอย่า ไม่ทำ! )
เนื่องจากอินสแตนซ์ p1
ของวัตถุ Person
ไม่มีการเข้าถึงโดยตรงไปยังวัตถุ prototype
หากเราต้องการเขียนทับ getFullName
ใน p1
เราจะทำดังนี้:
// We reference p1.getFullName, *NOT* p1.prototype.getFullName, // since p1.prototype does not exist: p1.getFullName = function(){ return 'I am anonymous'; }
ตอนนี้ p1
มีคุณสมบัติ getFullName
ของตัวเอง แต่อินสแตนซ์ p2
(สร้างในตัวอย่างก่อนหน้าของเรา) ไม่มี คุณสมบัติดังกล่าวเป็นของตัวเอง ดังนั้น การเรียกใช้ p1.getFullName()
จะเข้าถึงเมธอด getFullName
ของอินสแตนซ์ p1
เอง ขณะที่เรียกใช้ p2.getFullName()
จะเพิ่มสายต้นแบบไปยังอ็อบเจ็กต์ต้นแบบ Person
เพื่อแก้ไข getFullName
:
console.log(p1.getFullName()); // prints 'I am anonymous' console.log(p2.getFullName()); // prints 'Robert Doe'
สิ่งสำคัญอีกประการหนึ่งที่ต้องระวังก็คือ มันยังสามารถเปลี่ยนต้นแบบของวัตถุ ไดนามิก ได้อีกด้วย ตัวอย่างเช่น:
function Parent() { this.someVar = 'someValue'; }; // extend Parent's prototype to define a 'sayHello' method Parent.prototype.sayHello = function(){ console.log('Hello'); }; function Child(){ // this makes sure that the parent's constructor is called and that // any state is initialized correctly. Parent.call(this); }; // extend Child's prototype to define an 'otherVar' property... Child.prototype.otherVar = 'otherValue'; // ... but then set the Child's prototype to the Parent prototype // (whose prototype doesn't have any 'otherVar' property defined, // so the Child prototype no longer has 'otherVar' defined!) Child.prototype = Object.create(Parent.prototype); var child = new Child(); child.sayHello(); // prints 'Hello' console.log(child.someVar); // prints 'someValue' console.log(child.otherVar); // prints 'undefined'
เมื่อใช้การสืบทอดต้นแบบ อย่าลืมกำหนดคุณสมบัติในต้นแบบ หลังจาก ที่สืบทอดมาจากคลาสพาเรนต์หรือระบุต้นแบบสำรอง
เพื่อสรุป การค้นหาคุณสมบัติผ่านสายโซ่ต้นแบบ JavaScript ทำงานดังนี้:
- หากอ็อบเจ็กต์มีคุณสมบัติตามชื่อที่กำหนด ค่านั้นจะถูกส่งคืน (เมธอด
hasOwnProperty
สามารถใช้เพื่อตรวจสอบว่าอ็อบเจ็กต์มีคุณสมบัติที่มีชื่อเฉพาะหรือไม่) - หากวัตถุไม่มีคุณสมบัติที่มีชื่อ ต้นแบบของวัตถุจะถูกตรวจสอบ
- เนื่องจากต้นแบบนั้นเป็นอ็อบเจ็กต์เช่นกัน หากไม่มีคุณสมบัติ ต้นแบบของพาเรนต์จะถูกตรวจสอบ
- กระบวนการนี้ดำเนินต่อไปในสายโซ่ต้นแบบจนกว่าจะพบคุณสมบัติ
- ถ้าถึง
Object.prototype
และไม่มีคุณสมบัติ ถือว่าคุณสมบัติundefined
การทำความเข้าใจว่าการสืบทอดต้นแบบและการค้นหาคุณสมบัติทำงานอย่างไรนั้นมีความสำคัญโดยทั่วไปสำหรับนักพัฒนา แต่ก็มีความสำคัญเช่นกัน เนื่องจาก (บางครั้งมีนัยสำคัญ) การแยกประสิทธิภาพของ JavaScript ดังที่กล่าวไว้ในเอกสารประกอบสำหรับ V8 (เอ็นจิ้น JavaScript โอเพ่นซอร์สของ Google ประสิทธิภาพสูง) เอ็นจิ้น JavaScript ส่วนใหญ่ใช้โครงสร้างข้อมูลที่เหมือนพจนานุกรมเพื่อเก็บคุณสมบัติของอ็อบเจ็กต์ การเข้าถึงคุณสมบัติแต่ละรายการจึงจำเป็นต้องมีการค้นหาแบบไดนามิกในโครงสร้างข้อมูลนั้นเพื่อแก้ไขคุณสมบัติ วิธีการนี้ทำให้การเข้าถึงคุณสมบัติใน JavaScript มักจะช้ากว่าการเข้าถึงตัวแปรอินสแตนซ์ในภาษาการเขียนโปรแกรมเช่น Java และ Smalltalk
การค้นหาตัวแปรผ่านห่วงโซ่ขอบเขต
กลไกการค้นหาอื่นใน JavaScript ขึ้นอยู่กับขอบเขต
เพื่อให้เข้าใจวิธีการทำงาน จำเป็นต้องแนะนำแนวคิดของบริบทการดำเนินการ
ใน JavaScript มีบริบทการดำเนินการสองประเภท:
- บริบทส่วนกลาง สร้างขึ้นเมื่อมีการเปิดตัวกระบวนการ JavaScript
- บริบทท้องถิ่น สร้างขึ้นเมื่อมีการเรียกใช้ฟังก์ชัน
บริบทการดำเนินการจัดเป็นสแต็ก ที่ด้านล่างของสแต็ก จะมีบริบทส่วนกลางอยู่เสมอ ซึ่งไม่ซ้ำกันสำหรับแต่ละโปรแกรม JavaScript ทุกครั้งที่พบฟังก์ชัน บริบทการดำเนินการใหม่จะถูกสร้างขึ้นและผลักไปที่ด้านบนของสแต็ก เมื่อฟังก์ชันดำเนินการเสร็จสิ้น บริบทของฟังก์ชันจะถูกดึงออกจากสแต็ก
พิจารณารหัสต่อไปนี้:
// global context var message = 'Hello World'; var sayHello = function(n){ // local context 1 created and pushed onto context stack var i = 0; var innerSayHello = function() { // local context 2 created and pushed onto context stack console.log((i + 1) + ': ' + message); // local context 2 popped off of context stack } for (i = 0; i < n; i++) { innerSayHello(); } // local context 1 popped off of context stack }; sayHello(3); // Prints: // 1: Hello World // 2: Hello World // 3: Hello World
ภายในบริบทการดำเนินการแต่ละอันจะมีอ็อบเจ็กต์พิเศษที่เรียกว่า scope chain ซึ่งใช้เพื่อแก้ไขตัวแปร ขอบเขตของขอบเขตโดยพื้นฐานแล้วเป็นสแต็คของขอบเขตที่เข้าถึงได้ในปัจจุบัน ตั้งแต่บริบทที่ใกล้เคียงที่สุดไปจนถึงบริบทส่วนกลาง (เพื่อให้แม่นยำยิ่งขึ้นอีกเล็กน้อย วัตถุที่ด้านบนของสแต็กเรียกว่า Activation Object ซึ่งมีการอ้างอิงถึงตัวแปรท้องถิ่นสำหรับฟังก์ชันที่กำลังดำเนินการ อาร์กิวเมนต์ของฟังก์ชันที่มีชื่อ และอ็อบเจ็กต์ "พิเศษ" สองรายการ: this
และ arguments
) ตัวอย่างเช่น:

หมายเหตุในแผนภาพด้านบนว่า this
ชี้ไปที่วัตถุ window
โดยค่าเริ่มต้นอย่างไร และบริบทส่วนกลางประกอบด้วยตัวอย่างของวัตถุอื่นๆ เช่น console
และ location
อย่างไร
เมื่อพยายามแก้ไขตัวแปรผ่านห่วงโซ่ขอบเขต บริบทในทันทีจะถูกตรวจสอบหาตัวแปรที่ตรงกันก่อน หากไม่พบรายการที่ตรงกัน ระบบจะตรวจสอบอ็อบเจ็กต์บริบทถัดไปในขอบเขตของขอบเขต และอื่นๆ จนกว่าจะพบรายการที่ตรงกัน หากไม่พบรายการที่ตรงกัน จะเกิด ReferenceError
สิ่งสำคัญคือต้องทราบด้วยว่าขอบเขตใหม่จะถูกเพิ่มในสายโซ่ขอบเขตเมื่อพบบล็อก try-catch
หรือบล็อกที่ with
ในทั้งสองกรณีนี้ อ็อบเจ็กต์ใหม่จะถูกสร้างขึ้นและวางไว้ที่ด้านบนสุดของขอบเขตเชน:
function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; }; function persist(person) { with (person) { // The 'person' object was pushed onto the scope chain when we // entered this "with" block, so we can simply reference // 'firstName' and 'lastName', rather than person.firstName and // person.lastName if (!firstName) { throw new Error('FirstName is mandatory'); } if (!lastName) { throw new Error('LastName is mandatory'); } } try { person.save(); } catch(error) { // A new scope containing the 'error' object is accessible here console.log('Impossible to store ' + person + ', Reason: ' + error); } } var p1 = new Person('John', 'Doe'); persist(p1);
เพื่อให้เข้าใจอย่างถ่องแท้ว่าการค้นหาตัวแปรตามขอบเขตเกิดขึ้นได้อย่างไร สิ่งสำคัญคือต้องจำไว้ว่าใน JavaScript ในปัจจุบันไม่มีขอบเขตระดับบล็อก ตัวอย่างเช่น:
for (var i = 0; i < 10; i++) { /* ... */ } // 'i' is still in scope! console.log(i); // prints '10'
ในภาษาอื่นๆ ส่วนใหญ่ โค้ดด้านบนจะทำให้เกิดข้อผิดพลาดเนื่องจาก "ชีวิต" (เช่น ขอบเขต) ของตัวแปร i
จะถูกจำกัดให้อยู่ในบล็อก for ใน JavaScript นี่ไม่ใช่กรณี แต่จะเพิ่ม i
ลงในอ็อบเจ็กต์การเรียกทำงานที่ด้านบนสุดของขอบเขตเชน และมันจะอยู่ที่นั่นจนกว่าอ็อบเจ็กต์นั้นจะถูกลบออกจากขอบเขต ซึ่งเกิดขึ้นเมื่อบริบทการดำเนินการที่เกี่ยวข้องถูกลบออกจากสแต็ก ลักษณะการทำงานนี้เรียกว่าการชักรอกแบบปรับได้
อย่างไรก็ตาม เป็นที่น่าสังเกตว่าการรองรับขอบเขตระดับบล็อกกำลังเข้าสู่ JavaScript ผ่านคีย์เวิร์ด let
ใหม่ คำหลัก let
มีอยู่แล้วใน JavaScript 1.7 และถูกกำหนดให้เป็นคำหลัก JavaScript ที่ได้รับการสนับสนุนอย่างเป็นทางการตั้งแต่ ECMAScript 6
การแยกส่วนประสิทธิภาพของ JavaScript
วิธีการทำงานของการค้นหาคุณสมบัติและตัวแปร โดยใช้สายโซ่ต้นแบบและสายโซ่ขอบเขตตามลำดับ ทำงานใน JavaScript เป็นคุณสมบัติหลักอย่างหนึ่งของภาษา แต่เป็นหนึ่งในคุณสมบัติที่เข้าใจยากที่สุดและละเอียดอ่อนที่สุด
การดำเนินการค้นหาที่เราอธิบายไว้ในตัวอย่างนี้ ไม่ว่าจะอิงตามสายต้นแบบหรือสายโซ่ขอบเขต จะถูกทำซ้ำ ทุก ครั้งที่มีการเข้าถึงคุณสมบัติหรือตัวแปร เมื่อการค้นหานี้เกิดขึ้นภายในลูปหรือการดำเนินการที่เข้มข้นอื่นๆ อาจมีการแตกแขนงของประสิทธิภาพ JavaScript ที่สำคัญ โดยเฉพาะอย่างยิ่งเมื่อพิจารณาจากลักษณะเธรดเดียวของภาษา ซึ่งจะป้องกันไม่ให้การดำเนินการหลายรายการเกิดขึ้นพร้อมกัน
พิจารณาตัวอย่างต่อไปนี้:
var start = new Date().getTime(); function Parent() { this.delta = 10; }; function ChildA(){}; ChildA.prototype = new Parent(); function ChildB(){} ChildB.prototype = new ChildA(); function ChildC(){} ChildC.prototype = new ChildB(); function ChildD(){}; ChildD.prototype = new ChildC(); function ChildE(){}; ChildE.prototype = new ChildD(); function nestedFn() { var child = new ChildE(); var counter = 0; for(var i = 0; i < 1000; i++) { for(var j = 0; j < 1000; j++) { for(var k = 0; k < 1000; k++) { counter += child.delta; } } } console.log('Final result: ' + counter); } nestedFn(); var end = new Date().getTime(); var diff = end - start; console.log('Total time: ' + diff + ' milliseconds');
ในตัวอย่างนี้ เรามีต้นไม้สืบทอดยาวและสามลูปที่ซ้อนกัน ภายในลูปที่ลึกที่สุด ตัวแปรตัวนับจะเพิ่มขึ้นด้วยค่าของ delta
แต่ delta
ตั้งอยู่เกือบบนสุดของต้นไม้มรดก! ซึ่งหมายความว่าทุกครั้งที่เข้าถึง child.delta
ต้องนำทางต้นไม้ทั้งหมดจากล่างขึ้นบน สิ่งนี้สามารถส่งผลเสียต่อประสิทธิภาพการทำงานได้อย่างแท้จริง
เมื่อเข้าใจสิ่งนี้ เราสามารถปรับปรุงประสิทธิภาพของฟังก์ชัน nestedFn
ด้านบนได้อย่างง่ายดายโดยใช้ตัวแปร delta
ในเครื่องเพื่อแคชค่าใน child.delta
(และด้วยเหตุนี้จึงหลีกเลี่ยงความจำเป็นในการข้ามผ่านต้นไม้การสืบทอดทั้งหมดซ้ำๆ กัน) ดังนี้:
function nestedFn() { var child = new ChildE(); var counter = 0; var delta = child.delta; // cache child.delta value in current scope for(var i = 0; i < 1000; i++) { for(var j = 0; j < 1000; j++) { for(var k = 0; k < 1000; k++) { counter += delta; // no inheritance tree traversal needed! } } } console.log('Final result: ' + counter); } nestedFn(); var end = new Date().getTime(); var diff = end - start; console.log('Total time: ' + diff + ' milliseconds');
แน่นอน เทคนิคเฉพาะนี้ใช้ได้เฉพาะในสถานการณ์ที่ทราบว่าค่าของ child.delta
จะไม่เปลี่ยนแปลงในขณะที่ for loops กำลังดำเนินการอยู่ มิฉะนั้น สำเนาในเครื่องจะต้องได้รับการอัปเดตด้วยค่าปัจจุบัน
ตกลง ให้เรียกใช้เมธอด nestedFn
ทั้งสองเวอร์ชันและดูว่ามีความแตกต่างด้านประสิทธิภาพที่เห็นได้ชัดเจนระหว่างสองเวอร์ชันนี้หรือไม่
เราจะเริ่มต้นด้วยการรันตัวอย่างแรกใน node.js REPL:
diego@alkadia:~$ node test.js Final result: 10000000000 Total time: 8270 milliseconds
ซึ่งใช้เวลาประมาณ 8 วินาทีในการทำงาน นั่นเป็นเวลานาน
ตอนนี้เรามาดูกันว่าจะเกิดอะไรขึ้นเมื่อเราเรียกใช้เวอร์ชันที่ปรับให้เหมาะสมที่สุด:
diego@alkadia:~$ node test2.js Final result: 10000000000 Total time: 1143 milliseconds
คราวนี้ใช้เวลาเพียงหนึ่งวินาที เร็วกว่ามาก!
โปรดทราบว่าการใช้ตัวแปรท้องถิ่นเพื่อหลีกเลี่ยงการค้นหาที่มีราคาแพงเป็นเทคนิคที่สามารถใช้ได้ทั้งสำหรับการค้นหาคุณสมบัติ (ผ่านสายโซ่ต้นแบบ) และสำหรับการค้นหาตัวแปร (ผ่านสายโซ่ขอบเขต)
นอกจากนี้ "การแคช" ค่าประเภทนี้ (เช่น ในตัวแปรในขอบเขตภายในเครื่อง) ยังมีประโยชน์เมื่อใช้ไลบรารี JavaScript ทั่วไปบางส่วน ยกตัวอย่าง jQuery jQuery สนับสนุนแนวคิดของ "ตัวเลือก" ซึ่งโดยพื้นฐานแล้วเป็นกลไกในการดึงองค์ประกอบที่ตรงกันตั้งแต่หนึ่งรายการขึ้นไปใน DOM ความง่ายในการระบุตัวเลือกใน jQuery อาจทำให้ลืมว่าการค้นหาตัวเลือกแต่ละรายการมีค่าใช้จ่ายสูงเพียงใด (จากมุมมองด้านประสิทธิภาพ) ดังนั้น การจัดเก็บผลลัพธ์การค้นหาตัวเลือกในตัวแปรโลคัลอาจเป็นประโยชน์อย่างมากต่อประสิทธิภาพการทำงาน ตัวอย่างเช่น:
// this does the DOM search for $('.container') "n" times for (var i = 0; i < n; i++) { $('.container').append(“Line “+i+”<br />”); } // this accomplishes the same thing... // but only does the DOM search for $('.container') once, // although it does still modify the DOM "n" times var $container = $('.container'); for (var i = 0; i < n; i++) { $container.append("Line "+i+"<br />"); } // or even better yet... // this version only does the DOM search for $('.container') once // AND only modifies the DOM once var $html = ''; for (var i = 0; i < n; i++) { $html += 'Line ' + i + '<br />'; } $('.container').append($html);
โดยเฉพาะอย่างยิ่งในหน้าเว็บที่มีองค์ประกอบจำนวนมาก วิธีที่สองในตัวอย่างโค้ดด้านบนอาจส่งผลให้ประสิทธิภาพดีขึ้นกว่าวิธีแรกอย่างมาก
สรุป
การค้นหาข้อมูลใน JavaScript ค่อนข้างแตกต่างไปจากในภาษาอื่นๆ ส่วนใหญ่ และมีความเหมาะสมอย่างยิ่ง ดังนั้นจึงเป็นสิ่งสำคัญที่จะต้องเข้าใจแนวคิดเหล่านี้อย่างเต็มที่และถูกต้องเพื่อที่จะเชี่ยวชาญภาษาอย่างแท้จริง การค้นหาข้อมูลและข้อผิดพลาดทั่วไปของ JavaScript ควรหลีกเลี่ยงเมื่อทำได้ ความเข้าใจนี้น่าจะทำให้โค้ดสะอาดขึ้น แข็งแกร่งขึ้น ซึ่งได้รับการปรับปรุงประสิทธิภาพ JavaScript