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

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'值。这是一个具有参数复用的柯里化示例。

// 可以使用函数组合来避免柯里化的多函数组合。函数组合是将多个函数组合在一起以创建一个新函数的技术。这种技术可以使代码更加简洁和易于理解,并且可以避免创建过多的函数对象。以下是一个使用函数组合的示例:

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 延迟返回
// 柯里化的延迟返回是指将柯里化函数返回的函数保存在变量中,而不是立即调用它。这种技术可以延迟函数的执行,直到需要它的结果为止。这可以帮助减少不必要的计算,并提高代码的性能。以下是一个使用柯里化的延迟返回的示例:

 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的结果。由于我们已经保存了返回的函数,因此我们可以多次使用它,而不必每次都重新创建它。这可以提高代码的性能,并减少不必要的计算。

应用注意

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

递归实现函数柯里化

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。

以下是根据您的要求调整后的Markdown格式文章:

链式调用

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

3. 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;
  };
}

4. 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"

5. 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"
上一篇
下一篇