1. 事件循环
事件循环是JavaScript运行时环境的核心机制,它负责处理异步任务,如setTimeout、setInterval以及各种I/O操作。JavaScript是单线程的,这意味着它在一个时间点只能处理一个任务。为了实现异步操作,事件循环会将任务分为两类:宏任务(Macro Task)和微任务(Micro Task)。
宏任务包括:脚本执行、setTimeout、setInterval等。
微任务包括:Promise、MutationObserver(监听DOM对象树更改,进行DOM操作)等。
事件循环按以下顺序执行:
- 从宏任务队列中取出一个任务并执行。
- 执行完该宏任务后,处理所有微任务队列中的任务。
- 检查是否有新的宏任务,如果有,则重复步骤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特性,它允许函数在其定义的词法作用域之外访问和操作变量。
应用场景:
- 模块化和封装:闭包可以帮助您隐藏内部实现细节,从而创建模块化和封装良好的代码。
- 计时器和回调:闭包常用于实现定时器(如setTimeout和setInterval)或事件监听器的回调函数。
- 高阶函数:高阶函数是以其他函数为参数或返回其他函数的函数。闭包使得高阶函数能够访问并操作外部作用域的变量。
模块化封装示例:
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之间的主要区别:
- 语法和可读性:
- Promise使用链式方法调用(then、catch和finally)来处理异步操作的结果。这种方法在处理复杂的异步操作时可能导致所谓的“回调地狱”,使代码变得难以阅读和维护。
- async/await允许您使用类似于同步代码的语法来处理异步操作。这使得代码更简洁、可读性更强,并且更容易理解。
- 错误处理:
- 对于Promise,您需要使用catch方法或者在then方法中传递两个参数(一个处理成功,一个处理失败)来捕获和处理错误。
- 使用async/await时,您可以使用传统的try/catch语句来捕获和处理错误。这使得错误处理更加一致,与同步代码的处理方式相似。
- 返回值:
- 一个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)技术
- 防抖(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);
};
}
可以看到防抖 其实也是一种闭包的 实现
- 节流(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中引入)。
- 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!"
- ES6模块:
ES6模块是ECMAScript 2015(ES6)引入的模块系统。它使用import和export关键字来导入和导出模块。
代码示例:
// 导出一个模块
export function hello() {
console.log("Hello, World!");
}
// 导入一个模块
import { hello } from "./myModule";
hello(); // 输出:"Hello, World!"
7. 深拷贝和浅拷贝
- 浅拷贝(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("参数类型复杂, 请使用深拷贝")
}
- 深拷贝(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)进行相应的处理。
事件委托有以下优点:
- 减少事件监听器的数量:由于事件监听器是在父元素上添加的,而不是为每个子元素单独添加,因此减少了事件监听器的数量,从而提高了应用程序的性能。
- 动态元素处理:当动态添加或删除子元素时,无需为新的子元素添加或删除事件监听器,因为它们会自动使用父元素的事件监听器。这简化了处理动态内容的逻辑。
- 内存使用优化:使用事件委托,您可以减少为每个子元素分配的内存,因为只有一个事件监听器处理所有子元素的事件。
以下是一个简单的事件委托示例:
<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值的情况:
- 全局上下文中的this:
在全局上下文(函数外部)中,this指向全局对象。在浏览器环境中,全局对象是window。
console.log(this === window); // Output: true
- 函数调用中的this:
在普通函数调用中,this通常指向全局对象(非严格模式下)或undefined(严格模式下)。
function myFunction() {
console.log(this);
}
myFunction(); // Output: Window (non-strict mode) or undefined (strict mode)
- 方法调用中的this:
在对象方法调用中,this指向调用该方法的对象。
const obj = {
method() {
console.log(this);
},
};
obj.method(); // Output: obj
- 构造函数中的this:
在构造函数中,this指向新创建的实例对象。
function MyConstructor() {
this.value = 42;
}
const instance = new MyConstructor();
console.log(instance.value); // Output: 42
- 显示绑定中的this:
您可以使用call、apply或bind方法显式地设置函数的this值。
function myFunction() {
console.log(this);
}
const obj = { value: 42 };
myFunction.call(obj); // Output: obj
- 箭头函数中的this:
在箭头函数中,this值在函数创建时继承自外部作用域,而不是在调用时设置。
const obj = {
value: 42,
method() {
const arrowFunction = () => {
console.log(this);
};
arrowFunction();
},
};
obj.method(); // Output: obj
以上就是,我分享的JavaScript 的知识内容啦,代码片段中 有任何问题,欢迎评论 或 私信