系统学习JavaScript

JavaScript作为一种功能强大的编程语言,在Web开发中占据了核心地位。其独特的单线程、事件驱动和非阻塞I/O特性,使得异步编程成为JavaScript的重要部分。本文将从原生JavaScript、EventLoop、Promise、Async/Await、异常处理以及插件机制等方面,结合实例进行系统学习。

1.原生JavaScript基础

原生JavaScript(简称原生JS)是JavaScript的基石,它包含了语言的基本语法、数据类型、操作符、控制流结构、函数等。

1.1、基础语法

 1.1.1JS语法嵌入页面的方式
  • 行间事件:直接在HTML标签中使用onclick等事件属性调用JS代码。
  • 页面script标签:在HTML文档的<script>标签中编写JS代码。
  • 外部引入:通过<script src="..."></script>引入外部的JS文件
1.1.2.注释
  • 单行注释://
  • 多行注释:/* ... */

1.1.3.变量
  • 命名规则:以字母、$_开头,对大小写敏感。
  • 声明方式:使用varletconst(ES6新增)声明变量。
  • 数据类型:包括原始数据类型(如String、Number、Boolean、Null、Undefined、Symbol(ES6新增))和引用数据类型(如Object、Array、Function等)。
1.1.4.运算符
  • 算数运算符:+-*/%等。
  • 赋值运算符:=+=-=等。
  • 比较运算符:=====><等。
  • 逻辑运算符:&&||!等。
1.1.5.条件语句
  • if语句
  • else if语句
  • else语句
  • switch语句
// 假设这是从用户那里获取的某种选择  
let userChoice = 'banana';  
  
// 使用switch语句根据用户的选择输出不同的消息  
switch (userChoice) {  
    case 'apple':  
        console.log('你选择了苹果!');  
        break;  
    case 'banana':  
        console.log('你选择了香蕉!');  
        break;  
    case 'cherry':  
        console.log('你选择了樱桃!');  
        break;  
    case 'date':  
        console.log('你选择了枣!');  
        break;  
    default:  
        // 如果没有任何case匹配,执行这里的代码  
        console.log('你选择了未知的水果!');  
}  
  
// 输出结果将会是: 你选择了香蕉!

在这个例子中,userChoice变量的值被设置为'banana',然后switch语句根据这个值来查找匹配的case。找到匹配的'banana'后,执行该case下的代码块,输出'你选择了香蕉!',然后遇到break语句跳出switch块。

注意:

每个case块通常以break语句结束,以防止代码自动“落入”下一个case块中(这被称为“case穿透”或“case fall-through”)。然而,在某些情况下,你可能想要故意允许case穿透,但这种情况比较罕见,并且需要谨慎处理以避免逻辑错误。
default子句是可选的,它会在没有任何case匹配时执行。如果不包含default子句,并且没有任何case匹配,那么switch语句将不会执行任何操作。
switch语句中的表达式和case子句的值可以是任何数据类型,但JavaScript会将它们转换为字符串(如果它们不是字符串的话),然后进行字符串比较。因此,比较是区分大小写的
1.1.6.循环
  • for循环

for循环是最常用的循环结构之一,它通常用于在已知循环次数的情况下进行迭代。for循环的基本语法如下

for (初始化表达式; 条件表达式; 更新表达式) {  
    // 循环体  
}

示例:打印数字15
for (let i = 1; i <= 5; i++) {  
    console.log(i);  
}  
// 输出:  
// 1  
// 2  
// 3  
// 4  
// 5
  • while循环

while循环在给定条件为真时重复执行一段代码块。如果条件一开始就是假的,那么循环体一次也不会执行。while循环的基本语法如下:

while (条件表达式) {  
    // 循环体  
}

示例:使用while循环计算15的和。
let sum = 0;  
let i = 1;  
while (i <= 5) {  
    sum += i;  
    i++;  
}  
console.log(sum); // 输出: 15
  • do...while循环

do...while循环与while循环非常相似,但有一个关键的区别:do...while循环至少会执行一次循环体,因为它是在循环体的末尾检查条件表达式的。如果条件为真,则循环继续;如果条件为假,则循环结束。do...while循环的基本语法如下:

do {  
    // 循环体  
} while (条件表达式);

示例:使用do...while循环打印数字15(尽管这不是do...while循环最典型的用法,但用于演示)
let i = 1;  
do {  
    console.log(i);  
    i++;  
} while (i <= 5);  
// 输出:  
// 1  
// 2  
// 3  
// 4  
// 5
在这个do...while循环的例子中,尽管循环的条件是在循环体的末尾检查的,但循环体仍然会至少执行一次,因为条件检查是在循环体执行之后进行的
1.1.7.函数
  • 定义函数使用function关键字。
  • 支持参数默认值、剩余参数(ES6新增)等特性。

1.2、页面操作

1.2.1.DOM(文档对象模型)

  • DOM将HTML文档呈现为树状结构,每个节点都是某种类型的对象。
  • 通过DOM API可以访问和操作HTML文档的结构、样式和内容。

1.2.2.获取页面标签使用

        document.getElementById()document.getElementsByClassName()document.getElementsByTagName()等方法获取页面元素。

1.2.3.操作标签内容

  • 修改元素的文本内容:element.innerTextelement.textContent

        举例:

        <p id="demo">Hello, World!</p>

        document.getElementById("demo").innerText = "文本已修改!";

  • 修改元素的HTML内容:element.innerHTML

1.2.4.修改标签属性

        使用element.setAttribute()element.getAttribute()element.removeAttribute()等方法修改元素的属性。

1.2.5.事件操作

  • 为元素添加事件监听器:element.addEventListener('eventName', function() {...})
  • 移除事件监听器:element.removeEventListener('eventName', function() {...})

1.3、高级特性

1.3.1.闭包
  • 闭包是函数和声明该函数的词法环境的组合体。
  • 闭包允许一个函数访问并操作函数之外的变量。
实例:闭包与立即执行函数表达式(IIFE)

闭包是JavaScript中一个强大的特性,它允许一个函数访问并操作函数之外的变量。而IIFE则是一种立即执行的函数表达式,常用于创建一个独立的作用域,避免变量污染全局作用域。

	function createCounter() {
	    let count = 0;
	    return function() {
	        count += 1;
	        console.log(count);
	    };
	}
	const counter = createCounter();
	counter(); // 输出:1
	counter(); // 输出:2

	// IIFE示例
	(function() {
	    var localVariable = "I'm only accessible inside this IIFE";
	    console.log(localVariable);
	})();
	// 输出:I'm only accessible inside this IIFE
1.3.2.原型链和继承
  • JavaScript中的对象通过原型链实现继承。
  • 每个对象都有一个原型对象(__proto__),原型对象也有自己的原型对象,直到null为止。
1.3.3.异步编程
  • JavaScript是单线程的,但支持异步操作。
  • 常用的异步编程模式包括回调函数、Promises、async/await等。
1.3.4.ES6+新特性

ES6(ECMAScript 2015)及以后版本引入了许多新特性,如箭头函数、模板字符串、解构赋值、类(Class)、模块(Module)等。

实例:使用箭头函数
	const numbers = [1, 2, 3, 4, 5];

	const doubled = numbers.map(num => num * 2);

	console.log(doubled); // 输出: [2, 4, 6, 8, 10]

实例:使用模板字符串
	const name = "Alice";

	const greeting = `Hello, ${name}!`;

	console.log(greeting); // 输出: Hello, Alice!

2.EventLoop机制

EventLoop(事件循环)是异步编程中非常重要的一个概念,尤其在JavaScript这种单线程环境中,它是处理并发I/O操作(如网络请求、文件操作等)的关键机制。下面通过一个简单的例子来解释EventLoop的工作机制。

基本概念

  • 事件队列(Event Queue):所有异步任务(如定时器、网络请求等)完成后,都会将相应的回调函数放入事件队列中等待执行。
  • 调用栈(Call Stack):当代码执行时,会将执行环境(如函数)压入调用栈中,执行完毕后从调用栈中弹出。
  • EventLoop:持续监听调用栈和事件队列,当调用栈为空时,从事件队列中取出一个任务(回调函数)压入调用栈执行,这个过程循环往复。

示例

假设我们有以下JavaScript代码:

console.log('Start');  
  
setTimeout(() => {  
    console.log('Timeout');  
}, 0);  
  
console.log('Immediate');  
  
// 假设此处有大量的同步计算  
for (let i = 0; i < 1000000; i++) {  
    // 一些计算  
}  
  
console.log('End');

让我们通过EventLoop的机制来解释这段代码的执行流程:

  1. 初始状态:调用栈为空,事件队列为空。
  2. 执行console.log('Start');:此代码是同步的,所以直接执行并输出"Start",之后该函数从调用栈中弹出。
  3. 执行setTimeout(...):这是一个异步操作,JavaScript引擎会将这个定时器的回调函数放入事件队列中,但是不会立即执行它(即使设置的延迟时间为0,也不会立即执行,因为还有其他同步代码需要执行)。
  4. 执行console.log('Immediate');:同样是同步代码,直接执行并输出"Immediate",之后该函数从调用栈中弹出。
  5. 执行for循环:这是一个耗时的同步操作,它会阻塞其他代码的执行,直到循环结束。
  6. 执行console.log('End');for循环结束后,执行并输出"End",之后该函数从调用栈中弹出。
  7. EventLoop工作:此时,调用栈为空,EventLoop检查事件队列,发现有一个由setTimeout产生的回调函数。于是,这个回调函数被移出事件队列并压入调用栈执行,输出"Timeout"

关键点

  • 异步与同步:同步代码会立即执行,而异步代码会被放入事件队列中等待执行。
  • 事件队列:所有异步任务的回调函数都会被放入事件队列中,等待调用栈为空时执行。
  • EventLoop:不断检查调用栈和事件队列,确保调用栈为空时从事件队列中取出任务执行。

通过这种方式,JavaScript能够在不阻塞主线程的情况下处理并发操作,从而提供流畅的用户体验

3.Promise

Promise 是ES6引入的一种用于处理异步操作的对象。它代表了一个尚未完成但预期将来会完成的异步操作的结果。一个 Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。Promise 提供了链式调用的能力,通过 .then().catch() .finally() 方法,可以优雅地处理异步操作的成功、失败和完成(无论成功还是失败都会执行)情况。

下面是一个 Promise 异步链式调用的例子,用于模拟从服务器获取数据,并根据获取的数据执行进一步的操作:

实例:使用Promise链式调用

// 假设 fetchData 是一个返回 Promise 的函数,模拟异步获取数据  
function fetchData(url) {  
    return new Promise((resolve, reject) => {  
        // 这里使用 setTimeout 模拟异步操作  
        setTimeout(() => {  
            // 假设这是从服务器获取的数据  
            const data = { id: 1, name: 'Alice' };  
            // 假设请求成功  
            resolve(data);  
            // 如果请求失败,则调用 reject('Error message')  
            // reject('Error message');  
        }, 1000); // 假设请求耗时 1 秒  
    });  
}  
  
// 使用 Promise 链式调用处理异步操作  
fetchData('https://api.example.com/data')  
    .then(data => {  
        // 处理成功的情况  
        console.log('获取数据成功:', data);  
        // 假设根据获取的数据进行进一步的操作,这里返回一个新的 Promise  
        return fetchUserData(data.id); // 假设 fetchUserData 是另一个返回 Promise 的函数  
    })  
    .then(userData => {  
        // 处理 fetchUserData 异步操作成功的情况  
        console.log('获取用户数据成功:', userData);  
        // 可以继续链式调用其他异步操作  
    })  
    .catch(error => {  
        // 处理整个链中任何一个 Promise 失败的情况  
        console.error('异步操作失败:', error);  
    })  
    .finally(() => {  
        // 无论成功还是失败,都会执行  
        console.log('异步操作完成');  
    });  
  
// 假设的 fetchUserData 函数,也返回一个 Promise  
function fetchUserData(userId) {  
    return new Promise((resolve, reject) => {  
        setTimeout(() => {  
            const userData = { userId, name: 'Alice Details' };  
            resolve(userData);  
            // reject('Failed to fetch user data');  
        }, 500); // 假设这个操作耗时 0.5 秒  
    });  
}

在这个例子中,fetchData 函数模拟了一个异步的 HTTP 请求,返回一个 Promise 对象。然后,我们使用 .then() 方法来处理 Promise 成功的情况,并在其中调用了另一个返回 Promise 的函数 fetchUserData。通过链式调用 .then(),我们可以基于前一个异步操作的结果执行更多的异步操作。.catch() 方法用于捕获链中任何位置发生的错误,而 .finally() 方法则无论成功还是失败都会执行,通常用于清理资源等操作。

这样,通过 Promise 的链式调用,我们可以以一种非常清晰和易于理解的方式组织和管理复杂的异步操作流

4.Async/Await

Async/Await是ES8引入的,用于简化基于Promise的异步代码。它使得异步代码看起来和同步代码一样。

注意事项

4.1.错误处理:
  • Async/Await与Promise一样,需要正确处理异常。使用try/catch来捕获并处理async函数中的错误,避免程序因未捕获的异常而中断。
  • 示例:
async function fetchData() {  
  try {  
    const response = await fetch('https://api.example.com/data');  
    const data = await response.json();  
    console.log(data);  
  } catch (error) {  
    console.error('Fetch error:', error);  
  }  
}
4.2.避免死锁:
  • 不正确的使用async/await可能会导致死锁,特别是在同步上下文中等待异步操作。确保不要在async函数中同步地等待其他async函数的执行结果,这可能会导致调用栈被无限期地占用。
  • 示例:注意避免在async函数中直接调用另一个async函数而不使用await(除非你有意为之,比如作为Promise的一部分)。
4.3.资源管理:
  • 异步操作可能涉及外部资源(如数据库连接、文件句柄等),需要确保这些资源在使用后得到正确释放,以避免资源泄漏。
  • 示例:使用try/finally结构来确保资源被释放。
4.4.代码可读性:
  • 虽然async/await提高了代码的可读性,但过度嵌套或复杂的异步逻辑仍然可能使代码难以理解。
  • 示例:尽量保持async函数的简洁性,避免过深的嵌套层次。
4.5.兼容性:
  • Async/Await是ES2017(ES8)中引入的特性,确保你的运行环境支持这一特性(如现代浏览器或Node.js的较新版本)。

学习示例

以下是一个简单的Async/Await学习示例,用于异步加载图片并显示其尺寸

// 异步加载图片的函数  
function loadImg(url) {  
  return new Promise((resolve, reject) => {  
    const img = new Image();  
    img.onload = () => resolve(img);  
    img.onerror = (error) => reject(error);  
    img.src = url;  
  });  
}  
  
// 使用async/await来异步加载并显示图片尺寸  
async function displayImageSizes(urls) {  
  for (const url of urls) {  
    try {  
      const img = await loadImg(url);  
      console.log(`Width: ${img.width}, Height: ${img.height}`);  
      // 可以在这里将图片添加到DOM中  
    } catch (error) {  
      console.error(`Failed to load image at ${url}:`, error);  
    }  
  }  
}  
  
// 调用函数  
displayImageSizes(['https://example.com/image1.jpg', 'https://example.com/image2.jpg']);

在这个示例中,loadImg函数返回一个Promise对象,该对象在图片加载成功时解析为图片对象,在加载失败时拒绝并传递错误信息。displayImageSizes函数是一个async函数,它遍历一个图片URL数组,对每个URL调用loadImg函数,并使用await等待Promise解决,然后打印出图片的尺寸。如果加载过程中发生错误,则捕获并打印错误信息。

5.异常处理

在JavaScript中,异常处理主要通过try...catch语句来实现。在async/await中,异常可以通过try...catch直接捕获。

实例:使用try...catch捕获Promise异常

	async function fetchDataWithError() {
	    try {
	        await new Promise((resolve, reject) => {
	            reject(new Error('数据加载失败'));
	        });
	    } catch (error) {
	        console.error(error.message); // 输出:数据加载失败
	    }
	}

	fetchDataWithError();

6. 插件机制

虽然JavaScript本身没有内置的插件系统,但可以通过模块化、接口定义和动态加载等方式模拟插件机制。

实例:简单的插件系统模拟

	// 插件接口
	interface Plugin {
	    execute(): void;
	}

	// 插件实现
	class GreetingPlugin implements Plugin {
	    execute() {
	        console.log('Hello, this is a plugin!');
	    }
	}

	// 插件加载器
	function loadPlugin(pluginClass: typeof Plugin) {
	    const plugin = new pluginClass();
	    if (plugin instanceof Plugin) {
	        plugin.execute();
	    }
	}

	// 使用插件
	loadPlugin(GreetingPlugin);
	// 输出:Hello, this is a plugin!

注意:上述插件接口和类型定义使用了TypeScript的语法,以更清晰地表达接口和类型约束。在纯JavaScript环境中,你需要通过文档或代码约定来确保插件实现了预期的接口。

持续补充中...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值