JavaScript

อธิบาย event delegation

Event delegation เป็นเทคนิคที่เกี่ยวข้องกับการเพิ่ม event listeners ให้กับองค์ประกอบแม่ แทนที่จะเพิ่มให้กับองค์ประกอบลูก listener จะทำงานเมื่อเหตุการณ์ถูกเรียกใช้บนองค์ประกอบลูก เนื่องจากการบับเบิ้ลของเหตุการณ์ใน DOM ประโยชน์ของเทคนิคนี้คือ:

  • ลดการใช้หน่วยความจำ เนื่องจากต้องการ handler เพียงตัวเดียวบนองค์ประกอบแม่ แทนที่จะต้องแนบ event handlers บนองค์ประกอบลูกแต่ละตัว
  • ไม่จำเป็นต้องยกเลิกการผูก handler จากองค์ประกอบที่ถูกลบ และผูกเหตุการณ์สำหรับองค์ประกอบใหม่

อธิบายว่า `this` ทำงานอย่างไรใน JavaScript

ไม่มีคำอธิบายง่ายๆ สำหรับ this มันเป็นหนึ่งในแนวคิดที่สับสนที่สุดใน JavaScript คำอธิบายแบบหยาบๆ คือ ค่าของ this ขึ้นอยู่กับว่าฟังก์ชันถูกเรียกอย่างไร ฉันได้อ่านคำอธิบายมากมายเกี่ยวกับ this ออนไลน์ และฉันพบว่าคำอธิบายของ [Arnav Aggrawal] ชัดเจนที่สุด กฎต่อไปนี้ถูกนำมาใช้:

  1. หากใช้คีย์เวิร์ด new เมื่อเรียกฟังก์ชัน this ภายในฟังก์ชันจะเป็น object ใหม่เอี่ยม
  2. หากใช้ apply, call, หรือ bind เพื่อเรียก/สร้างฟังก์ชัน this ภายในฟังก์ชันจะเป็น object ที่ถูกส่งผ่านเป็น argument
  3. หากฟังก์ชันถูกเรียกเป็น method เช่น obj.method() - this คือ object ที่ฟังก์ชันเป็นคุณสมบัติของ
  4. หากฟังก์ชันถูกเรียกใช้เป็นการเรียกใช้ฟังก์ชันอิสระ ซึ่งหมายความว่าไม่ได้ถูกเรียกใช้โดยไม่มีเงื่อนไขใดๆ ข้างต้น this คือ global object ในเบราว์เซอร์ มันคือ object window หากอยู่ใน strict mode ('use strict') this จะเป็น undefined แทนที่จะเป็น global object
  5. หากกฎข้างต้นหลายข้อถูกนำมาใช้ กฎที่อยู่สูงกว่าจะชนะและจะกำหนดค่า this
  6. หากฟังก์ชันเป็น ES2015 arrow function มันจะละเว้นกฎทั้งหมดข้างต้นและรับค่า this ของขอบเขตโดยรอบในขณะที่ถูกสร้างขึ้น

สำหรับคำอธิบายเชิงลึก โปรดดู [บทความของเขาบน Medium]

คุณยกตัวอย่างวิธีที่การทำงานกับ this เปลี่ยนไปใน ES6 ได้หรือไม่?

ES6 ช่วยให้คุณสามารถใช้ [arrow functions] ซึ่งใช้ [enclosing lexical scope] โดยปกติแล้วจะสะดวก แต่จะป้องกันไม่ให้ผู้เรียกควบคุมบริบทผ่าน .call หรือ .apply ซึ่งผลที่ตามมาคือไลบรารีเช่น jQuery จะไม่ผูก this ในฟังก์ชัน event handler ของคุณอย่างถูกต้อง ดังนั้นจึงเป็นสิ่งสำคัญที่จะต้องจำสิ่งนี้ไว้เมื่อทำการ refactor แอปพลิเคชันเดิมขนาดใหญ่

อธิบายว่า prototypal inheritance ทำงานอย่างไร

วัตถุ JavaScript ทั้งหมดมีคุณสมบัติ __proto__ ยกเว้นวัตถุที่สร้างด้วย Object.create(null) ซึ่งเป็นการอ้างอิงถึงวัตถุอื่น ซึ่งเรียกว่า "prototype" ของวัตถุ เมื่อมีการเข้าถึงคุณสมบัติบนวัตถุ และหากไม่พบคุณสมบัตินั้นบนวัตถุ เอ็นจิ้น JavaScript จะดูที่ __proto__ ของวัตถุ และ __proto__ ของ __proto__ ไปเรื่อยๆ จนกว่าจะพบคุณสมบัติที่กำหนดไว้บน __proto__ ตัวใดตัวหนึ่ง หรือจนกว่าจะถึงจุดสิ้นสุดของ prototype chain พฤติกรรมนี้จำลองการสืบทอดแบบคลาสสิก แต่จริงๆ แล้วมันเป็นการ [มอบหมายอำนาจมากกว่าการสืบทอด]

ตัวอย่างของ Prototypal Inheritance

// Parent object constructor. function Animal(name) { this.name = name; } // Add a method to the parent object's prototype. Animal.prototype.makeSound = function () { console.log('The ' + this.constructor.name + ' makes a sound.'); }; // Child object constructor. function Dog(name) { Animal.call(this, name); // Call the parent constructor. } // Set the child object's prototype to be the parent's prototype. Object.setPrototypeOf(Dog.prototype, Animal.prototype); // Add a method to the child object's prototype. Dog.prototype.bark = function () { console.log('Woof!'); }; // Create a new instance of Dog. const bolt = new Dog('Bolt'); // Call methods on the child object. console.log(bolt.name); // "Bolt" bolt.makeSound(); // "The Dog makes a sound." bolt.bark(); // "Woof!"

สิ่งที่ควรทราบคือ:

  • .makeSound ไม่ได้ถูกกำหนดบน Dog ดังนั้นเอ็นจิ้นจึงขึ้นไปตาม prototype chain และพบ .makeSound ที่สืบทอดมาจาก Animal
  • การใช้ Object.create เพื่อสร้าง inheritance chain ไม่แนะนำอีกต่อไป ให้ใช้ Object.setPrototypeOf แทน

คุณคิดอย่างไรเกี่ยวกับ AMD vs CommonJS?

ทั้งสองเป็นวิธีในการนำระบบโมดูลมาใช้ ซึ่งไม่มีอยู่ใน JavaScript จนกระทั่ง ES2015 เข้ามา CommonJS เป็นแบบ synchronous ในขณะที่ AMD (Asynchronous Module Definition) เป็นแบบ asynchronous อย่างชัดเจน CommonJS ถูกออกแบบมาโดยคำนึงถึงการพัฒนาฝั่งเซิร์ฟเวอร์ ในขณะที่ AMD ซึ่งรองรับการโหลดโมดูลแบบ asynchronous มีจุดประสงค์เพื่อเบราว์เซอร์มากกว่า

ฉันพบว่าไวยากรณ์ AMD ค่อนข้างยืดยาว และ CommonJS ใกล้เคียงกับสไตล์ที่คุณจะเขียนคำสั่ง import ในภาษาอื่น ส่วนใหญ่แล้ว ฉันพบว่า AMD ไม่จำเป็น เพราะถ้าคุณให้บริการ JavaScript ทั้งหมดของคุณในไฟล์ bundle เดียวที่รวมเข้าด้วยกัน คุณจะไม่ได้รับประโยชน์จากคุณสมบัติการโหลดแบบ asynchronous นอกจากนี้ ไวยากรณ์ CommonJS ยังใกล้เคียงกับสไตล์ Node ในการเขียนโมดูล และมีการสลับบริบทน้อยลงเมื่อสลับระหว่างการพัฒนา JavaScript ฝั่งไคลเอ็นต์และฝั่งเซิร์ฟเวอร์

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

อธิบายว่าทำไมสิ่งต่อไปนี้จึงไม่ทำงานเป็น IIFE: `function foo(){ }();` อะไรที่ต้องเปลี่ยนเพื่อให้มันเป็น IIFE อย่างถูกต้อง?

IIFE ย่อมาจาก Immediately Invoked Function Expressions ตัวแยกวิเคราะห์ JavaScript อ่าน function foo(){ }(); เป็น function foo(){ } และ (); โดยที่ส่วนแรกคือ function declaration และส่วนหลัง (วงเล็บคู่) คือความพยายามในการเรียกฟังก์ชันแต่ไม่มีการระบุชื่อ จึงทำให้เกิด Uncaught SyntaxError: Unexpected token )

นี่คือสองวิธีในการแก้ไขที่เกี่ยวข้องกับการเพิ่มวงเล็บมากขึ้น: (function foo(){ })() และ (function foo(){ }()) คำสั่งที่ขึ้นต้นด้วย function ถือเป็น function declarations; การห่อฟังก์ชันนี้ด้วย () จะทำให้มันกลายเป็น function expression ซึ่งสามารถรันได้ด้วย () ที่ตามมา ฟังก์ชันเหล่านี้ไม่ถูกเปิดเผยใน global scope และคุณยังสามารถละเว้นชื่อของมันได้หากคุณไม่จำเป็นต้องอ้างอิงตัวเองภายใน body

คุณอาจใช้โอเปอเรเตอร์ void ได้เช่นกัน: void function foo(){ }(); โชคไม่ดีที่มีปัญหาหนึ่งกับการเข้าถึงดังกล่าว การประเมิน expression ที่กำหนดจะส่งคืน undefined เสมอ ดังนั้นหากฟังก์ชัน IIFE ของคุณส่งคืนอะไรก็ตาม คุณจะไม่สามารถใช้มันได้ ตัวอย่าง:

const foo = void (function bar() { return 'foo'; })(); console.log(foo); // undefined

ความแตกต่างระหว่างตัวแปรที่เป็น: `null`, `undefined` หรือ undeclared? คุณจะตรวจสอบสถานะเหล่านี้ได้อย่างไร?

Undeclared variables ถูกสร้างขึ้นเมื่อคุณกำหนดค่าให้กับตัวระบุที่ไม่ได้สร้างไว้ก่อนหน้าโดยใช้ var, let หรือ const ตัวแปรที่ไม่ได้ประกาศจะถูกกำหนดแบบ global นอกขอบเขตปัจจุบัน ใน strict mode จะมีการโยน ReferenceError เมื่อคุณพยายามกำหนดค่าให้กับตัวแปรที่ไม่ได้ประกาศ ตัวแปรที่ไม่ได้ประกาศนั้นไม่ดีเหมือนกับตัวแปร global ที่ไม่ดี หลีกเลี่ยงให้ได้มากที่สุด! ในการตรวจสอบ ให้ห่อการใช้งานไว้ในบล็อก try/catch

function foo() { x = 1; // Throws a ReferenceError in strict mode } foo(); console.log(x); // 1

ตัวแปรที่เป็น undefined คือตัวแปรที่ถูกประกาศแล้ว แต่ยังไม่ได้กำหนดค่า มันมีชนิดเป็น undefined หากฟังก์ชันไม่คืนค่าใดๆ เป็นผลลัพธ์ของการรัน และถูกกำหนดให้กับตัวแปร ตัวแปรนั้นก็จะมีค่าเป็น undefined ในการตรวจสอบ ให้เปรียบเทียบโดยใช้ตัวดำเนินการ strict equality (===) หรือ typeof ซึ่งจะให้สตริง 'undefined' โปรดทราบว่าคุณไม่ควรใช้ตัวดำเนินการ abstract equality เพื่อตรวจสอบ เนื่องจากมันจะคืนค่า true ด้วยหากค่าเป็น null

var foo; console.log(foo); // undefined console.log(foo === undefined); // true console.log(typeof foo === 'undefined'); // true console.log(foo == null); // true. Wrong, don't use this to check! function bar() {} var baz = bar(); console.log(baz); // undefined

ตัวแปรที่เป็น null จะถูกกำหนดค่า null อย่างชัดเจน มันแสดงถึงไม่มีค่าและแตกต่างจาก undefined ในแง่ที่ว่ามันถูกกำหนดอย่างชัดเจน ในการตรวจสอบ null ให้เปรียบเทียบโดยใช้ตัวดำเนินการ strict equality โปรดทราบว่าเช่นเดียวกับข้างต้น คุณไม่ควรใช้ตัวดำเนินการ abstract equality (==) เพื่อตรวจสอบ เนื่องจากมันจะคืนค่า true ด้วยหากค่าเป็น undefined

var foo = null; console.log(foo === null); // true console.log(typeof foo === 'object'); // true console.log(foo == undefined); // true. Wrong, don't use this to check!

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

Closure คืออะไร และคุณจะใช้มันอย่างไร/ทำไม?

Closure คือการรวมกันของฟังก์ชันและสภาพแวดล้อมทาง lexical ภายในฟังก์ชันนั้นถูกประกาศ คำว่า "lexical" หมายถึงข้อเท็จจริงที่ว่า lexical scoping ใช้ตำแหน่งที่ตัวแปรถูกประกาศภายในซอร์สโค้ดเพื่อกำหนดว่าตัวแปรนั้นใช้งานได้ที่ไหน Closures เป็นฟังก์ชันที่สามารถเข้าถึงตัวแปรของฟังก์ชันภายนอก (ที่ห่อหุ้ม) ได้—scope chain แม้หลังจากฟังก์ชันภายนอกได้คืนค่าแล้ว

ทำไมคุณถึงจะใช้มัน?

  • ความเป็นส่วนตัวของข้อมูล / การจำลองเมธอดส่วนตัวด้วย closures มักใช้ใน [module pattern]
  • [Partial applications หรือ currying]

คุณช่วยอธิบายความแตกต่างหลักระหว่าง `.forEach` loop และ `.map()` loop และทำไมคุณถึงเลือกใช้อันใดอันหนึ่ง?

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

forEach

  • วนซ้ำผ่านองค์ประกอบต่างๆ ในอาร์เรย์
  • รัน callback สำหรับแต่ละองค์ประกอบ
  • ไม่คืนค่า
const a = [1, 2, 3]; const doubled = a.forEach((num, index) => { // Do something with num and/or index. }); // doubled = undefined

map

  • วนซ้ำผ่านองค์ประกอบต่างๆ ในอาร์เรย์
  • "แมป" แต่ละองค์ประกอบไปยังองค์ประกอบใหม่โดยการเรียกฟังก์ชันบนแต่ละองค์ประกอบ สร้างอาร์เรย์ใหม่เป็นผลลัพธ์
const a = [1, 2, 3]; const doubled = a.map((num) => { return num * 2; }); // doubled = [2, 4, 6]

ความแตกต่างหลักระหว่าง .forEach และ .map() คือ .map() คืนค่าอาร์เรย์ใหม่ หากคุณต้องการผลลัพธ์ แต่ไม่ต้องการเปลี่ยนแปลงอาร์เรย์ต้นฉบับ .map() เป็นตัวเลือกที่ชัดเจน หากคุณเพียงต้องการวนซ้ำอาร์เรย์ forEach ก็เป็นตัวเลือกที่ดี

กรณีการใช้งานทั่วไปสำหรับ anonymous functions มีอะไรบ้าง?

สามารถใช้ใน IIFEs เพื่อห่อหุ้มโค้ดบางส่วนภายในขอบเขตท้องถิ่น เพื่อให้ตัวแปรที่ประกาศไว้ในนั้นไม่รั่วไหลไปยัง global scope

(function () { // Some code here. })();

เป็น callback ที่ใช้ครั้งเดียวและไม่จำเป็นต้องใช้ที่อื่น โค้ดจะดูเหมือนมีความเป็นอิสระและอ่านง่ายขึ้นเมื่อ handlers ถูกกำหนดไว้ในโค้ดที่เรียกใช้งานโดยตรง แทนที่จะต้องค้นหาที่อื่นเพื่อหา function body

setTimeout(function () { console.log('Hello world!'); }, 1000);

Arguments สำหรับโครงสร้างการเขียนโปรแกรมเชิงฟังก์ชันหรือ Lodash (คล้ายกับ callbacks)

const arr = [1, 2, 3]; const double = arr.map(function (el) { return el * 2; }); console.log(double); // [2, 4, 6]

คุณจัดระเบียบโค้ดอย่างไร? (module pattern, classical inheritance?)

ในอดีต ฉันใช้ Backbone สำหรับโมเดลของฉัน ซึ่งส่งเสริมแนวทาง OOP มากขึ้น โดยสร้างโมเดล Backbone และแนบเมธอดกับโมเดลเหล่านั้น

module pattern ยังคงยอดเยี่ยม แต่ในปัจจุบัน ฉันใช้ React/Redux ซึ่งใช้การไหลของข้อมูลแบบทิศทางเดียวที่อิงตามสถาปัตยกรรม Flux ฉันจะแทนโมเดลของแอปด้วยวัตถุธรรมดาและเขียนฟังก์ชันยูทิลิตี้แบบ pure function เพื่อจัดการวัตถุเหล่านี้ สถานะจะถูกจัดการโดยใช้ actions และ reducers เช่นเดียวกับแอปพลิเคชัน Redux อื่นๆ

ฉันหลีกเลี่ยงการใช้ classical inheritance หากเป็นไปได้ เมื่อและถ้าฉันใช้ ฉันจะยึดติดกับ [กฎเหล่านี้]

อะไรคือความแตกต่างระหว่าง host objects และ native objects?

Native objects คือวัตถุที่เป็นส่วนหนึ่งของภาษา JavaScript ที่กำหนดโดยข้อกำหนด ECMAScript เช่น String, Math, RegExp, Object, Function เป็นต้น

Host objects จัดทำโดยสภาพแวดล้อมรันไทม์ (เบราว์เซอร์หรือ Node) เช่น window, XMLHTTPRequest เป็นต้น

ความแตกต่างระหว่าง: `function Person(){}`, `var person = Person()`, และ `var person = new Person()`?

คำถามนี้ค่อนข้างคลุมเครือ ฉันเดาว่าตั้งใจจะถามเกี่ยวกับ constructors ใน JavaScript ในทางเทคนิคแล้ว function Person(){} เป็นเพียงการประกาศฟังก์ชันปกติ ข้อตกลงคือการใช้ PascalCase สำหรับฟังก์ชันที่ตั้งใจจะใช้เป็น constructors

var person = Person() เรียกใช้ Person เป็นฟังก์ชัน ไม่ใช่เป็น constructor การเรียกใช้ในลักษณะนี้เป็นข้อผิดพลาดทั่วไปหากตั้งใจให้ฟังก์ชันใช้เป็น constructor โดยทั่วไปแล้ว constructor จะไม่คืนค่าใดๆ ดังนั้นการเรียกใช้ constructor เหมือนฟังก์ชันปกติจะคืนค่า undefined และนั่นจะถูกกำหนดให้กับตัวแปรที่ตั้งใจให้เป็น instance

var person = new Person() สร้าง instance ของ object Person โดยใช้ operator new ซึ่งสืบทอดมาจาก Person.prototype อีกทางเลือกหนึ่งคือการใช้ Object.create เช่น: Object.create(Person.prototype)

function Person(name) { this.name = name; } var person = Person('John'); console.log(person); // undefined console.log(person.name); // Uncaught TypeError: Cannot read property 'name' of undefined var person = new Person('John'); console.log(person); // Person { name: "John" } console.log(person.name); // "john"

อะไรคือความแตกต่างระหว่าง `.call` และ `.apply`?

ทั้ง .call และ .apply ใช้สำหรับเรียกใช้ฟังก์ชันและพารามิเตอร์แรกจะถูกใช้เป็นค่าของ this ภายในฟังก์ชัน อย่างไรก็ตาม .call รับอาร์กิวเมนต์ที่คั่นด้วยเครื่องหมายจุลภาคเป็นอาร์กิวเมนต์ถัดไป ในขณะที่ .apply รับอาร์เรย์ของอาร์กิวเมนต์เป็นอาร์กิวเมนต์ถัดไป วิธีง่ายๆ ในการจำคือ C สำหรับ call และ comma-separated และ A สำหรับ apply และ an array of arguments

function add(a, b) { return a + b; } console.log(add.call(null, 1, 2)); // 3 console.log(add.apply(null, [1, 2])); // 3

อธิบาย `Function.prototype.bind`

นำมาจาก [MDN] โดยตรง:

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

จากประสบการณ์ของฉัน มันมีประโยชน์ที่สุดสำหรับการผูกค่าของ this ในเมธอดของคลาสที่คุณต้องการส่งผ่านไปยังฟังก์ชันอื่น สิ่งนี้มักทำใน React components

คุณจะใช้ `document.write()` เมื่อใด?

document.write() เขียนสตริงข้อความลงใน document stream ที่เปิดโดย document.open() เมื่อ document.write() ถูกรันหลังจากโหลดหน้าเว็บแล้ว มันจะเรียก document.open ซึ่งจะล้างเอกสารทั้งหมด (<head> และ <body> ถูกลบ!) และแทนที่เนื้อหาด้วยค่าพารามิเตอร์ที่ให้มา ดังนั้นโดยทั่วไปจึงถือว่าอันตรายและมีแนวโน้มที่จะถูกใช้ผิดวัตถุประสงค์

มีคำตอบออนไลน์บางส่วนที่อธิบายว่า document.write() ถูกใช้ในโค้ด analytics หรือ [เมื่อคุณต้องการรวมสไตล์ที่ควรทำงานเฉพาะเมื่อ JavaScript ถูกเปิดใช้งาน] มันยังถูกใช้ใน HTML5 boilerplate เพื่อ [โหลดสคริปต์พร้อมกันและรักษาลำดับการรัน]! อย่างไรก็ตาม ฉันสงสัยว่าเหตุผลเหล่านั้นอาจล้าสมัยไปแล้ว และในยุคปัจจุบันสามารถทำได้โดยไม่ต้องใช้ document.write() โปรดแก้ไขฉันด้วยหากฉันเข้าใจผิดเกี่ยวกับเรื่องนี้

อะไรคือความแตกต่างระหว่าง feature detection, feature inference และการใช้ UA string?

Feature Detection

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

if ('geolocation' in navigator) { // Can use navigator.geolocation } else { // Handle lack of feature }

[Modernizr] เป็นไลบรารีที่ยอดเยี่ยมสำหรับการจัดการ feature detection

Feature Inference

Feature inference ตรวจสอบคุณสมบัติเหมือน feature detection แต่ใช้ฟังก์ชันอื่นเพราะมันสันนิษฐานว่าคุณสมบัตินั้นจะยังคงอยู่ เช่น:

if (document.getElementsByTagName) { element = document.getElementById(id); }

สิ่งนี้ไม่แนะนำจริงๆ Feature detection นั้นแม่นยำกว่า

UA String

นี่คือสตริงที่เบราว์เซอร์รายงานซึ่งช่วยให้ peer โปรโตคอลเครือข่ายสามารถระบุประเภทแอปพลิเคชัน ระบบปฏิบัติการ ผู้จำหน่ายซอฟต์แวร์ หรือเวอร์ชันซอฟต์แวร์ของ user agent ของซอฟต์แวร์ที่ร้องขอ สามารถเข้าถึงได้ผ่าน navigator.userAgent อย่างไรก็ตาม สตริงนั้นซับซ้อนในการแยกวิเคราะห์และสามารถปลอมแปลงได้ ตัวอย่างเช่น Chrome รายงานว่าเป็นทั้ง Chrome และ Safari ดังนั้นในการตรวจจับ Safari คุณต้องตรวจสอบสตริง Safari และการไม่มีสตริง Chrome หลีกเลี่ยงวิธีนี้

อธิบาย Ajax อย่างละเอียดที่สุดเท่าที่จะเป็นไปได้

Ajax (asynchronous JavaScript and XML) เป็นชุดของเทคนิคการพัฒนาเว็บที่ใช้เทคโนโลยีเว็บหลายอย่างบนฝั่งไคลเอ็นต์เพื่อสร้างเว็บแอปพลิเคชันแบบ asynchronous ด้วย Ajax แอปพลิเคชันเว็บสามารถส่งและดึงข้อมูลจากเซิร์ฟเวอร์แบบ asynchronous (ในพื้นหลัง) โดยไม่รบกวนการแสดงผลและพฤติกรรมของหน้าเว็บที่มีอยู่ โดยการแยกเลเยอร์การแลกเปลี่ยนข้อมูลออกจากเลเยอร์การนำเสนอ Ajax อนุญาตให้หน้าเว็บ และโดยขยายไปถึงเว็บแอปพลิเคชัน สามารถเปลี่ยนเนื้อหาแบบไดนามิกได้โดยไม่จำเป็นต้องโหลดหน้าทั้งหมดซ้ำ ในทางปฏิบัติ การใช้งานสมัยใหม่มักใช้ JSON แทน XML เนื่องจากข้อดีของ JSON ที่เป็น native ของ JavaScript

API XMLHttpRequest มักถูกใช้สำหรับการสื่อสารแบบ asynchronous หรือในปัจจุบันคือ API fetch()

ข้อดีและข้อเสียของการใช้ Ajax มีอะไรบ้าง?

ข้อดี

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

ข้อเสีย

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

อธิบายว่า JSONP ทำงานอย่างไร (และทำไมจึงไม่ใช่ Ajax จริงๆ)

JSONP (JSON with Padding) เป็นวิธีการที่ใช้กันทั่วไปในการหลีกเลี่ยงนโยบาย cross-domain ในเว็บเบราว์เซอร์ เนื่องจากคำขอ Ajax จากหน้าปัจจุบันไปยัง cross-origin domain ไม่ได้รับอนุญาต

JSONP ทำงานโดยการส่งคำขอไปยัง cross-origin domain ผ่านแท็ก <script> และมักจะมีพารามิเตอร์คิวรี callback ตัวอย่างเช่น: https://example.com?callback=printData จากนั้นเซิร์ฟเวอร์จะห่อหุ้มข้อมูลภายในฟังก์ชันที่ชื่อว่า printData และส่งคืนไปยังไคลเอ็นต์

<script> function printData(data) { console.log(`My name is ${data.name}!`); } </script> <script src="[https://example.com?callback=printData](https://example.com?callback=printData)"></script>
printData({ name: 'Yang Shun' });

ไคลเอ็นต์ต้องมีฟังก์ชัน printData ใน global scope และฟังก์ชันจะถูกรันโดยไคลเอ็นต์เมื่อได้รับข้อมูลตอบกลับจาก cross-origin domain

JSONP อาจไม่ปลอดภัยและมีผลกระทบด้านความปลอดภัยบางประการ เนื่องจาก JSONP เป็น JavaScript จริงๆ จึงสามารถทำทุกอย่างที่ JavaScript ทำได้ ดังนั้นคุณต้องเชื่อถือผู้ให้บริการข้อมูล JSONP

ปัจจุบัน [CORS] เป็นแนวทางที่แนะนำ และ JSONP ถูกมองว่าเป็นวิธีการที่ใช้เล่ห์เหลี่ยม

คุณเคยใช้ JavaScript templating หรือไม่? ถ้าเคย คุณใช้ไลบรารีใดบ้าง?

ใช่ Handlebars, Underscore, Lodash, AngularJS และ JSX ฉันไม่ชอบการใช้ templating ใน AngularJS เพราะมันใช้สตริงใน directives อย่างมาก และข้อผิดพลาดในการพิมพ์จะไม่ถูกตรวจพบ JSX คือสิ่งใหม่ที่ฉันชอบมากที่สุด เนื่องจากมันใกล้เคียงกับ JavaScript มากและแทบไม่มีไวยากรณ์ให้เรียนรู้เลย ปัจจุบันคุณยังสามารถใช้ ES2015 template string literals เป็นวิธีที่รวดเร็วในการสร้างเทมเพลตโดยไม่ต้องพึ่งพาโค้ดจากบุคคลที่สาม

const template = `<div>My name is: ${name}</div>`;

อย่างไรก็ตาม โปรดระวัง XSS ที่อาจเกิดขึ้นในแนวทางข้างต้น เนื่องจากเนื้อหาไม่ได้รับการ escape ให้คุณ ซึ่งแตกต่างจากในไลบรารี templating

อธิบาย "hoisting"

Hoisting เป็นคำที่ใช้อธิบายพฤติกรรมของการประกาศตัวแปรในโค้ดของคุณ ตัวแปรที่ประกาศหรือเริ่มต้นด้วยคีย์เวิร์ด var จะมีการประกาศของมัน "ย้าย" ขึ้นไปด้านบนของขอบเขตโมดูล/ฟังก์ชัน ซึ่งเราเรียกว่า hoisting อย่างไรก็ตาม เฉพาะการประกาศเท่านั้นที่ถูก hoist การกำหนดค่า (ถ้ามี) จะยังคงอยู่ที่เดิม

โปรดทราบว่าการประกาศไม่ได้ถูกย้ายจริง - เอ็นจิ้น JavaScript จะแยกวิเคราะห์การประกาศระหว่างการคอมไพล์และรับทราบการประกาศและขอบเขตของมัน มันเข้าใจง่ายกว่าถ้าคุณเห็นภาพการประกาศว่าถูก hoist ไปด้านบนของขอบเขตของมัน มาอธิบายด้วยตัวอย่างบางส่วน

console.log(foo); // undefined var foo = 1; console.log(foo); // 1

การประกาศฟังก์ชัน (Function declarations) จะมีการ hoist body ในขณะที่ function expressions (ที่เขียนในรูปแบบของการประกาศตัวแปร) จะมีการ hoist เฉพาะการประกาศตัวแปรเท่านั้น

// Function Declaration console.log(foo); // [Function: foo] foo(); // 'FOOOOO' function foo() { console.log('FOOOOO'); } console.log(foo); // [Function: foo] // Function Expression console.log(bar); // undefined bar(); // Uncaught TypeError: bar is not a function var bar = function () { console.log('BARRRR'); }; console.log(bar); // [Function: bar]

ตัวแปรที่ประกาศผ่าน let และ const ก็ถูก hoist เช่นกัน อย่างไรก็ตาม แตกต่างจาก var และ function พวกมันไม่ได้ถูกกำหนดค่าเริ่มต้น และการเข้าถึงพวกมันก่อนการประกาศจะส่งผลให้เกิดข้อยกเว้น ReferenceError ตัวแปรจะอยู่ใน "temporal dead zone" ตั้งแต่เริ่มต้นบล็อกจนกว่าการประกาศจะถูกประมวลผล

x; // undefined y; // Reference error: y is not defined var x = 'local'; let y = 'local';

อธิบาย event bubbling

เมื่อเหตุการณ์ถูกเรียกใช้บนองค์ประกอบ DOM มันจะพยายามจัดการเหตุการณ์หากมี listener แนบอยู่ จากนั้นเหตุการณ์จะบับเบิ้ลขึ้นไปยังองค์ประกอบแม่และสิ่งเดียวกันก็เกิดขึ้น การบับเบิ้ลนี้เกิดขึ้นตามลำดับชั้นบรรพบุรุษขององค์ประกอบไปจนถึง document Event bubbling คือกลไกเบื้องหลัง event delegation

ความแตกต่างระหว่าง "attribute" และ "property" คืออะไร?

Attributes ถูกกำหนดใน HTML markup แต่ properties ถูกกำหนดใน DOM เพื่อแสดงให้เห็นความแตกต่าง ลองจินตนาการว่าเรามีช่องข้อความนี้ใน HTML: <input type="text" value="Hello">

const input = document.querySelector('input'); console.log(input.getAttribute('value')); // Hello console.log(input.value); // Hello

แต่หลังจากที่คุณเปลี่ยนค่าของช่องข้อความโดยเพิ่ม "World!" เข้าไปแล้ว นี่จะกลายเป็น:

console.log(input.getAttribute('value')); // Hello console.log(input.value); // Hello World!

ทำไมการขยาย built-in JavaScript objects จึงไม่ใช่ความคิดที่ดี?

การขยาย built-in/native JavaScript object หมายถึงการเพิ่ม properties/functions ให้กับ prototype ของมัน แม้ว่าสิ่งนี้อาจดูเหมือนเป็นความคิดที่ดีในตอนแรก แต่ในทางปฏิบัติมันอันตราย ลองจินตนาการว่าโค้ดของคุณใช้ไลบรารีไม่กี่ตัวที่ต่างก็ขยาย Array.prototype โดยการเพิ่มเมธอด contains เดียวกัน การใช้งานจะเขียนทับกันและโค้ดของคุณจะพังหากพฤติกรรมของสองเมธอดนี้ไม่เหมือนกัน

เวลาเดียวที่คุณอาจต้องการขยาย native object คือเมื่อคุณต้องการสร้าง polyfill ซึ่งโดยพื้นฐานแล้วเป็นการจัดเตรียมการใช้งานของคุณเองสำหรับเมธอดที่เป็นส่วนหนึ่งของข้อกำหนด JavaScript แต่อาจไม่มีอยู่ในเบราว์เซอร์ของผู้ใช้เนื่องจากเป็นเบราว์เซอร์รุ่นเก่า

ความแตกต่างระหว่าง document `load` event และ document `DOMContentLoaded` event?

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

เหตุการณ์ load ของ window จะถูกเรียกใช้หลังจาก DOM และทรัพยากรและสินทรัพย์ที่เกี่ยวข้องทั้งหมดโหลดเสร็จสิ้นแล้วเท่านั้น

อะไรคือความแตกต่างระหว่าง `==` และ `===`?

== คือตัวดำเนินการ abstract equality ในขณะที่ === คือตัวดำเนินการ strict equality ตัวดำเนินการ == จะเปรียบเทียบความเท่าเทียมกันหลังจากทำการแปลงประเภทที่จำเป็น ตัวดำเนินการ === จะไม่ทำการแปลงประเภท ดังนั้นหากค่าสองค่าไม่ใช่ประเภทเดียวกัน === จะคืนค่า false เมื่อใช้ == สิ่งแปลกๆ อาจเกิดขึ้นได้ เช่น:

1 == '1'; // true 1 == [1]; // true 1 == true; // true 0 == ''; // true 0 == '0'; // true 0 == false; // true

คำแนะนำของฉันคืออย่าใช้ตัวดำเนินการ == ยกเว้นเพื่อความสะดวกเมื่อเปรียบเทียบกับ null หรือ undefined โดยที่ a == null จะคืนค่า true หาก a เป็น null หรือ undefined

var a = null; console.log(a == null); // true console.log(a == undefined); // true

อธิบาย same-origin policy เกี่ยวกับ JavaScript

Same-origin policy ป้องกันไม่ให้ JavaScript ส่งคำขอข้ามขอบเขตโดเมน ต้นทางถูกกำหนดให้เป็นการรวมกันของ URI scheme, hostname และหมายเลขพอร์ต นโยบายนี้ป้องกันสคริปต์ที่เป็นอันตรายบนหน้าหนึ่งจากการเข้าถึงข้อมูลที่ละเอียดอ่อนบนหน้าเว็บอื่นผ่าน Document Object Model ของหน้านั้น

ทำให้สิ่งนี้ใช้งานได้:

duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]
function duplicate(arr) { return arr.concat(arr); } duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

หรือใช้ ES6:

const duplicate = (arr) => [...arr, ...arr]; duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

ทำไมถึงเรียกว่า Ternary expression อะไรคือสิ่งที่คำว่า "Ternary" บ่งชี้?

คำว่า "Ternary" บ่งชี้ถึงสาม และ ternary expression รับสาม operands คือเงื่อนไขการทดสอบ, expression "then" และ expression "else" Ternary expression ไม่ได้เฉพาะเจาะจงกับ JavaScript และฉันไม่แน่ใจว่าทำไมมันถึงอยู่ในรายการนี้ด้วยซ้ำ

อะไรคือ `"use strict";`? ข้อดีและข้อเสียของการใช้มันมีอะไรบ้าง?

'use strict' เป็นคำสั่งที่ใช้เพื่อเปิดใช้งาน strict mode สำหรับทั้งสคริปต์หรือฟังก์ชันแต่ละฟังก์ชัน Strict mode เป็นวิธีเลือกใช้รูปแบบที่จำกัดของ JavaScript

ข้อดี:

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

ข้อเสีย:

  • คุณสมบัติที่ขาดหายไปหลายอย่างที่นักพัฒนาบางคนอาจคุ้นเคย
  • ไม่สามารถเข้าถึง function.caller และ function.arguments ได้อีกต่อไป
  • การรวมสคริปต์ที่เขียนใน strict mode ที่แตกต่างกันอาจทำให้เกิดปัญหาได้

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

สร้าง for loop ที่วนซ้ำจนถึง `100` โดยแสดงผล **"fizz"** ที่พหุคูณของ `3`, **"buzz"** ที่พหุคูณของ `5` และ **"fizzbuzz"** ที่พหุคูณของ `3` และ `5`

ลองดู FizzBuzz เวอร์ชั่นนี้โดย [Paul Irish]

for (let i = 1; i <= 100; i++) { let f = i % 3 == 0, b = i % 5 == 0; console.log(f ? (b ? 'FizzBuzz' : 'Fizz') : b ? 'Buzz' : i); }

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

ทำไมโดยทั่วไปแล้ว การปล่อยให้ global scope ของเว็บไซต์เป็นไปตามที่เป็นอยู่และไม่แตะต้องมันจึงเป็นความคิดที่ดี?

ทุกสคริปต์สามารถเข้าถึง global scope ได้ และถ้าทุกคนใช้ global namespace เพื่อกำหนดตัวแปรของตน การชนกันน่าจะเกิดขึ้น ใช้ module pattern (IIFEs) เพื่อห่อหุ้มตัวแปรของคุณภายใน local namespace

ทำไมคุณถึงใช้บางอย่างเช่น `load` event? เหตุการณ์นี้มีข้อเสียหรือไม่? คุณรู้จักทางเลือกอื่นหรือไม่ และทำไมคุณถึงใช้ทางเลือกเหล่านั้น?

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

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

อธิบายว่า Single Page App คืออะไร และจะทำให้เป็น SEO-friendly ได้อย่างไร

เนื้อหาด้านล่างนี้มาจาก [Grab Front End Guide] ที่ยอดเยี่ยม ซึ่งบังเอิญเขียนโดยฉันเอง!

ปัจจุบันนักพัฒนาเว็บมักเรียกผลิตภัณฑ์ที่พวกเขาสร้างว่าเป็นเว็บแอป (web apps) มากกว่าเว็บไซต์ (websites) แม้จะไม่มีความแตกต่างที่เข้มงวดระหว่างสองคำนี้ แต่เว็บแอปมักจะมีปฏิสัมพันธ์สูงและเปลี่ยนแปลงได้ตลอดเวลา ทำให้ผู้ใช้สามารถดำเนินการและรับการตอบสนองต่อการกระทำของพวกเขาได้ โดยทั่วไปแล้ว เบราว์เซอร์จะได้รับ HTML จากเซิร์ฟเวอร์และแสดงผล เมื่อผู้ใช้นำทางไปยัง URL อื่น จำเป็นต้องมีการรีเฟรชหน้าทั้งหมด และเซิร์ฟเวอร์จะส่ง HTML ใหม่สดไปยังหน้าใหม่ นี่เรียกว่า server-side rendering

อย่างไรก็ตาม ใน SPA สมัยใหม่ จะใช้ client-side rendering แทน เบราว์เซอร์จะโหลดหน้าเริ่มต้นจากเซิร์ฟเวอร์ พร้อมกับสคริปต์ (เฟรมเวิร์ก, ไลบรารี, โค้ดแอป) และสไตล์ชีทที่จำเป็นสำหรับทั้งแอป เมื่อผู้ใช้นำทางไปยังหน้าอื่น จะไม่มีการรีเฟรชหน้าเกิดขึ้น URL ของหน้าจะได้รับการอัปเดตผ่าน [HTML5 History API] ข้อมูลใหม่ที่จำเป็นสำหรับหน้าใหม่ ซึ่งโดยทั่วไปอยู่ในรูปแบบ JSON จะถูกดึงโดยเบราว์เซอร์ผ่านคำขอ [AJAX] ไปยังเซิร์ฟเวอร์ จากนั้น SPA จะอัปเดตหน้าเว็บด้วยข้อมูลผ่าน JavaScript แบบไดนามิก ซึ่งได้ดาวน์โหลดไว้แล้วในการโหลดหน้าเริ่มต้น โมเดลนี้คล้ายกับการทำงานของแอปพลิเคชันมือถือแบบ native

ประโยชน์:

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

ข้อเสีย:

  • การโหลดหน้าเริ่มต้นที่หนักกว่าเนื่องจากการโหลดเฟรมเวิร์ก, โค้ดแอป และสินทรัพย์ที่จำเป็นสำหรับหลายหน้า
  • มีขั้นตอนเพิ่มเติมที่ต้องทำบนเซิร์ฟเวอร์ของคุณ ซึ่งคือการกำหนดค่าให้ส่งคำขอทั้งหมดไปยังจุดเข้าใช้งานเดียว และอนุญาตให้ client-side routing เข้ามาควบคุมจากตรงนั้น
  • SPAs อาศัย JavaScript ในการแสดงผลเนื้อหา แต่ไม่ใช่เครื่องมือค้นหาทั้งหมดที่รัน JavaScript ในระหว่างการรวบรวมข้อมูล และอาจเห็นเนื้อหาว่างเปล่าในหน้าของคุณ สิ่งนี้ทำให้การเพิ่มประสิทธิภาพกลไกค้นหา (SEO) ของแอปของคุณเสียหายโดยไม่ตั้งใจ อย่างไรก็ตาม ส่วนใหญ่แล้ว เมื่อคุณกำลังสร้างแอป SEO ไม่ใช่ปัจจัยที่สำคัญที่สุด เนื่องจากเนื้อหาทั้งหมดไม่จำเป็นต้องได้รับการจัดทำดัชนีโดยเครื่องมือค้นหา หากต้องการแก้ไขปัญหานี้ คุณสามารถ server-side render แอปของคุณ หรือใช้บริการเช่น [Prerender] เพื่อ "แสดงผล JavaScript ของคุณในเบราว์เซอร์ บันทึก HTML แบบคงที่ และส่งคืนให้กับโปรแกรมรวบรวมข้อมูล"

ประสบการณ์ของคุณกับ Promises และ/หรือ polyfills ของมันมีแค่ไหน?

มีความรู้ในการทำงานเกี่ยวกับมัน Promise คือออบเจกต์ที่อาจสร้างค่าเดียวได้ในอนาคต: ไม่ว่าจะเป็นค่าที่ได้รับการแก้ไขแล้ว หรือเหตุผลที่ไม่ได้รับการแก้ไข (เช่น เกิดข้อผิดพลาดเครือข่าย) Promise อาจอยู่ใน 1 ใน 3 สถานะที่เป็นไปได้: fulfilled, rejected, หรือ pending ผู้ใช้ Promise สามารถแนบ callbacks เพื่อจัดการค่าที่ fulfilled หรือเหตุผลของการ rejected ได้

polyfills ทั่วไปบางตัวคือ $.deferred, Q และ Bluebird แต่ไม่ใช่ทั้งหมดที่สอดคล้องกับข้อกำหนด ES2015 รองรับ Promises โดยไม่จำเป็นต้องใช้ polyfills ในปัจจุบัน

ข้อดีและข้อเสียของการใช้ Promises แทน callbacks มีอะไรบ้าง?

ข้อดี

  • หลีกเลี่ยง callback hell ที่อาจอ่านยาก
  • ทำให้ง่ายต่อการเขียนโค้ด asynchronous แบบลำดับที่อ่านง่ายด้วย .then()
  • ทำให้ง่ายต่อการเขียนโค้ด asynchronous แบบขนานด้วย Promise.all()
  • ด้วย promises สถานการณ์เหล่านี้ที่เกิดขึ้นในการเขียนโค้ดแบบ callbacks-only จะไม่เกิดขึ้น:
    • เรียก callback เร็วเกินไป
    • เรียก callback ช้าเกินไป (หรือไม่เคยเรียกเลย)
    • เรียก callback น้อยเกินไปหรือมากเกินไป
    • ล้มเหลวในการส่งมอบสภาพแวดล้อม/พารามิเตอร์ที่จำเป็น
    • กลืนกินข้อผิดพลาด/ข้อยกเว้นที่อาจเกิดขึ้น

ข้อเสีย

  • โค้ดซับซ้อนขึ้นเล็กน้อย (ถกเถียงได้)
  • ในเบราว์เซอร์รุ่นเก่าที่ไม่รองรับ ES2015 คุณต้องโหลด polyfill เพื่อใช้งาน

ข้อดี/ข้อเสียของการเขียนโค้ด JavaScript ในภาษาที่คอมไพล์เป็น JavaScript มีอะไรบ้าง?

ตัวอย่างของภาษาที่คอมไพล์เป็น JavaScript ได้แก่ CoffeeScript, Elm, ClojureScript, PureScript และ TypeScript

ข้อดี:

  • แก้ไขปัญหาบางอย่างที่ยืดเยื้อใน JavaScript และยับยั้งการใช้ JavaScript anti-patterns
  • ช่วยให้คุณเขียนโค้ดได้สั้นลง โดยให้ syntactic sugar เหนือ JavaScript ซึ่งฉันคิดว่า ES5 ขาดไป แต่ ES2015 นั้นยอดเยี่ยม
  • ประเภทคงที่นั้นยอดเยี่ยม (ในกรณีของ TypeScript) สำหรับโครงการขนาดใหญ่ที่ต้องดูแลรักษาในระยะยาว

ข้อเสีย:

  • ต้องการกระบวนการสร้าง/คอมไพล์ เนื่องจากเบราว์เซอร์รันเฉพาะ JavaScript และโค้ดของคุณจะต้องถูกคอมไพล์เป็น JavaScript ก่อนที่จะให้บริการแก่เบราว์เซอร์
  • การดีบักอาจเป็นเรื่องยากหาก source maps ของคุณไม่แมปกับ source ที่คอมไพล์ไว้ล่วงหน้าอย่างดี
  • นักพัฒนาส่วนใหญ่ไม่คุ้นเคยกับภาษาเหล่านี้และจะต้องเรียนรู้ มีค่าใช้จ่ายในการเริ่มต้นสำหรับทีมของคุณหากคุณใช้มันสำหรับโครงการของคุณ
  • ชุมชนที่เล็กกว่า (ขึ้นอยู่กับภาษา) ซึ่งหมายถึงทรัพยากร บทเรียน ไลบรารี และเครื่องมือจะหาได้ยากขึ้น
  • การรองรับ IDE/editor อาจขาดหายไป
  • ภาษาเหล่านี้จะล้าหลังมาตรฐาน JavaScript ล่าสุดเสมอ
  • นักพัฒนาควรตระหนักว่าโค้ดของพวกเขาถูกคอมไพล์เป็นอะไร เพราะนั่นคือสิ่งที่จะรันจริงๆ และนั่นคือสิ่งที่สำคัญในท้ายที่สุด

ในทางปฏิบัติ ES2015 ได้ปรับปรุง JavaScript อย่างมากและทำให้การเขียนดีขึ้นมาก ฉันไม่เห็นความจำเป็นสำหรับ CoffeeScript ในปัจจุบันแล้ว

คุณใช้เครื่องมือและเทคนิคอะไรในการดีบักโค้ด JavaScript?

  • React และ Redux
    • [React Devtools]
    • [Redux Devtools]
  • Vue
    • [Vue Devtools]
  • JavaScript
    • [Chrome Devtools]
    • คำสั่ง debugger
    • การดีบักแบบ console.log แบบเก่าที่ดี

คุณใช้โครงสร้างภาษาใดในการวนซ้ำคุณสมบัติของออบเจกต์และรายการอาร์เรย์?

สำหรับออบเจกต์:

  • for-in loops - for (var property in obj) { console.log(property); } อย่างไรก็ตาม สิ่งนี้จะวนซ้ำผ่านคุณสมบัติที่สืบทอดมาด้วย และคุณจะต้องเพิ่มการตรวจสอบ obj.hasOwnProperty(property) ก่อนที่จะใช้งาน
  • Object.keys() - Object.keys(obj).forEach(function (property) { ... }) Object.keys() เป็น static method ที่จะแสดงคุณสมบัติที่สามารถวนซ้ำได้ทั้งหมดของออบเจกต์ที่คุณส่งให้
  • Object.getOwnPropertyNames() - Object.getOwnPropertyNames(obj).forEach(function (property) { ... }) Object.getOwnPropertyNames() เป็น static method ที่จะแสดงคุณสมบัติที่สามารถวนซ้ำได้และไม่สามารถวนซ้ำได้ทั้งหมดของออบเจกต์ที่คุณส่งให้

สำหรับอาร์เรย์:

  • for loops - for (var i = 0; i < arr.length; i++) ข้อผิดพลาดทั่วไปในที่นี้คือ var อยู่ใน function scope ไม่ใช่ block scope และส่วนใหญ่คุณต้องการตัวแปร iterator ที่มี block scope ES2015 แนะนำ let ซึ่งมี block scope และแนะนำให้ใช้แทน ดังนั้นนี่จะกลายเป็น: for (let i = 0; i < arr.length; i++)
  • forEach - arr.forEach(function (el, index) { ... }) โครงสร้างนี้อาจสะดวกกว่าในบางครั้งเพราะคุณไม่จำเป็นต้องใช้ index หากคุณต้องการเพียงแค่องค์ประกอบของอาร์เรย์ นอกจากนี้ยังมีเมธอด every และ some ที่จะช่วยให้คุณยุติการวนซ้ำก่อนเวลาได้
  • for-of loops - for (let elem of arr) { ... } ES6 แนะนำลูปใหม่คือ for-of loop ที่ช่วยให้คุณสามารถวนซ้ำออบเจกต์ที่สอดคล้องกับ iterable protocol เช่น String, Array, Map, Set เป็นต้น มันรวมข้อดีของ for loop และเมธอด forEach() ข้อดีของ for loop คือคุณสามารถหยุดการวนซ้ำได้ และข้อดีของ forEach() คือมันกระชับกว่า for loop เพราะคุณไม่จำเป็นต้องมีตัวแปรนับ ด้วย for-of loop คุณจะได้รับทั้งความสามารถในการหยุดการวนซ้ำและไวยากรณ์ที่กระชับยิ่งขึ้น

ส่วนใหญ่แล้ว ฉันจะชอบเมธอด .forEach แต่จริงๆ แล้วมันขึ้นอยู่กับสิ่งที่คุณพยายามทำ ก่อน ES6 เราใช้ for loops เมื่อเราต้องการยุติการวนซ้ำก่อนกำหนดโดยใช้ break แต่ตอนนี้ด้วย ES6 เราสามารถทำได้ด้วย for-of loops ฉันจะใช้ for loops เมื่อฉันต้องการความยืดหยุ่นมากยิ่งขึ้น เช่น การเพิ่ม iterator มากกว่าหนึ่งครั้งต่อการวนซ้ำ

นอกจากนี้ เมื่อใช้ for-of loop หากคุณต้องการเข้าถึงทั้ง index และ value ของแต่ละองค์ประกอบอาร์เรย์ คุณสามารถทำได้ด้วยเมธอด ES6 Array entries() และ destructuring:

const arr = ['a', 'b', 'c']; for (let [index, elem] of arr.entries()) { console.log(index, ': ', elem); }

อธิบายความแตกต่างระหว่าง mutable และ immutable objects

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

อะไรคือตัวอย่างของ immutable object ใน JavaScript?

ใน JavaScript ชนิดข้อมูลบางชนิดที่สร้างมาพร้อม (ตัวเลข, สตริง) เป็น immutable แต่ custom object โดยทั่วไปเป็น mutable

immutable JavaScript objects ที่สร้างมาพร้อมบางตัวคือ Math, Date

ต่อไปนี้คือวิธีบางอย่างในการเพิ่ม/จำลอง immutability บน plain JavaScript objects

Object Constant Properties

โดยการรวม writable: false และ configurable: false คุณสามารถสร้างค่าคงที่ (ไม่สามารถเปลี่ยนแปลง, กำหนดใหม่ หรือลบได้) เป็นคุณสมบัติของออบเจกต์ เช่น:

let myObject = {}; Object.defineProperty(myObject, 'number', { value: 42, writable: false, configurable: false, }); console.log(myObject.number); // 42 myObject.number = 43; console.log(myObject.number); // 42

Prevent Extensions

หากคุณต้องการป้องกันไม่ให้ออบเจกต์มีคุณสมบัติใหม่เพิ่มเติมเข้ามา แต่ยังคงคุณสมบัติอื่นๆ ของออบเจกต์ไว้ตามเดิม ให้เรียกใช้ Object.preventExtensions(...):

var myObject = { a: 2, }; Object.preventExtensions(myObject); myObject.b = 3; myObject.b; // undefined

ใน non-strict mode การสร้าง b จะล้มเหลวอย่างเงียบๆ ใน strict mode จะโยน TypeError

Seal

Object.seal() สร้างออบเจกต์ที่ "sealed" ซึ่งหมายความว่ามันรับออบเจกต์ที่มีอยู่และโดยพื้นฐานแล้วเรียก Object.preventExtensions() บนมัน แต่ยังทำเครื่องหมายคุณสมบัติที่มีอยู่ทั้งหมดเป็น configurable: false ด้วย

ดังนั้น คุณไม่เพียงแต่ไม่สามารถเพิ่มคุณสมบัติใดๆ ได้อีก แต่คุณยังไม่สามารถกำหนดค่าใหม่หรือลบคุณสมบัติที่มีอยู่ได้ (แม้ว่าคุณจะยังคงสามารถแก้ไขค่าของพวกมันได้)

Freeze

Object.freeze() สร้างออบเจกต์ที่ถูกตรึง ซึ่งหมายความว่ามันรับออบเจกต์ที่มีอยู่และโดยพื้นฐานแล้วเรียก Object.seal() บนมัน แต่ยังทำเครื่องหมายคุณสมบัติ "data accessor" ทั้งหมดเป็น writable:false เพื่อไม่ให้ค่าของพวกมันเปลี่ยนแปลงได้

แนวทางนี้เป็นระดับสูงสุดของ immutability ที่คุณสามารถทำได้สำหรับออบเจกต์เอง เนื่องจากมันป้องกันการเปลี่ยนแปลงใดๆ กับออบเจกต์หรือคุณสมบัติโดยตรงใดๆ ของมัน (แม้ว่า ดังที่กล่าวไว้ข้างต้น เนื้อหาของออบเจกต์อื่นที่อ้างอิงจะไม่ได้รับผลกระทบ)

var immutable = Object.freeze({});

การตรึงออบเจกต์ไม่อนุญาตให้เพิ่มคุณสมบัติใหม่ให้กับออบเจกต์ และป้องกันการลบหรือแก้ไขคุณสมบัติที่มีอยู่ Object.freeze() รักษาความสามารถในการระบุ, การกำหนดค่า, ความสามารถในการเขียน และ prototype ของออบเจกต์ มันคืนค่าออบเจกต์ที่ส่งผ่านและไม่สร้างสำเนาที่ถูกตรึง

ข้อดีและข้อเสียของ immutability มีอะไรบ้าง?

ข้อดี

  • ตรวจจับการเปลี่ยนแปลงได้ง่ายขึ้น - ความเท่าเทียมกันของออบเจกต์สามารถพิจารณาได้อย่างมีประสิทธิภาพและง่ายดายผ่านความเท่าเทียมกันของการอ้างอิง สิ่งนี้มีประโยชน์สำหรับการเปรียบเทียบความแตกต่างของออบเจกต์ใน React และ Redux
  • โปรแกรมที่มี immutable objects คิดง่ายกว่า เนื่องจากคุณไม่ต้องกังวลว่าออบเจกต์อาจมีการเปลี่ยนแปลงเมื่อเวลาผ่านไป
  • ไม่จำเป็นต้องมีการสำเนาป้องกันอีกต่อไปเมื่อ immutable objects ถูกส่งคืนจากหรือส่งผ่านไปยังฟังก์ชัน เนื่องจากไม่มีความเป็นไปได้ที่ immutable object จะถูกแก้ไขโดยมัน
  • การแชร์ผ่านการอ้างอิงทำได้ง่าย - การคัดลอกออบเจกต์หนึ่งก็ดีเท่ากับอีกชุดหนึ่ง ดังนั้นคุณสามารถแคชออบเจกต์หรือนำออบเจกต์เดียวกันกลับมาใช้ซ้ำได้หลายครั้ง
  • Thread-safe - Immutable objects สามารถใช้งานได้อย่างปลอดภัยระหว่างเธรดในสภาพแวดล้อมแบบ multi-threaded เนื่องจากไม่มีความเสี่ยงที่พวกมันจะถูกแก้ไขในเธรดที่รันพร้อมกันอื่นๆ
  • การใช้ไลบรารีเช่น ImmutableJS ออบเจกต์จะถูกแก้ไขโดยใช้ structural sharing และต้องการหน่วยความจำน้อยลงสำหรับการมีหลายออบเจกต์ที่มีโครงสร้างคล้ายกัน

ข้อเสีย

  • การใช้งานโครงสร้างข้อมูล immutable และการดำเนินการที่ขาดความรอบคอบอาจส่งผลให้ประสิทธิภาพต่ำมากเนื่องจากมีการสร้างออบเจกต์ใหม่ทุกครั้ง แนะนำให้ใช้ไลบรารีสำหรับโครงสร้างข้อมูล immutable ที่มีประสิทธิภาพและการดำเนินการที่ใช้ประโยชน์จากการแบ่งปันโครงสร้าง
  • การจัดสรร (และยกเลิกการจัดสรร) ออบเจกต์ขนาดเล็กจำนวนมาก แทนที่จะแก้ไขออบเจกต์ที่มีอยู่ อาจส่งผลกระทบต่อประสิทธิภาพ ความซับซ้อนของ allocator หรือ garbage collector โดยทั่วไปขึ้นอยู่กับจำนวนออบเจกต์ใน heap
  • โครงสร้างข้อมูลแบบวนซ้ำ เช่น กราฟ สร้างได้ยาก หากคุณมีออบเจกต์สองชิ้นที่ไม่สามารถแก้ไขได้หลังจากเริ่มต้นใช้งาน คุณจะทำให้พวกมันชี้ไปหากันได้อย่างไร?

คุณจะทำให้โค้ดของคุณเป็น immutable ได้อย่างไร?

อีกทางเลือกหนึ่งคือการใช้การประกาศ const ร่วมกับเทคนิคที่กล่าวมาข้างต้นสำหรับการสร้าง สำหรับออบเจกต์ที่ "mutating" ให้ใช้ spread operator, Object.assign, Array.concat() เป็นต้น เพื่อสร้างออบเจกต์ใหม่แทนที่จะเปลี่ยนแปลงออบเจกต์ต้นฉบับ

ตัวอย่าง:

// Array Example const arr = [1, 2, 3]; const newArr = [...arr, 4]; // [1, 2, 3, 4] // Object Example const human = Object.freeze({ race: 'human' }); const john = { ...human, name: 'John' }; // {race: "human", name: "John"} const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}

อธิบายความแตกต่างระหว่าง synchronous และ asynchronous functions

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

Asynchronous functions โดยทั่วไปจะรับ callback เป็นพารามิเตอร์ และการรันจะดำเนินต่อไปยังบรรทัดถัดไปทันทีหลังจากฟังก์ชัน asynchronous ถูกเรียกใช้ Callback จะถูกเรียกใช้เมื่อการดำเนินการ asynchronous เสร็จสิ้นและ call stack ว่างเปล่า การดำเนินการที่ใช้ทรัพยากรมาก เช่น การโหลดข้อมูลจากเว็บเซิร์ฟเวอร์ หรือการสืบค้นฐานข้อมูล ควรทำแบบ asynchronous เพื่อให้ main thread สามารถดำเนินการอื่นๆ ได้ต่อไป แทนที่จะบล็อกจนกว่าการดำเนินการที่ใช้เวลานานนั้นจะเสร็จสิ้น (ในกรณีของเบราว์เซอร์ UI จะหยุดทำงาน)

Event loop คืออะไร? ความแตกต่างระหว่าง call stack และ task queue คืออะไร?

Event loop คือลูปแบบ single-threaded ที่คอยตรวจสอบ call stack และตรวจสอบว่ามีงานใดๆ ที่ต้องทำใน task queue หรือไม่ หาก call stack ว่างเปล่าและมีฟังก์ชัน callback อยู่ใน task queue ฟังก์ชันจะถูกถอดออกจากคิวและถูกดันไปยัง call stack เพื่อดำเนินการ

หากคุณยังไม่ได้ดูการบรรยายของ Philip Robert เกี่ยวกับ Event Loop คุณควรดู มันเป็นหนึ่งในวิดีโอที่มีผู้เข้าชมมากที่สุดเกี่ยวกับ JavaScript

อธิบายความแตกต่างในการใช้งาน `foo` ระหว่าง `function foo() {}` และ `var foo = function() {}`

แบบแรกคือ function declaration ในขณะที่แบบหลังคือ function expression ความแตกต่างที่สำคัญคือ function declarations มี body ของมันถูก hoist แต่ body ของ function expressions จะไม่ถูก hoist (พวกมันมีพฤติกรรมการ hoisting เดียวกันกับตัวแปร) สำหรับคำอธิบายเพิ่มเติมเกี่ยวกับการ hoisting โปรดดูคำถามด้านบน เกี่ยวกับการ hoisting หากคุณพยายามเรียกใช้ function expression ก่อนที่จะถูกกำหนด คุณจะได้รับข้อผิดพลาด Uncaught TypeError: XXX is not a function

Function Declaration

foo(); // 'FOOOOO' function foo() { console.log('FOOOOO'); }

Function Expression

foo(); // Uncaught TypeError: foo is not a function var foo = function () { console.log('FOOOOO'); };

ความแตกต่างระหว่างตัวแปรที่สร้างโดยใช้ `let`, `var` หรือ `const` คืออะไร?

ตัวแปรที่ประกาศโดยใช้คีย์เวิร์ด var จะมีขอบเขตอยู่ที่ฟังก์ชันที่สร้างขึ้น หรือถ้าสร้างขึ้นภายนอกฟังก์ชันใดๆ จะมีขอบเขตอยู่ที่ global object let และ const เป็น block scoped ซึ่งหมายความว่าสามารถเข้าถึงได้เฉพาะภายในชุดวงเล็บปีกกาที่ใกล้ที่สุด (ฟังก์ชัน, บล็อก if-else หรือ for-loop)

function foo() { // All variables are accessible within functions. var bar = 'bar'; let baz = 'baz'; const qux = 'qux'; console.log(bar); // bar console.log(baz); // baz console.log(qux); // qux } console.log(bar); // ReferenceError: bar is not defined console.log(baz); // ReferenceError: baz is not defined console.log(qux); // ReferenceError: qux is not defined
if (true) { var bar = 'bar'; let baz = 'baz'; const qux = 'qux'; } // var declared variables are accessible anywhere in the function scope. console.log(bar); // bar // let and const defined variables are not accessible outside of the block they were defined in. console.log(baz); // ReferenceError: baz is not defined console.log(qux); // ReferenceError: qux is not defined

var อนุญาตให้ hoist ตัวแปรได้ ซึ่งหมายความว่าสามารถอ้างอิงถึงตัวแปรในโค้ดก่อนที่จะประกาศได้ let และ const จะไม่อนุญาตสิ่งนี้ แต่จะโยนข้อผิดพลาดแทน

console.log(foo); // undefined var foo = 'foo'; console.log(baz); // ReferenceError: can't access lexical declaration 'baz' before initialization let baz = 'baz'; console.log(bar); // ReferenceError: can't access lexical declaration 'bar' before initialization const bar = 'bar';

การประกาศตัวแปรซ้ำด้วย var จะไม่โยนข้อผิดพลาด แต่ let และ const จะโยนข้อผิดพลาด

var foo = 'foo'; var foo = 'bar'; console.log(foo); // "bar" let baz = 'baz'; let baz = 'qux'; // Uncaught SyntaxError: Identifier 'baz' has already been declared

let และ const แตกต่างกันตรงที่ let อนุญาตให้กำหนดค่าของตัวแปรใหม่ได้ ในขณะที่ const ไม่อนุญาต

// This is fine. let foo = 'foo'; foo = 'bar'; // This causes an exception. const baz = 'baz'; baz = 'qux';

ความแตกต่างระหว่าง ES6 class และ ES5 function constructors คืออะไร?

มาดูตัวอย่างของแต่ละอย่างกันก่อน:

// ES5 Function Constructor function Person(name) { this.name = name; } // ES6 Class class Person { constructor(name) { this.name = name; } }

สำหรับ constructors แบบง่ายๆ พวกมันดูคล้ายกันมาก

ความแตกต่างหลักใน constructor เกิดขึ้นเมื่อใช้การสืบทอด หากเราต้องการสร้างคลาส Student ที่สืบทอดมาจาก Person และเพิ่มฟิลด์ studentId นี่คือสิ่งที่เราต้องทำนอกเหนือจากข้างต้น

// ES5 Function Constructor function Student(name, studentId) { // Call constructor of superclass to initialize superclass-derived members. Person.call(this, name); // Initialize subclass's own members. this.studentId = studentId; } Student.prototype = Object.create(Person.prototype); Student.prototype.constructor = Student; // ES6 Class class Student extends Person { constructor(name, studentId) { super(name); this.studentId = studentId; } }

การใช้การสืบทอดใน ES5 นั้นซับซ้อนกว่ามาก และเวอร์ชัน ES6 ก็เข้าใจและจดจำได้ง่ายกว่า

คุณช่วยยกตัวอย่างการใช้งาน arrow => function syntax ใหม่นี้ได้หรือไม่? ไวยากรณ์ใหม่นี้แตกต่างจากฟังก์ชันอื่นอย่างไร?

ประโยชน์ที่ชัดเจนอย่างหนึ่งของ arrow functions คือการทำให้ไวยากรณ์ที่จำเป็นในการสร้างฟังก์ชันง่ายขึ้น โดยไม่จำเป็นต้องใช้คีย์เวิร์ด function this ภายใน arrow functions ยังผูกกับขอบเขตที่ห่อหุ้ม ซึ่งแตกต่างจากฟังก์ชันปกติที่ this ถูกกำหนดโดยออบเจกต์ที่เรียกใช้มัน this ที่มีขอบเขตแบบ lexical มีประโยชน์เมื่อเรียกใช้ callbacks โดยเฉพาะใน React components

ข้อดีของการใช้ arrow syntax สำหรับเมธอดใน constructor มีอะไรบ้าง?

ข้อได้เปรียบหลักของการใช้ arrow function เป็นเมธอดภายใน constructor คือค่าของ this จะถูกกำหนดในขณะที่สร้างฟังก์ชัน และไม่สามารถเปลี่ยนแปลงได้หลังจากนั้น ดังนั้น เมื่อ constructor ถูกใช้เพื่อสร้างออบเจกต์ใหม่ this จะอ้างถึงออบเจกต์นั้นเสมอ ตัวอย่างเช่น สมมติว่าเรามี constructor Person ที่รับชื่อจริงเป็นอาร์กิวเมนต์ และมีเมธอดสองตัวสำหรับ console.log ชื่อนั้น โดยตัวหนึ่งเป็นฟังก์ชันปกติและอีกตัวเป็น arrow function:

const Person = function (firstName) { this.firstName = firstName; this.sayName1 = function () { console.log(this.firstName); }; this.sayName2 = () => { console.log(this.firstName); }; }; const john = new Person('John'); const dave = new Person('Dave'); john.sayName1(); // John john.sayName2(); // John // The regular function can have its 'this' value changed, but the arrow function cannot john.sayName1.call(dave); // Dave (because "this" is now the dave object) john.sayName2.call(dave); // John john.sayName1.apply(dave); // Dave (because 'this' is now the dave object) john.sayName2.apply(dave); // John john.sayName1.bind(dave)(); // Dave (because 'this' is now the dave object) john.sayName2.bind(dave)(); // John var sayNameFromWindow1 = john.sayName1; sayNameFromWindow1(); // undefined (because 'this' is now the window object) var sayNameFromWindow2 = john.sayName2; sayNameFromWindow2(); // John

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

สิ่งนี้มีประโยชน์อย่างยิ่งใน React class components หากคุณกำหนดเมธอดของคลาสสำหรับบางอย่างเช่น click handler โดยใช้ฟังก์ชันปกติ และจากนั้นคุณส่ง click handler นั้นลงไปยัง child component ในฐานะ prop คุณจะต้อง bind this ใน constructor ของ parent component ด้วย หากคุณใช้ arrow function แทน ก็ไม่จำเป็นต้อง bind "this" ด้วย เนื่องจากเมธอดจะได้รับค่า "this" โดยอัตโนมัติจาก lexical context ที่ห่อหุ้ม

นิยามของ higher-order function คืออะไร?

Higher-order function คือฟังก์ชันใดๆ ที่รับฟังก์ชันหนึ่งตัวขึ้นไปเป็นอาร์กิวเมนต์ ซึ่งใช้ในการดำเนินการกับข้อมูลบางอย่าง และ/หรือคืนค่าฟังก์ชันเป็นผลลัพธ์ Higher-order functions มีวัตถุประสงค์เพื่อสรุปการดำเนินการบางอย่างที่ทำซ้ำๆ ตัวอย่างคลาสสิกของสิ่งนี้คือ map ซึ่งรับอาร์เรย์และฟังก์ชันเป็นอาร์กิวเมนต์ จากนั้น map จะใช้ฟังก์ชันนี้เพื่อแปลงแต่ละรายการในอาร์เรย์ โดยคืนค่าอาร์เรย์ใหม่พร้อมข้อมูลที่แปลงแล้ว ตัวอย่างยอดนิยมอื่นๆ ใน JavaScript ได้แก่ forEach, filter และ reduce Higher-order function ไม่จำเป็นต้องจัดการกับอาร์เรย์เท่านั้น เนื่องจากมีกรณีการใช้งานมากมายสำหรับการคืนค่าฟังก์ชันจากฟังก์ชันอื่น Function.prototype.bind เป็นหนึ่งในตัวอย่างใน JavaScript

Map

สมมติว่าเรามีอาร์เรย์ของชื่อที่เราต้องแปลงแต่ละสตริงให้เป็นตัวพิมพ์ใหญ่

const names = ['irish', 'daisy', 'anna'];

วิธีแบบ imperative จะเป็นดังนี้:

const transformNamesToUppercase = function (names) { const results = []; for (let i = 0; i < names.length; i++) { results.push(names[i].toUpperCase()); } return results; }; transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

การใช้ .map(transformerFn) ทำให้โค้ดสั้นลงและเป็น declarative มากขึ้น

const transformNamesToUppercase = function (names) { return names.map((name) => name.toUpperCase()); }; transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

คุณช่วยยกตัวอย่างการ destructuring ออบเจกต์หรืออาร์เรย์ได้หรือไม่?

Destructuring เป็น expression ที่มีอยู่ใน ES6 ซึ่งช่วยให้วิธีการที่กระชับและสะดวกในการดึงค่าของ Objects หรือ Arrays และวางลงในตัวแปรที่แตกต่างกัน

Array destructuring

// Variable assignment. const foo = ['one', 'two', 'three']; const [one, two, three] = foo; console.log(one); // "one" console.log(two); // "two" console.log(three); // "three"
// Swapping variables let a = 1; let b = 3; [a, b] = [b, a]; console.log(a); // 3 console.log(b); // 1

Object destructuring

// Variable assignment. const o = { p: 42, q: true }; const { p, q } = o; console.log(p); // 42 console.log(q); // true

ES6 Template Literals ให้ความยืดหยุ่นมากในการสร้างสตริง คุณช่วยยกตัวอย่างได้หรือไม่?

Template literals ช่วยให้การทำ string interpolation หรือการรวมตัวแปรในสตริงเป็นเรื่องง่าย ก่อน ES2015 เป็นเรื่องปกติที่จะทำอะไรแบบนี้:

var person = { name: 'Tyler', age: 28 }; console.log( 'Hi, my name is ' + person.name + ' and I am ' + person.age + ' years old!', ); // 'Hi, my name is Tyler and I am 28 years old!'

ด้วย template literals คุณสามารถสร้างผลลัพธ์เดียวกันได้ดังนี้แทน:

const person = { name: 'Tyler', age: 28 }; console.log(`Hi, my name is ${person.name} and I am ${person.age} years old!`); // 'Hi, my name is Tyler and I am 28 years old!'

โปรดทราบว่าคุณใช้ backticks ไม่ใช่ quotes เพื่อระบุว่าคุณกำลังใช้ template literal และคุณสามารถแทรก expressions ภายใน placeholders ${} ได้

กรณีการใช้งานที่เป็นประโยชน์อีกประการหนึ่งคือการสร้างสตริงแบบหลายบรรทัด ก่อน ES2015 คุณสามารถสร้างสตริงแบบหลายบรรทัดได้ดังนี้:

console.log('This is line one.\nThis is line two.'); // This is line one. // This is line two.

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

console.log('This is line one.\n' + 'This is line two.'); // This is line one. // This is line two.

อย่างไรก็ตาม template literals จะรักษาระยะห่างที่คุณเพิ่มเข้าไป ตัวอย่างเช่น การสร้างผลลัพธ์แบบหลายบรรทัดที่เราสร้างไว้ข้างต้น คุณสามารถทำได้ง่ายๆ ดังนี้:

console.log(`This is line one. This is line two.`); // This is line one. // This is line two.

อีกกรณีการใช้งานหนึ่งของ template literals คือการใช้เป็นสิ่งทดแทนไลบรารี templating สำหรับการแทรกตัวแปรแบบง่ายๆ:

const person = { name: 'Tyler', age: 28 }; document.body.innerHTML = ` <div> <p>Name: ${person.name}</p> <p>Age: ${person.age}</p> </div> `;

โปรดทราบว่าโค้ดของคุณอาจเสี่ยงต่อ XSS โดยการใช้ .innerHTML ทำความสะอาดข้อมูลของคุณก่อนแสดงผลหากมาจากผู้ใช้!

คุณช่วยยกตัวอย่าง curry function และทำไมไวยากรณ์นี้จึงมีข้อดีได้หรือไม่?

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

function curry(fn) { if (fn.length === 0) { return fn; } function _curried(depth, args) { return function (newArgument) { if (depth - 1 === 0) { return fn(...args, newArgument); } return _curried(depth - 1, [...args, newArgument]); }; } return _curried(fn.length, []); } function add(a, b) { return a + b; } var curriedAdd = curry(add); var addFive = curriedAdd(5); var result = [0, 1, 2, 3, 4, 5].map(addFive); // [5, 6, 7, 8, 9, 10]

ประโยชน์ของการใช้ spread syntax มีอะไรบ้าง และแตกต่างจาก rest syntax อย่างไร?

Spread syntax ของ ES6 มีประโยชน์อย่างมากเมื่อเขียนโค้ดในรูปแบบ functional เนื่องจากเราสามารถสร้างสำเนาของอาร์เรย์หรือออบเจกต์ได้อย่างง่ายดายโดยไม่ต้องพึ่งพา Object.create, slice หรือฟังก์ชันไลบรารี คุณลักษณะภาษานี้มักใช้ในโครงการ Redux และ RxJS

function putDookieInAnyArray(arr) { return [...arr, 'dookie']; } const result = putDookieInAnyArray(['I', 'really', "don't", 'like']); // ["I", "really", "don't", "like", "dookie"] const person = { name: 'Todd', age: 29, }; const copyOfTodd = { ...person };

Rest syntax ของ ES6 นำเสนอทางลัดสำหรับการรวมจำนวนอาร์กิวเมนต์โดยพลการที่จะส่งผ่านไปยังฟังก์ชัน มันเหมือนกับ invers of the spread syntax โดยรับข้อมูลและบรรจุลงในอาร์เรย์แทนที่จะแกะอาร์เรย์ของข้อมูล และมันใช้งานได้ในอาร์กิวเมนต์ฟังก์ชัน รวมถึงในการกำหนด destructuring ของอาร์เรย์และออบเจกต์

function addFiveToABunchOfNumbers(...numbers) { return numbers.map((x) => x + 5); } const result = addFiveToABunchOfNumbers(4, 5, 6, 7, 8, 9, 10); // [9, 10, 11, 12, 13, 14, 15] const [a, b, ...rest] = [1, 2, 3, 4]; // a: 1, b: 2, rest: [3, 4] const { e, f, ...others } = { e: 1, f: 2, g: 3, h: 4, }; // e: 1, f: 2, others: { g: 3, h: 4 }

คุณสามารถแชร์โค้ดระหว่างไฟล์ได้อย่างไร?

ขึ้นอยู่กับสภาพแวดล้อม JavaScript

บนฝั่งไคลเอ็นต์ (สภาพแวดล้อมเบราว์เซอร์) ตราบใดที่ตัวแปร/ฟังก์ชันถูกประกาศใน global scope (window) สคริปต์ทั้งหมดสามารถอ้างถึงพวกมันได้ อีกทางเลือกหนึ่งคือการนำ Asynchronous Module Definition (AMD) มาใช้ผ่าน RequireJS เพื่อแนวทางที่เป็นโมดูลมากขึ้น

บนฝั่งเซิร์ฟเวอร์ (Node.js) วิธีทั่วไปคือการใช้ CommonJS แต่ละไฟล์จะถูกถือว่าเป็นโมดูล และสามารถส่งออกตัวแปรและฟังก์ชันโดยแนบไปกับออบเจกต์ module.exports

ES2015 กำหนดไวยากรณ์โมดูลซึ่งมีเป้าหมายเพื่อแทนที่ทั้ง AMD และ CommonJS สิ่งนี้จะได้รับการสนับสนุนในทั้งสภาพแวดล้อมเบราว์เซอร์และ Node ในที่สุด

ทำไมคุณอาจต้องการสร้าง static class members?

Static class members (properties/methods) ไม่ได้ผูกติดกับ instance เฉพาะของคลาส และมีค่าเดียวกันโดยไม่คำนึงถึงว่า instance ใดกำลังอ้างถึงมัน Static properties โดยทั่วไปเป็นตัวแปรการกำหนดค่า และ static methods มักจะเป็น pure utility functions ที่ไม่ขึ้นอยู่กับสถานะของ instance