JavaScript 知识 事件循环、闭包、Promise、原型继承、防抖和节流、模块化、深拷贝和浅拷贝、事件委托、垃圾回收以及 this 关键字

1. 事件循环

事件循环是JavaScript运行时环境的核心机制,它负责处理异步任务,如setTimeout、setInterval以及各种I/O操作。JavaScript是单线程的,这意味着它在一个时间点只能处理一个任务。为了实现异步操作,事件循环会将任务分为两类:宏任务(Macro Task)和微任务(Micro Task)。

宏任务包括:脚本执行、setTimeout、setInterval等。
微任务包括:Promise、MutationObserver(监听DOM对象树更改,进行DOM操作)等。

事件循环按以下顺序执行:

  1. 从宏任务队列中取出一个任务并执行。
  2. 执行完该宏任务后,处理所有微任务队列中的任务。
  3. 检查是否有新的宏任务,如果有,则重复步骤1,否则继续下一个循环。
    通过这种方式,事件循环确保了JavaScript代码的顺序执行,同时允许异步任务在适当的时间插入到执行流中。

下方给出一个伪代码示例,便于理解 事件循环

function eventLoop() {
  while (true) {
    // 检查是否有待处理的宏任务
    if (macroTaskQueue.length > 0) {
      // 取出一个宏任务并执行
      const macroTask = macroTaskQueue.shift();
      execute(macroTask);
    }

    // 处理所有的微任务
    while (microTaskQueue.length > 0) {
      // 取出一个微任务并执行
      const microTask = microTaskQueue.shift();
      execute(microTask);
    }

    // 检查是否有新的宏任务,如果没有,继续下一个循环
    if (macroTaskQueue.length === 0) {
      break;
    }
  }
}

2. 闭包

闭包是一个强大的JavaScript特性,它允许函数在其定义的词法作用域之外访问和操作变量。

应用场景:

  1. 模块化和封装:闭包可以帮助您隐藏内部实现细节,从而创建模块化和封装良好的代码。
  2. 计时器和回调:闭包常用于实现定时器(如setTimeout和setInterval)或事件监听器的回调函数。
  3. 高阶函数:高阶函数是以其他函数为参数或返回其他函数的函数。闭包使得高阶函数能够访问并操作外部作用域的变量。

模块化封装示例:

function createModule() {
  let privateVariable = "I am private";

  function privateFunction() {
    return privateVariable;
  }

  function publicFunction() {
    return privateFunction();
  }

  return {
    publicFunction: publicFunction
  };
}

const myModule = createModule();
console.log(myModule.publicFunction()); // 输出:"I am private"

计时器和回调示例:

function delayedGreeting(name, delay) {
  function greet() {
    console.log(`Hello, ${name}!`);
  }

  setTimeout(greet, delay);
}

delayedGreeting("Amlr", 1000); // 在1秒后输出:"Hello, Amlr!"
delayedGreeting("John", 2000); // 在2秒后输出:"Hello, John!"

高阶函数示例:

function multiplier(factor) {
  function multiply(number) {
    return number * factor;
  }

  return multiply;
}

const double = multiplier(2);
const triple = multiplier(3);

console.log(double(4)); // 输出:8
console.log(triple(4)); // 输出:12

3. Promise

Promise是JavaScript中一种非常重要的异步编程工具,用于表示一个尚未完成(pending)且可能在将来成功或失败的操作。Promise使得您能够以更清晰、易于理解的方式编写异步代码,例如请求数据、读取文件或执行其他需要等待结果的操作。

Promise有三种状态:

pending(等待):初始状态,既不是成功,也不是失败。
fulfilled(实现):表示操作成功完成。
rejected(拒绝):表示操作失败。
Promise一旦从等待状态变为实现或拒绝状态,其状态就不会再改变。

创建一个Promise:

const myPromise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 操作成功 */) {
    resolve("Success");
  } else {
    reject("Error");
  }
});

为了处理Promise的成功或失败结果,您可以使用then和catch方法。then方法用于处理成功的情况,catch方法用于处理失败的情况。还可以使用finally方法处理无论成功还是失败都需要执行的代码。

处理Promise结果:

myPromise
  .then((result) => {
    console.log(`Success: ${result}`);
  })
  .catch((error) => {
    console.log(`Error: ${error}`);
  })
  .finally(() => {
    console.log("Promise completed");
  });

您还可以使用async/await语法来简化Promise的处理。这使您能够使用同步代码的样式编写异步操作。

async function processData() {
  try {
    const result = await myPromise;
    console.log(`Success: ${result}`);
  } catch (error) {
    console.log(`Error: ${error}`);
  } finally {
    console.log("Promise completed");
  }
}
processData();

Promise和async/await之间的主要区别:

  1. 语法和可读性:
  • Promise使用链式方法调用(then、catch和finally)来处理异步操作的结果。这种方法在处理复杂的异步操作时可能导致所谓的“回调地狱”,使代码变得难以阅读和维护。
  • async/await允许您使用类似于同步代码的语法来处理异步操作。这使得代码更简洁、可读性更强,并且更容易理解。
  1. 错误处理:
  • 对于Promise,您需要使用catch方法或者在then方法中传递两个参数(一个处理成功,一个处理失败)来捕获和处理错误。
  • 使用async/await时,您可以使用传统的try/catch语句来捕获和处理错误。这使得错误处理更加一致,与同步代码的处理方式相似。
  1. 返回值:
  • 一个Promise对象表示一个尚未完成且可能在将来成功或失败的操作。您可以使用Promise的then和catch方法来处理操作的结果。
  • async函数始终返回一个Promise。当您在async函数中使用await时,它会暂停函数的执行,直到等待的Promise解析为结果值。如果Promise被拒绝,await会抛出一个错误,您可以使用try/catch来捕获这个错误。

尽管Promise和async/await在处理异步操作时有所不同,但它们实际上是相互关联的。async/await语法是基于Promise实现的,它们一起提供了一种更简洁、易于理解的方式来处理异步操作。

在实践中,您可以根据实际需求和个人喜好选择使用Promise或async/await。对于需要大量链式操作或需要更精细的控制流程的场景,Promise可能更合适。对于需要简洁、易于阅读和维护的代码的场景,async/await可能是更好的选择。

4. 原型继承

在JavaScript中,原型继承是实现对象之间属性和方法共享的一种方式。JavaScript是基于原型的语言,这意味着对象之间的继承是通过原型(prototype)实现的,而不是通过类(如在基于类的语言中)。

代码示例:

// 创建一个构造函数
function Animal(name) {
  this.name = name;
}

// 为Animal的原型添加一个方法
Animal.prototype.speak = function() {
  console.log(`${this.name} makes a noise.`);
};

// 创建一个新的Animal实例
const animal = new Animal("Dog");
animal.speak(); // 输出:"Dog makes a noise."

// 创建一个新的构造函数,继承自Animal
function Dog(name) {
  Animal.call(this, name);
}

// 设置Dog的原型为Animal的实例,实现继承
Dog.prototype = Object.create(Animal.prototype);

// 重置Dog原型的构造函数指向,以保持正确的构造函数引用
Dog.prototype.constructor = Dog;

// 为Dog的原型添加一个方法
Dog.prototype.bark = function() {
  console.log(`${this.name} barks.`);
};

// 创建一个新的Dog实例
const dog = new Dog("Buddy");
dog.speak(); // 输出:"Buddy makes a noise."
dog.bark(); // 输出:"Buddy barks."

使用Object.create()方法设置原型链是一种推荐的方法,因为它不会触发父构造函数中的任何副作用。在设置原型后,我们还需要重置Dog原型的构造函数指向,以保持正确的构造函数引用。

5. 防抖(debouncing)和节流(throttling)技术

  1. 防抖(Debouncing):
    防抖技术意味着在一段时间内的连续触发事件,仅执行最后一次触发的事件处理函数。在这段时间内,如果事件再次触发,则重新计时。防抖通常用于处理用户输入、搜索、窗口调整等场景。

代码示例:

function debounce(fn,delay){
  if(!fn) throw new Error("缺少必传参数")
  if(typeof delay != 'number') throw new Error("请传入正确格式的时间控制")
  let timerId;
  return function(...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

可以看到防抖 其实也是一种闭包的 实现

  1. 节流(Throttling):
    节流技术意味着在一定时间间隔内,事件处理函数至多执行一次,即使事件在该时间段内连续触发。节流通常用于处理滚动事件、窗口调整、动画等场景。

代码示例:

function throttle(fn, delay) {
  if(!fn) throw new Error("缺少必传参数")
  if(typeof delay != 'number') throw new Error("请传入正确格式的时间控制")
  let timeout, previous = 0;

  return function() {
    const context = this, args = arguments;
    const now = new Date().getTime();

    if (now - previous > delay) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      fn.apply(context, args);
      previous = now;
    } else if (!timeout) {
      timeout = setTimeout(function() {
        fn.apply(context, args);
        previous = new Date().getTime();
      }, delay - (now - previous));
    }
  };
}

6. 模块化(Modularization)

JavaScript模块化是将代码分解为独立、可重用的模块的过程。通过模块化,您可以组织和维护代码,更容易地实现代码重用,避免全局变量污染,以及提高代码的可读性和可维护性。

在现代JavaScript开发中,主要有两种模块化方法:CommonJS(如Node.js中使用)和ES6模块(ECMAScript 2015中引入)。

  1. CommonJS:
    CommonJS是 Node.js 中使用的模块系统。它使用 require 函数来导入其他模块,使用 module.exports 或 exports 来导出模块的公共接口。
    需要注意的是,CommonJs 是一种同步加载模块机制,适用于 服务端 nodejs开发,如果用于浏览器前端开发的话, 会有各种加载的延迟,造成页面的卡顿,所以前端一般使用的是 ES6模块 开发

代码示例:

// 导出一个模块
module.exports = {
  hello: function() {
    console.log("Hello, World!");
  }
};

// 导入一个模块
const myModule = require("./myModule");
myModule.hello(); // 输出:"Hello, World!"
  1. ES6模块:
    ES6模块是ECMAScript 2015(ES6)引入的模块系统。它使用import和export关键字来导入和导出模块。

代码示例:

// 导出一个模块
export function hello() {
  console.log("Hello, World!");
}

// 导入一个模块
import { hello } from "./myModule";
hello(); // 输出:"Hello, World!"

7. 深拷贝和浅拷贝

  1. 浅拷贝(Shallow Copy):
    浅拷贝仅复制对象的顶层属性。对于嵌套的属性,复制后的对象和原始对象仍然共享相同的引用。以下是一个浅拷贝的实现:
function shallClone(data){
  if(!data) throw new Error("缺少必传参数")
  if(typeof data != 'object' || data === null) return data
  if(Array.isArray(data)) return data.slice()
  if(Object.prototype.toString.call(data) === "[object Object]") return Object.assign({}, data)
  throw new Error("参数类型复杂, 请使用深拷贝")
}
  1. 深拷贝(Deep Copy):
    深拷贝会递归地复制对象的所有属性,包括嵌套的属性。这样,复制后的对象和原始对象将完全独立。以下是一个深拷贝的实现(这里指只兼容了对 function 、Date、正则的拷贝):
function deepClone(data, clonePrototype = false){
  if(!data) throw new Error("缺少必传参数")
  // 是否为函数
  if(typeof data == 'function') return deepCloneFunction(data)
  // 是否为Date
  if(data instanceof Date) return cloneDate(data)
  if(typeof data != 'object' || data === null) return data
  // 是否为正则
  if(data instanceof RegExp) return cloneRegExp(data)
  if(clonePrototype){
    const newObj = Object.create(Object.getPrototypeOf(data));
    for (const key in data) {
      if (Object.hasOwnProperty.call(data, key)) {
        newObj[key] = deepClone(data[key],true);
      }
    }
    return newObj;
  }else{
    let result = Array.isArray(data)?[]:{}
    for(let key in data){
      if(data[key] instanceof RegExp) result[key] = cloneRegExp(data[key])
      else if(typeof data[key] === 'function') result[key] = deepCloneFunction(data[key])
      else if(data[key] instanceof Date) result[key] = cloneDate(data[key])
      else if(typeof data[key] === 'object' && data[key] !== null) result[key] = deepClone(data[key])
      else result[key] = data[key]
    }
    return result
  }
}

function cloneRegExp(regexp) {
  var flags = '';
  flags += regexp.global ? 'g' : '';
  flags += regexp.ignoreCase ? 'i' : '';
  flags += regexp.multiline ? 'm' : '';
  return new RegExp(regexp.source, flags);
}

function deepCloneFunction(fn) {
  // 如果传入的不是函数,直接返回
  if (typeof fn !== 'function') return fn;

  // 创建一个新函数
  const clonefn = function(...args) {
    // 在新函数中调用原函数
    return fn.apply(this, args);
  };

  // 复制原函数的原型
  Object.setPrototypeOf(clonefn, Object.getPrototypeOf(fn));

  // 递归拷贝原函数引用的对象和函数
  Object.keys(fn).forEach((prop) => {
    if (typeof fn[prop] === 'object') {
      clonefn[prop] = deepCloneFunction(fn[prop]);
    } else {
      clonefn[prop] = fn[prop];
    }
  });

  // 处理闭包作用域中的变量
  const closureVars = Object.getOwnPropertyNames(fn);
  closureVars.forEach((prop) => {
    if (!clonefn.hasOwnProperty(prop)) {
      Object.defineProperty(clonefn, prop, Object.getOwnPropertyDescriptor(fn, prop));
    }
  });

  return clonefn;
}

function cloneDate(date){
  if(!date instanceof Date) return date
  return new Date(date)
}

8. 事件委托

事件委托是一种事件处理模式,用于减少事件监听器的数量,提高应用程序的性能。事件委托的核心思想是利用事件冒泡(Event Bubbling)机制,将事件监听器添加到父元素上,而不是为每个子元素单独添加事件监听器。当子元素触发事件时,事件会冒泡到父元素,父元素的事件监听器会捕获这个事件并根据事件的目标(Event.target)进行相应的处理。

事件委托有以下优点:

  1. 减少事件监听器的数量:由于事件监听器是在父元素上添加的,而不是为每个子元素单独添加,因此减少了事件监听器的数量,从而提高了应用程序的性能。
  2. 动态元素处理:当动态添加或删除子元素时,无需为新的子元素添加或删除事件监听器,因为它们会自动使用父元素的事件监听器。这简化了处理动态内容的逻辑。
  3. 内存使用优化:使用事件委托,您可以减少为每个子元素分配的内存,因为只有一个事件监听器处理所有子元素的事件。

以下是一个简单的事件委托示例:

<ul id="list">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>

<script>
  const list = document.getElementById("list");

  list.addEventListener("click", function (event) {
    const target = event.target;

    if (target.tagName === "LI") {
      console.log("Clicked:", target.textContent);
    }
  });
</script>

9. 垃圾回收

垃圾回收 请观看 我的另外一篇文章 JavaScript 垃圾回收机制

10. this 关键字

在JavaScript中,this关键字是一个特殊的变量,它在函数调用时自动设置,指向当前函数的执行上下文。this的值取决于函数的调用方式,而不是函数的定义方式。这里是几种常见的this值的情况:

  1. 全局上下文中的this:
    在全局上下文(函数外部)中,this指向全局对象。在浏览器环境中,全局对象是window。
console.log(this === window); // Output: true
  1. 函数调用中的this:
    在普通函数调用中,this通常指向全局对象(非严格模式下)或undefined(严格模式下)。
function myFunction() {
  console.log(this);
}

myFunction(); // Output: Window (non-strict mode) or undefined (strict mode)
  1. 方法调用中的this:
    在对象方法调用中,this指向调用该方法的对象。
const obj = {
  method() {
    console.log(this);
  },
};

obj.method(); // Output: obj
  1. 构造函数中的this:
    在构造函数中,this指向新创建的实例对象。
function MyConstructor() {
  this.value = 42;
}

const instance = new MyConstructor();
console.log(instance.value); // Output: 42
  1. 显示绑定中的this:
    您可以使用call、apply或bind方法显式地设置函数的this值。
function myFunction() {
  console.log(this);
}

const obj = { value: 42 };

myFunction.call(obj); // Output: obj
  1. 箭头函数中的this:
    在箭头函数中,this值在函数创建时继承自外部作用域,而不是在调用时设置。
const obj = {
  value: 42,
  method() {
    const arrowFunction = () => {
      console.log(this);
    };

    arrowFunction();
  },
};

obj.method(); // Output: obj

以上就是,我分享的JavaScript 的知识内容啦,代码片段中 有任何问题,欢迎评论 或 私信

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路上的小蜗牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值