JavaScript Prototype Chains, Scope Chains และ Performance: สิ่งที่คุณต้องรู้

เผยแพร่แล้ว: 2022-03-11

JavaScript: มากกว่าที่เห็น

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() และอื่นๆ

ความสัมพันธ์ระหว่างต้นแบบ JavaScript และสายโซ่ขอบเขตมีความสำคัญ

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

เมื่อคุณสร้างอินสแตนซ์อ็อบเจ็กต์ (โดยการเรียกใช้ฟังก์ชันโดยใช้โอเปอเรเตอร์ 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' 

ดูว่า P1 และ P2 เกี่ยวข้องกับต้นแบบบุคคลอย่างไรในตัวอย่างต้นแบบ JavaScript นี้

สิ่งสำคัญอีกประการหนึ่งที่ต้องระวังก็คือ มันยังสามารถเปลี่ยนต้นแบบของวัตถุ ไดนามิก ได้อีกด้วย ตัวอย่างเช่น:

 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 ในกลุ่มต้นแบบ

เพื่อสรุป การค้นหาคุณสมบัติผ่านสายโซ่ต้นแบบ 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 ) ตัวอย่างเช่น:

วิธีที่ขอบเขตของขอบเขตเกี่ยวข้องกับอ็อบเจ็กต์ถูกสรุปไว้ในตัวอย่าง JavaScript นี้

หมายเหตุในแผนภาพด้านบนว่า 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

ที่เกี่ยวข้อง: ในฐานะนักพัฒนา JS นี่คือสิ่งที่ทำให้ฉันนอนไม่หลับ / ทำให้รู้สึกถึงความสับสนในคลาส ES6