현실을 직시하자면: 개발자로서 우리는 항상 작업 흐름을 간소화하고 귀중한 코딩 시간을 단축할 수 있는 방법을 찾고 있습니다.
멋지고 효율적인 솔루션이 코앞에 있는데 누가 투박한 코드와 씨름하며 몇 시간을 보내고 싶겠습니까?
오늘 저는 여러분이 이 기술 없이 어떻게 살았는지 궁금하게 만들 10가지 JavaScript 비법(기본 제공, 맞춤 기능 포함)을 공유하겠습니다.
좋아요! 첫 번째 섹션으로 넘어가겠습니다. 간단하면서도 매우 유용한 JavaScript 기능부터 시작하겠습니다.
문제: 객체 내부의 속성에 액세스하려고 하지만 체인의 모든 속성이 존재하는지 확신할 수 없습니다. 이로 인해 "정의되지 않은 속성을 읽을 수 없습니다" 오류가 발생할 수 있습니다.
const user = { // address: { street: "123 Main St" } }; let street = user.address.street; console.log(street); // Uncaught TypeError: Cannot read properties of undefined (reading 'street')
오래되고 고통스러운 해결책: 액세스를 시도하기 전에 각 속성이 존재하는지 확인하기 위해 여러 개의 중첩된 if 문을 작성해야 합니다.
const user = { // address: { street: "123 Main St" } }; // The old, painful way: let street = user && user.address && user.address.street; console.log(street); // undefined
새로운 최신 솔루션: 구조를 위한 선택적 체인! ?.를 사용하면 속성이 누락된 경우 표현식이 정의되지 않은 상태로 단락되어 오류를 방지합니다.
const user = { // address: { street: "123 Main St" } }; // The elegant, modern way: let street = user?.address?.street; console.log(street); // undefined:
선택적 체이닝(?.)을 사용하면 체인의 속성이 null이거나 정의되지 않은 경우 표현식이 단락되어 무서운 TypeError를 발생시키는 대신 단순히 정의되지 않은 값을 반환합니다. 더 이상 투박한 if 문으로 인해 코드가 복잡해지지 않습니다!
실제 사례:
API에서 데이터를 가져오고 응답 구조가 다를 수 있다고 상상해 보세요. 여러 개의 중첩된 검사를 작성하는 대신 선택적 체인은 존재하거나 존재하지 않을 수 있는 데이터에 액세스하는 깔끔하고 간결한 방법을 제공합니다.
문제: 변수가 null이거나 정의되지 않은 경우 변수에 기본값을 할당하고 싶지만 코드에서 유효할 수 있는 0 또는 0과 같은 잘못된 값을 실수로 재정의하고 싶지는 않습니다. 빈 문자열.
오래된 골치 아픈 해결책: 논리 OR 연산자(||)를 사용하여 기본값을 설정하면 의도하지 않은 결과가 발생할 수 있습니다.
const user = { name: 0 }; // The old way (potentially problematic): let postCount = user.name || "No posts yet!"; console.log(postCount); // Outputs "No posts yet!", even though 0 might be a valid post count.
새로운 최신 솔루션: nullish 병합 연산자(??)를 사용하면 문제가 해결됩니다! 왼쪽 피연산자가 null이거나 정의되지 않은 경우에만 기본값을 제공합니다.
const user = { name: 0 }; // The new, improved way: let postCount = user.name ?? "No posts yet!"; console.log(postCount); // Outputs 0, respecting the actual value of user.name
우리 믿음직한?? 왼쪽 피연산자가 null이거나 정의되지 않은 경우에만 단계를 수행하여 의도한 경우에만 기본값이 사용되도록 합니다.
실제 사례:
'게시물 수'에 대한 유효한 입력이 0인 사용자 프로필을 상상해 보세요. ||를 사용하여 기본값을 설정하면 0이 기본값으로 잘못 대체됩니다. ?? 연산자는 이 맥락에서 0의 진정한 의미를 존중하여 이러한 함정을 피합니다.
문제: 개체가 있고 개체가 생성된 후 해당 개체의 속성이 실수로 변경되지 않도록 하려고 합니다. 이는 일정하게 유지되어야 하는 구성 개체나 데이터에 특히 중요합니다.
const colors = { primary: "blue", secondary: "green" }; colors.primary = "red"; // Accidental modification is too easy! console.log(colors.primary); // Outputs "red" - the object was modified
해결책: Object.freeze()는 개체를 견고하게 만듭니다! 이는 해당 속성이 더 이상 수정되는 것을 방지합니다.
const colors = { primary: "blue", secondary: "green" }; Object.freeze(colors); colors.primary = "red"; // This will silently fail console.log(colors.primary); // Still outputs "blue"
Object.freeze()는 객체를 가져와서 불변으로 만듭니다. 해당 속성을 변경하려는 시도는 자동으로 무시됩니다. 마치 물건을 진열장에 넣는 것과 같습니다. 볼 수는 있지만 만질 수는 없습니다!
실제 사례:
객체에 구성 설정이 저장되어 있다고 상상해 보세요. Object.freeze()를 사용하면 이러한 설정이 애플리케이션 전체에서 일정하게 유지되어 예상치 못한 동작으로 이어질 수 있는 우발적인 수정을 방지할 수 있습니다.
문제: 배열에서 특정 값을 추출하여 개별 변수에 할당해야 합니다. 인덱스를 사용하는 기존 배열 액세스는 특히 긴 배열의 경우 다소 투박하게 느껴질 수 있습니다.
기존의 고통스러운 해결책: 결국 인덱스로 요소에 액세스하게 되므로 읽기가 어렵고 오류가 발생하기 쉬울 수 있으며, 특히 배열이 커질수록 더욱 그렇습니다.
const rgb = [255, 128, 0]; const red = rgb[0]; const green = rgb[1]; const blue = rgb[2]; console.log(red, green, blue); // 255 128 0
새로운 최신 솔루션: 배열 구조 분해는 배열 요소를 개별 변수로 "압축해제"하는 우아하고 읽기 쉬운 방법을 제공합니다.
const rgb = [255, 128, 0]; const [red, green, blue] = rgb; console.log(red, green, blue); // 255 128 0
과제 왼쪽에 대괄호 []를 사용하여 배열의 구조를 반영하는 패턴을 만듭니다. 그런 다음 JavaScript는 배열의 해당 값을 변수에 깔끔하게 할당합니다.
실제 사례:
Imagine you have an array representing a user’s information: [name, age, city]. With destructuring, you can easily extract these values into separate variables for more readable and maintainable code.
Problem: You’re writing a function, and you want to provide default values for parameters in case the caller doesn’t supply them.
Old Painful Solution: You’d have to check if the arguments were undefined within the function body and assign default values manually.
function greet(name, message) { const userName = name || "Stranger"; const greeting = message || "Hello there!"; console.log(`${greeting}, ${userName}!`); } greet(); // Hello there!, Stranger! greet("Alice"); // Hello there!, Alice! greet("Bob", "Good morning"); // Good morning, Bob!
New Modern Solution: Default parameters let you specify default values for function parameters directly within the function definition.
By assigning values to parameters in the function signature (name = "Stranger"), we tell JavaScript to use those values if the corresponding arguments are not provided when the function is called.
Real-World Example:
Consider a function that calculates the area of a rectangle. You could set default values for width and height to 1, so if the function is called without arguments, it returns the area of a unit square.
Problem: You want to create more powerful and flexible string formatting capabilities beyond what’s offered by basic template literals. You might need custom parsing, escaping, or data transformations within your string construction.
Old Painful Solution: You’d rely on a combination of string concatenation, helper functions, and potentially complex logic to achieve the desired results.
function highlight(text, name) { // Find the index of the placeholder within the text const placeholderIndex = text.indexOf("%name%"); if (placeholderIndex !== -1) { // Replace the placeholder with the actual name return text.substring(0, placeholderIndex) + name + text.substring(placeholderIndex + 6); } else { return text; } } const name = "Alice"; const message = highlight("Welcome, %name%!", name); console.log(message); // "Welcome, Alice!"
New Modern Solution: Tagged template literals allow you to define custom functions (called “tag functions”) that can process template literal strings before they’re interpolated.
function highlight(strings, ...values) { let result = ''; for (let i = 0; i < strings.length; i++) { result += strings[I]; if (values[i]) { result += `<span class="highlight">${values[i]}</span>`; } } return result; } const name = "Alice"; const message = highlight`Welcome, ${name}!`; console.log(message); // "Welcome, <span class="highlight">Alice</span>!"
Old Solution: We relied on a separate function (highlight) that took the text and the value to be inserted as separate arguments. We manually searched for a placeholder (%name%) and replaced it. This approach is less flexible, more error-prone (what if the placeholder is wrong?), and doesn't scale well for more complex formatting.
New Solution: With tagged template literals, the highlight function receives the string parts and the interpolated values as separate arguments. This allows for much cleaner manipulation and transformation of the string based on its structure and the provided values.
Real-World Example:
Creating Domain-Specific Languages (DSLs): Build custom templating engines, query builders, or even mini-languages within your JavaScript code.
Internationalization (i18n): Handle translations and localized string formatting based on user preferences.
Security: Implement robust sanitization and escaping mechanisms for user-generated content within strings.
Problem: You need fine-grained control over object operations, such as property access, assignment, function calls, or even object construction. You might want to implement custom validation, logging, or even modify the behavior of existing objects without directly changing their code.
Old Painful Solution: You’d often resort to:
Wrapper Functions: Creating functions that encapsulate object interactions, adding overhead and potentially obscuring the underlying object’s interface.
Overriding Methods: Modifying object prototypes, which can lead to unexpected side effects and conflicts, especially in larger codebases.
const user = { name: "Alice", age: 30, }; function validateAge(age) { if (age < 0 || age > 120) { throw new Error("Invalid age value!"); } return age; } // Using a wrapper function to enforce validation function setUserAge(user, newAge) { user.age = validateAge(newAge); } setUserAge(user, 35); // Works setUserAge(user, -5); // Throws an error
New Modern Solution: Proxy objects act as intermediaries, intercepting fundamental operations on an object and giving you the power to customize how those operations are handled.
const user = { name: "Alice", age: 30, }; const userProxy = new Proxy(user, { set: function (target, property, value) { if (property === "age") { if (value < 0 || value > 120) { throw new Error("Invalid age value!"); } } // Update the original object's property target[property] = value; return true; // Indicate success }, }); userProxy.age = 35; // Works userProxy.age = -5; // Throws an error
We create a Proxy object, passing in the target object (user) and a handler object.
The handler object defines “traps” for various operations. In this case, we use the set trap to intercept property assignments.
Inside the set trap, we perform custom validation for the age property.
If the validation passes, we update the original object’s property using target[property] = value.
Real-World Example:
Data Validation and Sanitization: Enforce data integrity rules before saving objects to a database or sending them over a network.
Change Tracking: Log or react to changes made to an object’s properties.
Lazy Loading: Defer loading expensive object properties until they are actually accessed.
Problem: You need to perform sophisticated transformations or calculations on arrays, going beyond simple aggregation like finding the sum or maximum value.
Old Painful Solution: You might resort to:
Imperative Loops: Writing verbose for or while loops, often with nested logic and temporary variables, making the code harder to read and maintain.
Specialized Functions: Creating separate functions for each specific array transformation, leading to code duplication.
const orders = [ { product: "Shirt", quantity: 2, price: 15 }, { product: "Shoes", quantity: 1, price: 50 }, { product: "Hat", quantity: 3, price: 10 }, ]; // Calculate the total value of all orders (imperative approach) let totalValue = 0; for (let i = 0; i < orders.length; i++) { totalValue += orders[i].quantity * orders[i].price; } console.log(totalValue); // Output: 110
New Modern Solution: The reduce() method provides a versatile way to iterate over an array and "reduce" it to a single value, applying a callback function to each element and accumulating a result.
const orders = [ { product: "Shirt", quantity: 2, price: 15 }, { product: "Shoes", quantity: 1, price: 50 }, { product: "Hat", quantity: 3, price: 10 }, ]; // Calculate the total value of all orders using reduce const totalValue = orders.reduce((accumulator, order) => { return accumulator + order.quantity * order.price; }, 0); // Initial value of the accumulator console.log(totalValue); // Output: 110
reduce() takes two arguments: a callback function and an optional initial value for the accumulator.
The callback function receives the accumulator (which starts with the initial value or the first element) and the current element.
In each iteration, the callback returns the updated accumulator, which is then passed to the next iteration.
The final value returned by reduce() is the accumulated result.
Real-World Example:
const products = [ { name: "Apple", category: "Fruit" }, { name: "Banana", category: "Fruit" }, { name: "Carrot", category: "Vegetable" }, ]; const groupedProducts = products.reduce((groups, product) => { const category = product.category; if (!groups[category]) { groups[category] = []; } groups[category].push(product); return groups; }, {}); console.log(groupedProducts); // Output: { Fruit: [{...}, {...}], Vegetable: [{...}] }
const nestedArray = [1, [2, 3], [4, [5, 6]]]; const flatArray = nestedArray.reduce( (acc, current) => acc.concat(Array.isArray(current) ? current.flat() : current),[]); console.log(flatArray); // Output: [1, 2, 3, 4, 5, 6]
const numbers = [1, 2, 2, 3, 4, 4, 5]; const uniqueNumbers = numbers.reduce((unique, number) => { return unique.includes(number) ? unique : [...unique, number]; }, []); console.log(uniqueNumbers); // Output: [1, 2, 3, 4, 5]
Mastering reduce() unlocks a higher level of array manipulation, allowing you to express complex transformations concisely and elegantly.
Problem: You need to copy arrays, combine them, or insert elements at specific positions. Similarly, you might want to create copies of objects with modified properties. Doing this manually can be tedious and involve loops or multiple lines of code.
Old Painful Solution: You’d use combinations of slice(), concat(), or Object.assign() for these tasks:
Arrays:
const numbers1 = [1, 2, 3]; const numbers2 = [4, 5, 6]; // Concatenating arrays const combinedArray = numbers1.concat(numbers2); // Inserting the number 0 at index 2 (the old way) const newArray = numbers1.slice(0, 2).concat([0], numbers1.slice(2));
Objects:
const product = { name: "Phone", price: 499, }; // Creating a modified copy const updatedProduct = Object.assign({}, product, { price: 599 });
New Modern Solution: The spread syntax (...) provides a more concise and flexible way to work with arrays and objects:
Arrays:
const numbers1 = [1, 2, 3]; const numbers2 = [4, 5, 6]; // Concatenating arrays const combinedArray = [...numbers1, ...numbers2]; // Inserting an element const newArray = [...numbers1.slice(0, 2), 0, ...numbers1.slice(2)];
Objects:
const product = { name: "Phone", price: 499, }; // Creating a modified copy const updatedProduct = { ...product, price: 599 };
Spread Syntax with Arrays: When used with arrays, ... expands the elements of an array in place.
Spread Syntax with Objects: When used with objects, ... expands the key-value pairs of an object.
Why It’s Easier:
Conciseness: Spread syntax significantly reduces the code required for common array and object operations.
Readability: The code becomes more declarative and easier to understand.
Real-World Example:
// Example in a React component this.setState(prevState => ({ ...prevState, cartItems: [...prevState.cartItems, newItem], }));
Spread syntax is a versatile tool that simplifies array and object manipulation, making your code more concise, readable, and maintainable.
Problem: You often need to write short, anonymous functions for event handlers, callbacks, or array methods, but the traditional function syntax can feel a bit verbose in these cases.
Old Painful Solution: You’d use the function keyword to define anonymous functions:
// Example with an array method const numbers = [1, 2, 3, 4, 5]; const doubledNumbers = numbers.map(function(number) { return number * 2; }); console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]
New Modern Solution: Arrow functions (=>) provide a more compact syntax for writing functions, especially for short function bodies:
const numbers = [1, 2, 3, 4, 5]; const doubledNumbers = numbers.map((number) => number * 2); console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]
Syntax: An arrow function is defined with parentheses for parameters (or a single parameter without parentheses), followed by the arrow (=>), and then the function body.
Implicit Return: If the function body contains a single expression, the result of that expression is implicitly returned without needing the return keyword.
Lexical this Binding: Arrow functions don't have their own this binding. They inherit this from the surrounding scope, which can be very useful in certain situations (we'll explore this in a later example).
Why It’s Easier:
Shorter Syntax: Arrow functions significantly reduce the code required to define simple functions.
Improved Readability: The code becomes more concise and easier to follow, especially when used with array methods.
Real-World Example:
const button = document.getElementById("myButton"); button.addEventListener("click", () => { console.log("Button clicked!"); });
This is just the beginning! The world of JavaScript is vast. ?
Keep experimenting, keep learning, and never be afraid to break things (in a safe coding environment, of course! ?).
Want to stay connected? Follow me on Instagram @codingwithjd for more coding tips, tricks, and even some bad programming jokes. ?
위 내용은 '당신은 평생 어디에 있었나요?'라고 말하게 만드는 JavaScript 해킹의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!