ホームページ  >  記事  >  ウェブフロントエンド  >  「これまでどこにいたの?」と言わせる JavaScript ハック

「これまでどこにいたの?」と言わせる JavaScript ハック

王林
王林オリジナル
2024-08-11 06:02:05596ブラウズ

JavaScript Hacks That Will Make You Say “Where Have You Been All My Life?

正直に言います: 開発者として、私たちはワークフローを合理化し、コーディング時間を貴重な数分で短縮する方法を常に模索しています。

洗練された効率的なソリューションがすぐそこにあるのに、不格好なコードと格闘して何時間も費やしたい人がいるでしょうか?

今日は、JavaScript のトリックなしでどうやって生きてきたか不思議に思うような 10 の JavaScript トリック (組み込みのものもあれば、カスタムもある) を紹介します。

素晴らしいですね! 最初のセクションに進みましょう。シンプルですが非常に便利な JavaScript 機能から始めます。


1. オプションのチェーン: TypeErrors に別れを告げましょう!

問題: オブジェクトの奥深くにあるプロパティにアクセスしようとしていますが、チェーン内のすべてのプロパティが存在するかどうかわかりません。これにより、恐ろしい「未定義のプロパティを読み取れません」エラーが発生する可能性があります。

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 からデータを取得していると想像してください。応答構造は異なる可能性があります。複数のネストされたチェックを記述する代わりに、オプションのチェーンを使用すると、存在するかどうかわからないデータにアクセスするためのクリーンで簡潔な方法が提供されます。


2. Nullish Coalescing Operator: デフォルト値がさらに賢くなった

問題: 変数が null または未定義の場合、変数にデフォルト値を代入したいが、コード内で有効な可能性がある偽の値 (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.

新しいモダンなソリューション: ヌル合体演算子 (??) が窮地を救います。左側のオペランドが厳密に 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 の本当の意味を尊重して、この落とし穴を回避します。


3. Object.freeze(): 不変にしましょう!

問題: オブジェクトがあります。オブジェクトの作成後にそのプロパティが誤って変更されないようにする必要があります。これは、一定に保つ必要がある構成オブジェクトまたはデータの場合に特に重要です。

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() を使用すると、これらの設定がアプリケーション全体で一定に保たれ、予期しない動作につながる可能性のある誤った変更が防止されます。


4. 配列の分割: 解凍が簡単に

問題: 配列から特定の値を抽出し、それらを個々の変数に割り当てる必要があります。インデックスを使用した従来の配列アクセスは、特に長い配列の場合、少し扱いに​​くいと感じることがあります。

古くて痛ましい解決策: 最終的にはインデックスによって要素にアクセスすることになり、特に配列が大きくなると、読みにくくなり、エラーが発生しやすくなります。

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.


5. Default Parameters: No More Undefined Headaches

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.


6. Tagged Template Literals: Supercharge Your Strings

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.


7. Proxy Objects: Intercept and Control

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.


8. The Power of reduce(): Beyond Simple Array Summation

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:

  • Data Grouping: Transform an array of objects into a grouped object based on a specific property.
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: [{...}] }
  • Flattening Arrays: Merge nested arrays into a single flat array.
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]
  • Creating Unique Lists: Extract unique values from an array.
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.


9. Spread Syntax for Easy Array and Object Manipulation

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:

  • Modifying State in React: Spread syntax is widely used in React and other UI libraries to create updated copies of state objects without mutating the original state:
// 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.


10. Arrow Functions: A Concise Syntax for Functions

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:

  • Event Handlers: Arrow functions are very common when attaching event listeners:
const button = document.getElementById("myButton");

button.addEventListener("click", () => {
    console.log("Button clicked!"); 
});

Ready for More? ?

  • 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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。