45 个每个开发人员都应该知道的 JavaScript 超级技巧 - 抄录

本文抄录与:《45 个每个开发人员都应该知道的 JavaScript 超级技巧 》

JavaScript 是一种应用场景多且功能强大的语言,对于现代 Web 开发至关重要。以下是一些超级技巧,它们将帮助你成为更高效、更有效的 JavaScript 开发人员,每个技巧都有详细的解释和示例。

1. 使用 `let` 和 `const` 代替 `var`

问题:`var` 具有函数作用域,这可能导致错误和不可预测的行为。

解决方案:使用具有块作用域的 `let` 和 `const`。

let count = 0;const PI = 3.14;

使用 `let` 和 `const` 有助于防止与作用域相关的错误,确保变量只能在定义的块内访问。

2. 默认参数

问题:如果没有提供参数,函数可能会失败。

解决方案:使用默认参数设置后备值。

function greet(name = 'Guest') {
    return `Hello, ${name}!`;
}
console.log(greet()); // "Hello, Guest!"

默认参数确保函数具有合理的默认值,从而防止错误并使代码更加健壮。

3. 模板文字

问题:字符串连接可能很麻烦且容易出错。

解决方案:使用模板文字进行更清晰、更易读的字符串插值。

const name = 'John'
const greeting = `Hello, ${name}!`
console.log(greeting) // "Hello, John!"

模板文字使创建带有嵌入表达式和多行字符串的字符串变得更加容易。

4. 解构赋值

问题:从对象和数组中提取值可能非常冗长。

解决方案:使用解构赋值更简洁地提取值。

const user = { name: 'Jane', age: 25 }
const { name, age } = user
console.log(name, age) // "Jane" 25

解构赋值允许您轻松地将对象中的属性和数组中的元素提取到不同的变量中。

5. 箭头函数

问题:传统函数表达式可能很冗长,并且不会在词汇上绑定“this”。

解决方案:使用箭头函数来实现更短的语法和词汇“this”。

const add = (a, b) => a + b
console.log(add(2, 3)) // 5

箭头函数为函数表达式提供了简洁的语法,并确保 `this` 在词汇上是绑定的。

6. 扩展运算符

问题:组合数组或对象可能很麻烦。

解决方案:使用扩展运算符可以轻松组合数组和对象。

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = […arr1, …arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

扩展运算符允许您将一个数组或对象的元素扩展到另一个数组或对象中。

7. 剩余参数

问题:处理可变数量的函数参数可能很棘手。

解决方案:使用剩余参数捕获数组中的所有参数。

function sum(…args) {
    return args.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

剩余参数允许您将无限数量的参数作为数组处理,从而使您的函数更加灵活。

8. 短路求值

问题:编写条件语句可能很冗长。

解决方案:使用短路求值编写简洁的条件。

const isLoggedIn = true
const user = isLoggedIn && { name: 'Jane', age: 25 }
console.log(user) // { name: 'Jane', age: 25 }

短路求值使用逻辑运算符 `&&` 和 `||` 来简化条件表达式。

9. 可选链 (+)

问题:如果链中的任何部分为 `null` 或 `undefined`,则访问深层嵌套的属性可能会导致错误。

解决方案:使用可选链安全地访问嵌套属性。

const user = { profile: { name: 'Jane' } }
const userName = user?.profile?.name
console.log(userName) // "Jane"

可选链式连接允许您安全地访问嵌套属性,而无需明确检查链式连接的每一级是否为 `null` 或 `undefined`。

10. 空值合并 (+)

问题:如果值为 `0` 或 `””`,则使用 `||` 提供默认值可能会产生意外结果。

解决方案:仅在 `null` 或 `undefined` 时使用空值合并 (`??`) 提供默认值。

const user = { name: '', age: 0 }
const userName = user.name ?? 'Anonymous'
const userAge = user.age ?? 18
console.log(userName) // ""console.log(userAge); // 0

空值合并仅允许在左侧为“null”或“undefined”时提供默认值。

11. 对象属性简写

问题:将变量分配给对象属性可能会重复。

解决方案:使用属性简写来简化对象创建。

const name = 'Jane'
const age = 25
const user = { name, age }
console.log(user) // { name: 'Jane', age: 25 }

属性简写允许您在属性名称与变量名称匹配时省略属性名称,从而使代码更简洁。

12. 动态属性名称

问题:使用动态属性名称创建对象可能很冗长。

解决方案:使用计算属性名称动态创建对象属性。

const propName = 'age'
const user = { name: 'Jane', [propName]: 25 }
console.log(user) // { name: 'Jane', age: 25 }

计算属性名称允许您动态创建对象属性,使用表达式的值作为属性名称。

13. 数组 `map()`、`filter()` 和 `reduce()`

问题:迭代数组以转换、过滤或累积值可能会重复。

解决方案:使用 `map()`、`filter()` 和 `reduce()` 进行常见的数组操作。

const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map((num) => num * 2)
console.log(doubled) // [2, 4, 6, 8, 10]
const evens = numbers.filter((num) => num % 2 === 0)
console.log(evens) // [2, 4]
const sum = numbers.reduce((total, num) => total + num, 0)
console.log(sum) // 15

这些数组方法提供了一种转换、过滤和减少数组的函数式方法,使您的代码更具表现力和简洁性。

14. 字符串 `includes()`、`startsWith()` 和 `endsWith()`

问题:检查字符串是否包含、以子字符串开头或以子字符串结尾可能很冗长。

解决方案:使用 `includes()`、`startsWith()` 和 `endsWith()` 进行更简单的字符串检查。

const str = 'Hello, world!'
console.log(str.includes('world')) // true
console.log(str.startsWith('Hello')) // true
console.log(str.endsWith('!')) // true

这些字符串方法提供了一种简单易读的方法来检查子字符串的存在、开始或结束。

15. 函数参数中的数组和对象解构

问题:从作为函数参数传递的数组或对象中提取值可能很冗长。

解决方案:在函数参数中使用解构来直接提取值。

const user = { name: 'Jane', age: 25 }
function greet({ name, age }) {
  return `Hello, ${name}! You are ${age} years old.`
}
console.log(greet(user)) // "Hello, Jane! You are 25 years old."

函数参数中的解构允许您直接从传递给函数的对象或数组中提取值,从而使代码更简洁、更易读。

16. 解构中的默认值

问题:解构对象时处理缺失的属性可能很麻烦。

解决方案:在解构中使用默认值来提供后备值。

const user = { name: 'Jane' }
const { name, age = 18 } = user
console.log(name) // "Jane"
console.log(age); // 18

解构中的默认值允许您为可能缺失的属性提供后备值,从而使您的代码更加健壮。

17. 对象 `assign()`

问题:克隆或合并对象可能很冗长且容易出错。

解决方案:使用 `Object.assign()` 克隆或合并对象。

const target = { a: 1 }
const source = { b: 2 }
const merged = Object.assign(target, source)
console.log(merged) // { a: 1, b: 2 }

`Object.assign()` 允许您高效地克隆或合并对象,从而减少手动复制的需要。

18. 数组 `find()` 和 `findIndex()`

问题:使用循环在数组中查找元素或其索引可能很麻烦。

解决方案:使用 `find()` 和 `findIndex()` 使代码更易读。

const users = [
  { id: 1, name: 'Jane' },
  { id: 2, name: 'John' }
]
const user = users.find((u) => u.id === 1)
console.log(user) // { id: 1, name: 'Jane' }
const index = users.findIndex((u) => u.id === 1)
console.log(index) // 0

这些数组方法提供了一种根据条件查找元素或其索引的简单方法,从而提高了代码的可读性。

19. 数组 `some()` 和 `every()`

问题:检查数组中的部分或全部元素是否满足条件可能会很冗长。

解决方案:使用 `some()` 和 `every()` 来获得更简洁的代码。

const numbers = [1, 2, 3, 4, 5]
const hasEven = numbers.some((num) => num % 2 === 0)
console.log(hasEven) // true
const allEven = numbers.every((num) => num % 2 === 0)
console.log(allEven) // false

这些数组方法允许您以简洁的方式检查数组中的部分或全部元素是否满足条件。

20. 数组 `flat()` 和 `flatMap()`  (++)

问题:展平嵌套数组或映射和展平数组可能很麻烦。

解决方案:使用 `flat()` 和 `flatMap()` 使代码更易读。

const nested = [1, [2, [3, [4]]]]
const flat = nested.flat(2)
console.log(flat) // [1, 2, 3, [4]]
const mapped = [1, 2, 3].flatMap((x) => [x, x * 2])
console.log(mapped) // [1, 2, 2, 4, 3, 6]

这些数组方法提供了一种简单的方法来展平嵌套数组,并在一个步骤中映射和展平。

21. 数组 `from()` 和 `of()`

问题:从可迭代对象或参数创建数组可能很冗长。

解决方案:使用 `Array.from()` 和 `Array.of()` 获得更简洁的代码。

const set = new Set([1, 2, 3])
const arrFromSet = Array.from(set)
console.log(arrFromSet) // [1, 2, 3]
const arrOfNumbers = Array.of(1, 2, 3)
console.log(arrOfNumbers) // [1, 2, 3]

`Array.from()` 允许您从可迭代对象创建数组,而 `Array.of()` 允许您从参数列表创建数组。

22. 回调中的参数解构

问题:访问传递给回调的对象的属性可能很冗长。

解决方案:在回调参数中使用解构以获得更简洁的代码。

const users = [
  { id: 1, name: 'Jane' },
  { id: 2, name: 'John' }
]
users.forEach(({ id, name }) => {
  console.log(`User ID: ${id}, User Name: ${name}`)
})

回调参数中的解构允许您直接访问传递给回调的对象的属性,从而使代码更简洁。

23. 可选回调函数

问题:处理可选回调函数可能很麻烦。

解决方案:使用短路求值来调用可选回调。

function fetchData(url, callback) {
  fetch(url)
    .then((response) => response.json())
    .then((data) => {
      callback && callback(data)
    })
}

短路求值允许您仅在提供可选回调函数时才调用该函数,从而使代码更加健壮。

24. Promisify 回调

问题:将基于回调的函数转换为承诺可能很麻烦。

解决方案:使用实用函数来 promisify 回调。

function promisify(fn) {
  return function (…args) {
    return new Promise((resolve, reject) => {
      fn(…args, (err, result) => {
        if (err) reject(err);
        else resolve(result);
      });
    });
  };
}
const readFile = promisify(require('fs').readFile);
readFile('path/to/file.txt', 'utf8')
.then(data => console.log(data))
.catch(err => console.error(err));

Promisifying 允许您将基于回调的函数转换为承诺,从而更轻松地使用 async/await 语法。

25. 用于类似同步代码的 Async/Await

问题:使用承诺编写异步代码可能冗长且难以阅读。

解决方案:使用 async/await 以同步风格编写异步代码。

async function fetchData(url) {
  try {
    const response = await fetch(url)
    const data = await response.json()
    console.log(data)
  } catch (error) {
    console.error('Error fetching data:', error)
  }
}
fetchData('https://api.example.com/data')

Async/await 提供了一种编写外观和行为都像同步代码的异步代码的方法,从而提高了可读性和可维护性。

26. 链接承诺 (+)

问题:按顺序处理多个异步操作可能很麻烦。

解决方案:链式承诺处理多个异步操作。

fetch('https://api.example.com/data')
  .then((response) => response.json())
  .then((data) => {
    console.log('Data:', data)
    return fetch('https://api.example.com/more-data')
  })
  .then((response) => response.json())
  .then((moreData) => {
    console.log('More Data:', moreData)
  })
  .catch((error) => {
    console.error('Error:', error)
  })

链接 Promise 可让您按顺序处理多个异步操作,从而提高可读性和可维护性。

27. Promise.all 用于并发执行

问题:同时处理多个异步操作可能具有挑战性。

解决方案:使用 `Promise.all` 来处理并发异步操作。

const fetchData1 = fetch('https://api.example.com/data1').then((response) => response.json())
const fetchData2 = fetch('https://api.example.com/data2').then((response) => response.json())
Promise.all([fetchData1, fetchData2])
  .then(([data1, data2]) => {
    console.log('Data 1:', data1)
    console.log('Data 2:', data2)
  })
  .catch((error) => {
    console.error('Error:', error)
  })

`Promise.all` 允许您同时处理多个异步操作,并在所有操作完成后继续执行。

28. 防抖动函数 (+)

问题:频繁的函数调用(例如在窗口调整大小事件期间)会降低性能。

解决方案:使用防抖动函数来限制函数执行的速率。

function debounce(func, wait) {
  let timeout;
  return function (…args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), wait);
  };
}
window.addEventListener('resize', debounce(() => {
  console.log('Window resized');
}, 200));

防抖动函数可确保函数仅在一段时间不活动后才被调用,从而提高性能。

29. 节流阀函数 (+)

问题:限制频繁触发的事件(如滚动或调整大小)的函数执行速率。

解决方案:使用节流阀函数来限制函数的执行速率。

function throttle(func, limit) {
  let lastFunc;
  let lastRan;
  return function (…args) {
    if (!lastRan) {
      func.apply(this, args);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(() => {
        if (Date.now() - lastRan >= limit) {
          func.apply(this, args);
          lastRan = Date.now();
        }
      }, limit - (Date.now() - lastRan));
    }
  };
}
window.addEventListener('scroll', throttle(() => {
  console.log('Window scrolled');
}, 200));

节流函数可确保在指定时间段内最多只调用一次函数,从而提高频繁触发事件的性能。

30. 深度克隆对象

问题:克隆嵌套对象可能很棘手且容易出错。

解决方案:使用结构化克隆或 Lodash 等库来深度克隆对象。

const obj = { a: 1, b: { c: 2 } }
const deepClone = JSON.parse(JSON.stringify(obj))
console.log(deepClone) // { a: 1, b: { c: 2 } }

深度克隆确保嵌套对象按值复制,而不是按引用复制,从而防止对原始对象进行意外修改。

31. 记忆化 (++)

问题:反复调用昂贵的函数会降低性能。

解决方案:使用记忆化来缓存昂贵的函数调用的结果。

function memoize(func) {
  const cache = new Map();
  return function (…args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    const result = func.apply(this, args);
    cache.set(key, result);
    return result;
  };
}
const expensiveFunction = memoize((num) => {
  console.log('Computing…');
  return num * 2;
});
console.log(expensiveFunction(2)); // "Computing…" 4
console.log(expensiveFunction(2)); // 4

记忆化通过缓存昂贵的函数调用结果并返回缓存的结果以供后续具有相同参数的调用来提高性能。

32. 柯里化函数 (++)

问题:创建具有多个参数的函数可能很麻烦。

解决方案:使用柯里化创建具有部分应用参数的函数。

function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args)
    }
    return function (...nextArgs) {
      return curried.apply(this, args.concat(nextArgs))
    }
  }
}
const sum = (a, b, c) => a + b + c
const curriedSum = curry(sum)
console.log(curriedSum(1)(2)(3)) // 6
console.log(curriedSum(1, 2)(3)) // 6

通过柯里化,您可以创建可以用较少参数调用的函数,并返回接受其余参数的新函数。

33. 部分应用

问题:调用带有重复参数的函数可能很繁琐。

解决方案:使用部分应用将一些参数预先应用于函数。

function partial(func, ...presetArgs) {
  return function (...laterArgs) {
    return func(...presetArgs, ...featureListlaterArgs)
  }
}
const multiply = (a, b, c) => a * b * c
const double = partial(multiply, 2)
console.log(double(3, 4)) // 24

部分应用允许您通过预先应用一些参数来创建新函数,从而使您的代码更加灵活和可重用。

34. 函数组合

问题:将多个函数组合成一个操作可能很麻烦。

解决方案:使用函数组合来组合多个函数。

const compose =
  (...funcs) =>
  (arg) =>
    funcs.reduceRight((prev, fn) => fn(prev), arg)
const add = (x) => x + 1
const multiply = (x) => x * 2
const addThenMultiply = compose(multiply, add)
console.log(addThenMultiply(5)) // 12

函数组合允许您通过组合多个函数来创建新函数,从而使您的代码更加模块化和可重用。

35. 函数流水线

问题:将一系列函数应用于一个值可能会很冗长。

解决方案:使用函数流水线按顺序应用一系列函数。

const pipe =
  (...funcs) =>
  (arg) =>
    funcs.reduce((prev, fn) => fn(prev), arg)
const add = (x) => x + 1
const multiply = (x) => x * 2
const addThenMultiply = pipe(add, multiply)
console.log(addThenMultiply(5)) // 12

函数流水线允许您按顺序将一系列函数应用于一个值,从而提高代码的可读性和可维护性。

36. 自调用函数

问题:定义后立即执行函数可能很麻烦。

解决方案:使用立即调用函数表达式 (IIFE)。

(function () {
    console.log('This runs immediately!');
})();

IIFE 允许您在定义后立即执行函数,这对于创建隔离范围和避免污染全局命名空间非常有用。

37. 避免使用全局变量

问题:全局变量可能导致冲突和意外的副作用。

解决方案:使用局部变量和模块来避免污染全局命名空间。

// Using local variables
function doSomething() {
  let localVariable = 'This is local'
  console.log(localVariable)
} // Using modules
const myModule = (function () {
  let privateVariable = 'This is private'
  return {
    publicMethod() {
      console.log(privateVariable)
    }
  }
})()
myModule.publicMethod() // "This is private"

避免使用全局变量有助于防止冲突和意外副作用,从而使您的代码更加模块化和易于维护。

38. 使用闭包进行封装

问题:暴露函数的内部细节可能会导致误用。

解决方案:使用闭包封装内部细节。

function createCounter() {
  let count = 0
  return {
    increment() {
      count++
      return count
    },
    decrement() {
      count--
      return count
    }
  }
}
const counter = createCounter()
console.log(counter.increment()) // 1
console.log(counter.increment()) // 2
console.log(counter.decrement()) // 1

闭包允许您封装内部细节并仅公开必要的功能,从而提高代码安全性和可维护性。

39. 模块模式

问题:将代码组织成可重用的模块可能具有挑战性。

解决方案:使用模块模式创建可重用和封装的代码。

const myModule = (function () {
  let privateVariable = 'This is private'
  function privateMethod() {
    console.log(privateVariable)
  }
  return {
    publicMethod() {
      privateMethod()
    }
  }
})()
myModule.publicMethod() // "This is private"

模块模式允许您创建可重用和封装的代码,从而改善代码组织和可维护性。

40. 单例模式

问题:确保只创建一个类的实例可能具有挑战性。

解决方案:使用单例模式创建单个实例。

const singleton = (function () {
  let instance
  function createInstance() {
    return { name: 'Singleton Instance' }
  }
  return {
    getInstance() {
      if (!instance) {
        instance = createInstance()
      }
      return instance
    }
  }
})()
const instance1 = singleton.getInstance()
const instance2 = singleton.getInstance()
console.log(instance1 === instance2) // true

单例模式确保只创建一个类的实例,这对于管理共享资源或配置很有用。

41. 工厂模式

问题:创建具有复杂初始化的对象可能很麻烦。

解决方案:使用工厂模式创建对象。

function createUser(name, role) {
  return {
    name,
    role,
    sayHello() {
      console.log(`Hello, my name is ${this.name} and I am a ${this.role}`)
    }
  }
}
const admin = createUser('Alice', 'admin')
const user = createUser('Bob', 'user')
admin.sayHello() // "Hello, my name is Alice and I am an admin"
user.sayHello() // "Hello, my name is Bob and I am a user"

工厂模式允许您以灵活且可重用的方式创建具有复杂初始化的对象。

42. 观察者模式

问题:管理状态变化和通知多个组件可能具有挑战性。

解决方案:使用观察者模式来管理状态变化并通知观察者。

function Subject() {
  this.observers = []
}
Subject.prototype = {
  subscribe(observer) {
    this.observers.push(observer)
  },
  unsubscribe(observer) {
    this.observers = this.observers.filter((obs) => obs !== observer)
  },
  notify(data) {
    this.observers.forEach((observer) => observer.update(data))
  }
}
function Observer(name) {
  this.name = name
}
Observer.prototype.update = function (data) {
  console.log(`${this.name} received data: ${data}`)
}
const subject = new Subject()
const observer1 = new Observer('Observer 1')
const observer2 = new Observer('Observer 2')
subject.subscribe(observer1)
subject.subscribe(observer2)
subject.notify('New data available') // "Observer 1 received data: New data available" "Observer 2 received data: New data available"

观察者模式允许您管理状态变化并通知多个观察者,从而改善代码组织和可维护性。

43. 事件委托

问题:向多个元素添加事件监听器会降低性能。

解决方案:使用事件委托有效地管理事件。

document.getElementById('parent').addEventListener('click', (event) => {
  if (event.target && event.target.matches('button.className')) {
    console.log('Button clicked:', event.target.textContent)
  }
})

事件委托允许您通过向公共父元素添加单个事件侦听器并处理多个子元素的事件来有效地管理事件。

44. 避免使用 `eval()`

问题:使用 `eval()` 可能导致安全漏洞和性能问题。

解决方案:避免使用 `eval()` 并使用更安全的替代方案。

// Avoid
const code = 'console.log("Hello, world!")'
eval(code) // "Hello, world!"
// Use safer alternatives
const func = new Function('console.log("Hello, world!")')
func() // "Hello, world!"

避免使用 `eval()` 有助于防止安全漏洞和性能问题,从而使您的代码更安全、更高效。

45. 使用 `for…of` 进行迭代

问题:使用 `for…in` 迭代数组容易出错。

解决方案:使用 `for…of` 迭代数组和其他可迭代对象。

const arr = [1, 2, 3, 4, 5]
for (const value of arr) {
  console.log(value)
} // 1// 2// 3// 4// 5

`for…of` 提供了一种简单而安全的方法

总结

无论您是想要提升技能的经验丰富的开发人员,还是渴望学习基础知识的新手,此文章内容都能满足大家的需求,深入了解并发现像专业人士一样掌握 JavaScript 的秘诀!”

感谢您的阅读,祝编程愉快!

 

posted on 2024-06-03 09:32  bala001  阅读(5)  评论(0编辑  收藏  举报

导航