首页 >web前端 >js教程 >JavaScript 黑客会让你说'我这辈子你去哪儿了?

JavaScript 黑客会让你说'我这辈子你去哪儿了?

王林
王林原创
2024-08-11 06:02:05720浏览

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

让我们面对现实:作为开发人员,我们一直在寻找简化工作流程并节省宝贵的编码时间的方法。

当一个时尚而高效的解决方案即将出现时,谁愿意花几个小时来处理笨重的代码?

今天,我将分享 10 个 JavaScript 技巧——一些是内置的,一些是自定义的——这会让你想知道如果没有它们你会如何生活。

太棒了!让我们继续第一部分。我们将从一个简单但非常有用的 JavaScript 功能开始。


1. 可选链接:告别那些类型错误!

问题:您正在尝试访问对象深处的属性,但不确定链中的所有属性是否都存在。这可能会导致那些可怕的“无法读取未定义的属性”错误。

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 或未定义,则表达式会短路并简单地返回 undefined,而不是抛出可怕的 TypeError。不再有笨重的 if 语句让您的代码变得混乱!

现实示例:

假设您正在从 API 获取数据,并且响应结构可能会有所不同。可选链不是编写多个嵌套检查,而是提供了一种干净简洁的方式来访问可能存在或不存在的数据。


2. 空合并运算符:默认值变得更加智能

问题: 如果变量为 null 或未定义,您希望为其分配默认值,但您不想意外覆盖代码中可能有效的虚假值,例如 0 或空字符串。

旧的痛苦解决方案:使用逻辑或运算符 (||) 设置默认值可能会导致这些意想不到的后果。

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中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn