前端开发之:javascript高级

javascript 高级

安全的函数作用域

立即调用的函数表达式(IIFE)是一种常见的JavaScript模式,用于创建私有作用域。IIFE是一个函数表达式,它在定义后立即调用。在IIFE中定义的变量和函数只能在IIFE内部访问,而不是在全局作用域中访问。这使得IIFE非常有用,因为它们可以帮助防止命名冲突和其他问题,同时仍然允许在全局作用域中使用公共变量和函数。

(function() {
  // 这段代码被包裹在IIFE中,以创建一个私有作用域
  var privateVariable = "I am private!";
  function privateFunction() {
    console.log(privateVariable);
  }
  // 这些变量和函数在IIFE之外是不可访问的
  var publicVariable = "I am public!";
  function publicFunction() {
    console.log(publicVariable);
  }
  // 这个对象只包含公共变量和函数
  window.myObject = {
    publicVariable: publicVariable,
    publicFunction: publicFunction
  };
})();
// myObject对象可以从IIFE之外访问,但它只包含公共变量和函数。私有变量和函数是不可访问的。

Technique描述中文
Closure一个记住其外部变量并可以访问它们的函数闭包
Currying将接受多个参数的函数转换为一次接受一个参数的函数柯里化
Memoization缓存函数的结果,以便无需重新计算记忆化
Debounce限制函数被调用的速率,直到自上次调用以来经过了一定的时间防抖动
Throttle限制函数被调用的速率,确保它每个指定时间间隔最多被调用一次节流

示例

  1. Closure 闭包
    // 所提供的代码块是 JavaScript 中闭包的一个示例。闭包是一个记住其外部变量并可以访问它们的函数。在这个例子中,内部函数 innerFunction 记住了其外部函数 outerFunction 中的 outerVariable 的值。当 innerFunction 被返回并赋值给 innerFunc 时,它仍然可以访问 outerVariable 并在调用时记录其值。
    function outerFunction() {
    let outerVariable = "I am outside!";
    function innerFunction() {
      console.log(outerVariable);
    }
    return innerFunction;
    }
    let innerFunc = outerFunction();
    innerFunc(); // 输出:"I am outside!"
    

    描述

    闭包是一个记住其外部变量并可以访问它们的函数。
    在JavaScript中,每当创建一个函数时,都会创建一个闭包。
    闭包允许函数访问其外部作用域中的变量,即使在函数返回后仍然可以访问这些变量。这使得闭包非常有用,因为它们可以用于许多不同的编程模式,例如模块模式和柯里化。

    特点

    1. 闭包中的变量通常存储在堆内存中,而不是栈内存中,因为它们需要在函数调用后继续存在。当函数返回时,其内部函数和变量仍然存在于内存中,因为它们被闭包引用。
    2. 闭包的缺点是可能会导致内存泄漏,因为闭包中的变量不会被垃圾回收器回收,直到闭包被销毁。如果闭包被频繁创建并且保留了大量数据,则可能会导致内存问题。此外,闭包可能会导致意外的变量共享问题,因为闭包可以访问其外部作用域中的变量,这可能会导致变量被意外更改。因此,在使用闭包时,需要小心处理变量作用域和内存管理问题。

    3. 闭包的广泛应用包括但不限于:

      Technique描述中文
      Module Pattern使用闭包来创建私有变量和方法,以避免全局命名空间污染模块模式
      Currying使用闭包来将多个参数的函数转换为一系列接受单个参数的函数柯里化
      Event Handlers使用闭包来保存事件处理程序中的状态信息,将相关的函数和数据组合在一起并隐藏实现细节事件处理程序
      Callback Functions使用闭包来保存回调函数中的状态信息,以便稍后访问它们回调函数
      setTimeout and setInterval使用闭包来保存定时器中的状态信息,以便稍后访问它们定时器
      防抖和节流限制函数被调用的速率和频率

    闭包 tips:

    1. 避免在循环中创建闭包,因为这可能会导致意外的变量共享问题。
    2. 避免在闭包中存储大量数据,以避免内存泄漏和性能问题。
    3. 在不需要使用闭包时将其销毁,以避免内存泄漏。
    4. 仔细处理变量作用域和内存管理问题,以避免意外的行为和性能问题。
    5. 仅在必要时使用闭包,以避免过度使用和不必要的复杂性。

    浏览器内存泄漏

    // 浏览器内存泄漏是指在Web浏览器中,由于代码错误或不良设计而导致的内存泄漏问题。这些问题可能会导致浏览器崩溃或变慢,因此需要及时解决。以下是一些可能导致浏览器内存泄漏的常见原因:

原因描述
全局变量在JavaScript中,全局变量会一直存在于内存中,直到页面关闭或变量被删除。如果您在代码中使用全局变量,请确保在不需要时将其删除,以避免内存泄漏。
闭包如上所述,闭包可能会导致内存泄漏,因为它们可以保留对其外部作用域中变量的引用。如果闭包被频繁创建并且保留了大量数据,则可能会导致内存问题。
DOM引用在JavaScript中,对DOM元素的引用也可能导致内存泄漏。如果您在代码中保留对DOM元素的引用,而这些元素已被删除或替换,则可能会导致内存泄漏。
定时器和回调函数在JavaScript中,定时器和回调函数也可能导致内存泄漏。如果您在代码中使用定时器或回调函数,请确保在不需要时将其删除,以避免内存泄漏。
循环引用在JavaScript中,循环引用也可能导致内存泄漏。如果您在代码中创建了循环引用,请确保在不需要时将其删除,以避免内存泄漏。
  1. Currying 柯里化
function multiply(a, b) {
  return a * b;
}
function curriedMultiply(a) {
  return function(b) {
    return multiply(a, b);
  }
}
let curriedDouble = curriedMultiply(2);
console.log(curriedDouble(5)); // 输出: 10

柯里化特征

特征描述
参数复用将一个或多个参数固定为常量,以便在稍后的时间点重复使用它们
延迟返回将柯里化函数返回的函数保存在变量中,而不是立即调用它,减少不必要的计算,并提高代码的性能
多参数分解为单个参数的回调将多个参数的函数转换为一系列接受单个参数的函数,这些单参数函数可以被组合在一起,以便在稍后的时间点一次性地传递所有参数

2.1 柯里化的参数复用是指在柯里化函数中,将一个或多个参数固定为常量,以便在稍后的时间点重复使用它们。这可以通过在柯里化函数中返回一个新函数来实现,该函数将固定参数与新参数组合在一起。这种技术可以帮助减少重复的代码,并提高代码的可读性和可维护性。

```javascript
function curriedMultiply(a) {
return function(b) {
return a * b;
}
}

let curriedDouble = curriedMultiply(2);
console.log(curriedDouble(5)); // 输出: 10

// 在上面的代码中,我们有一个curriedMultiply函数,它接受一个参数'a'并返回一个新函数,该函数接受另一个参数'b'并返回'a'和'b'的乘积。然后,我们通过使用参数2调用curriedMultiply来创建一个新函数curriedDouble。这个新函数curriedDouble接受一个参数'b'并返回2和'b'的乘积。我们可以重复使用这个函数,而不必再次指定'a',并使用不同的'b'值。这是一个具有参数复用的柯里化示例。

<pre><code class="line-numbers"> ```javascript
// 可以使用函数组合来避免柯里化的多函数组合。函数组合是将多个函数组合在一起以创建一个新函数的技术。这种技术可以使代码更加简洁和易于理解,并且可以避免创建过多的函数对象。以下是一个使用函数组合的示例:
// 函数柯里化的应用之一是在函数式编程中实现函数组合。函数组合是将多个函数组合在一起以创建一个新函数的技术。这种技术可以使代码更加简洁和易于理解,并且可以避免创建过多的函数对象。以下是一个使用函数组合的示例:

function add(a, b) {
return a + b;
}

function multiply(a, b) {
return a * b;
}

function compose(f, g) {
return function(x, y, z) {
return f(g(x, y), z);
};
}

let addAndMultiply = compose(multiply, add);
console.log(addAndMultiply(2, 3, 4)); // 输出: 20

// 在上面的示例中,我们定义了两个函数add和multiply,然后定义了一个compose函数,该函数将两个函数组合在一起以创建一个新函数addAndMultiply。这个新函数接受三个参数,并将它们传递给组合函数f,该函数将两个参数传递给g函数,然后将结果传递给multiply函数。最后,我们使用addAndMultiply函数来计算2 + 3,然后将结果乘以4,得到20。
```

2.2 延迟返回
// 柯里化的延迟返回是指将柯里化函数返回的函数保存在变量中,而不是立即调用它。这种技术可以延迟函数的执行,直到需要它的结果为止。这可以帮助减少不必要的计算,并提高代码的性能。以下是一个使用柯里化的延迟返回的示例:
```javascript
function add(a, b) {
return a + b;
}

function curriedAdd(a) {
return function(b) {
return add(a, b);
}
}

let add5 = curriedAdd(5); // 保存返回的函数
console.log(add5(3)); // 输出: 8
console.log(add5(4)); // 输出: 9

// 在上面的示例中,我们定义了一个add函数和一个curriedAdd函数,该函数将两个参数的add函数转换为一个接受单个参数的函数。然后,我们将curriedAdd函数应用于5,将返回的函数保存在add5变量中。最后,我们使用add5函数来计算3 + 5和4 + 5的结果。由于我们已经保存了返回的函数,因此我们可以多次使用它,而不必每次都重新创建它。这可以提高代码的性能,并减少不必要的计算。
```

### 应用注意
// 柯里化的多参数使用问题是指在函数式编程中,当需要传递多个参数时,柯里化函数可能会导致创建过多的函数对象,从而影响代码的性能。为了解决这个问题,可以使用函数组合或延迟返回来避免创建过多的函数对象,并提高代码的性能。另外,递归实现函数柯里化也是一种解决多参数使用问题的方法,可以使代码更加简洁和易于理解。

### 递归实现函数柯里化
```javascript
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}

// 使用递归实现函数柯里化可以使代码更加简洁和易于理解,并且可以避免创建过多的函数对象。在上面的示例中,我们定义了一个curry函数,该函数接受一个函数fn作为参数,并返回一个新函数。这个新函数使用递归来实现柯里化,如果传递的参数数量大于或等于原始函数的参数数量,则调用原始函数并返回结果。否则,返回一个新函数,该函数将传递的参数与之前的参数合并,并继续等待更多的参数。最后,我们可以使用curry函数来对任何函数进行柯里化,例如:

function add(a, b, c) {
return a + b + c;
}

let curriedAdd = curry(add);
console.log(curriedAdd(2)(3)(4)); // 输出: 9

// 在上面的示例中,我们使用curry函数对add函数进行柯里化,并将其赋值给curriedAdd变量。然后,我们使用curriedAdd函数来计算2 + 3 + 4,得到9。

链式调用

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  };
}

function compose(...fns) {
  return function composed(result) {
    for (let i = fns.length - 1; i >= 0; i--) {
      result = fns[i].call(this, result);
    }
    return result;
  };
}

// 使用函数柯里化实现函数链式调用
function chainFunctions(...fns) {
  return function chainedFunctions(...args) {
    let result = fns[0].apply(this, args);
    for (let i = 1; i < fns.length; i++) {
      result = fns[i].call(this, result);
    }
    return result;
  };
}

function add(a, b, c) {
  return a + b + c;
}

function multiply(a, b) {
  return a * b;
}

let addAndMultiply = chainFunctions(curry(add), multiply);
console.log(addAndMultiply(2)(3)(4)); // 输出: 20
  1. Memoization 记忆化
function expensiveFunction() {
  console.log("Expensive function is called");
  return 3.1415;
}
function memoize(func) {
  let cache = {};
  return function() {
    let key = JSON.stringify(arguments);
    if (cache[key]) {
      console.log("Cached result is returned");
      return cache[key];
    }
    let result = func.apply(this, arguments);
    cache[key] = result;
    console.log("New result is calculated and cached");
    return result;
  }
}
let memoizedFunction = memoize(expensiveFunction);
console.log(memoizedFunction(1, 2)); // 输出: "New result is calculated and cached" "3.1415"
console.log(memoizedFunction(1, 2)); // 输出: "Cached result is returned" "3.1415"

Memoization 的原理

Memoization是一种优化技术,它可以缓存函数的结果,以避免重复计算。在memoize函数中,我们定义了一个cache对象,用于存储函数的结果。当memoizedFunction被调用时,它首先检查cache对象是否已经存储了该函数的结果。如果是,则返回缓存的结果,否则计算新的结果并将其存储在cache对象中。这样,下次调用memoizedFunction时,就可以直接返回缓存的结果,而不需要重新计算。

Memoization 的注意项

  1. Memoization只适用于纯函数,即函数的输出只由输入决定。如果函数的输出还受到其他因素的影响,如全局变量、随机数等,则无法使用Memoization。
  2. Memoization会占用内存,因为它需要缓存函数的结果。如果缓存的结果过多,可能会导致内存溢出。因此,需要根据实际情况来决定是否使用Memoization。
  3. Memoization的缓存对象需要考虑清除机制,以避免缓存过期或者无用的缓存占用内存。可以考虑使用WeakMap等数据结构来实现缓存对象,以便自动清除无用的缓存。
function memoize(func) {
  let cache = new WeakMap(); // 使用WeakMap作为缓存对象
  return function() {
    let args = Array.from(arguments); // 将arguments转换为数组
    if (cache.has(args)) { // 使用has方法检查缓存中是否已经存在该参数
      console.log("Cached result is returned");
      return cache.get(args); // 使用get方法获取缓存中的结果
    }
    let result = func.apply(this, arguments);
    cache.set(args, result); // 使用set方法将结果存储到缓存中
    console.log("New result is calculated and cached");
    return result;
  }
}
  1. Debounce 防抖动
function debounce(func, delay) {
  let timeoutId;
  return function() {
    if (timeoutId) {
      clearTimeout(timeoutId);
    }
    timeoutId = setTimeout(() => {
      func.apply(this, arguments);
    }, delay);
  }
}
function logMessage() {
  console.log("Message is logged");
}
let debouncedLogMessage = debounce(logMessage, 1000);
debouncedLogMessage(); // Output: (after 1 second) "Message is logged"

防抖原理

防抖是一种优化技术,它可以防止函数在短时间内被频繁调用。在debounce函数中,我们定义了一个timeoutId变量,用于存储setTimeout返回的计时器ID。当debounced函数被调用时,它首先检查timeoutId是否存在。如果存在,则清除计时器,否则设置一个新的计时器,并在指定的延迟时间后调用原始函数。这样,如果debounced函数在指定的延迟时间内被多次调用,只有最后一次调用会触发原始函数的执行。

防抖需要注意的项:

  1. 防抖的时间间隔需要根据实际情况进行调整,过短的时间间隔可能无法达到预期的效果,过长的时间间隔可能会影响用户体验。
  2. 需要注意this指向,可以使用箭头函数或者bind方法来绑定this。
  3. 需要注意防抖函数的返回值,如果原始函数有返回值,需要在防抖函数中将其返回。
    // 防抖函数中的this指向可以使用箭头函数或者bind方法来绑定
    // 例如:
    function debounce(func, delay) {
      let timeoutId;
      return function() {
        if (timeoutId) {
          clearTimeout(timeoutId);
        }
        timeoutId = setTimeout(() => {
          func.apply(this, arguments);
        }, delay);
      }
    }
    // 在箭头函数中,this指向的是定义时的作用域,而不是调用时的作用域
    // 如果需要在防抖函数中绑定this,可以使用bind方法
    // 例如:
    let obj = {
      logMessage() {
        console.log("Message is logged");
      }
    };
    let debouncedLogMessage = debounce(obj.logMessage.bind(obj), 1000);
    debouncedLogMessage(); // Output: (after 1 second) "Message is logged"
    
    1. Throttle 节流
function throttle(func, limit) {
  let inThrottle;
  return function() {
    if (!inThrottle) {
      func.apply(this, arguments);
      inThrottle = true;
      setTimeout(() => {
        inThrottle = false;
      }, limit);
    }
  }
}
function logMessage() {
  console.log("Message is logged");
}
let throttledLogMessage = throttle(logMessage, 1000);
throttledLogMessage(); // Output: "Message is logged"
throttledLogMessage(); // Output: (after 1 second) "Message is logged"

节流原理

节流是一种优化技术,它可以限制函数在一定时间内只能执行一次。在throttle函数中,我们定义了一个inThrottle变量,用于标记函数是否处于节流状态。当throttled函数被调用时,它首先检查inThrottle变量是否为true。如果为true,则表示函数正在节流状态中,直接返回。否则,调用原始函数,并将inThrottle设置为true。在指定的时间间隔后,将inThrottle设置为false,这样就可以再次触发函数的执行。

节流需要注意的项:

  1. 节流的时间间隔需要根据实际情况进行调整,过短的时间间隔可能无法达到预期的效果,过长的时间间隔可能会影响用户体验。
  2. 需要注意this指向,可以使用箭头函数或者bind方法来绑定this。
  3. 需要注意节流函数的返回值,如果原始函数有返回值,需要在节流函数中将其返回。
// 节流函数中的this指向可以使用箭头函数或者bind方法来绑定
// 例如:
function throttle(func, limit) {
  let inThrottle;
  return function() {
    if (!inThrottle) {
      func.apply(this, arguments);
      inThrottle = true;
      setTimeout(() => {
        inThrottle = false;
      }, limit);
    }
  }
}
// 在箭头函数中,this指向的是定义时的作用域,而不是调用时的作用域
// 如果需要在节流函数中绑定this,可以使用bind方法
// 例如:
let obj = {
  logMessage() {
    console.log("Message is logged");
  }
};
let throttledLogMessage = throttle(obj.logMessage.bind(obj), 1000);
throttledLogMessage(); // Output: "Message is logged"
throttledLogMessage(); // Output: (after 1 second) "Message is logged"
上一篇
下一篇