JavaScript 基础入门:从零开始学 JS

一、JavaScript 简介

JavaScript(简称 JS)是一种高级的、解释型的编程语言,由 Netscape 公司的 Brendan Eich 在 1995 年开发,最初命名为 LiveScript,后因与 Java 的合作关系而改名为 JavaScript。作为 Web 开发的三大核心技术之一(其他两个是 HTML 和 CSS),JavaScript 在现代网页中扮演着至关重要的角色。

JS 的主要特点包括:

  1. 客户端脚本语言:直接在用户的浏览器中执行,无需服务器处理
  2. 动态类型:变量类型在运行时确定
  3. 基于原型的面向对象特性
  4. 函数是一等公民
  5. 支持异步编程(通过 Promise 和 async/await)

与 HTML(超文本标记语言)负责页面结构、CSS(层叠样式表)负责页面样式不同,JavaScript 主要负责页面的行为和交互功能。典型的应用场景包括:

  • 表单验证:检查用户输入是否符合要求(如邮箱格式验证)
  • 动态内容更新:无需刷新页面即可加载新内容(如社交媒体信息流)
  • 动画效果:创建平滑的页面过渡和交互效果
  • 用户交互响应:处理点击、滚动等用户事件
  • 与后端 API 通信:通过 AJAX 或 Fetch API 获取数据

现代 JavaScript 已经发展出多个框架和库(如 React、Vue、Angular),使得开发复杂的前端应用变得更加高效。根据 Stack Overflow 2022 开发者调查,JavaScript 已连续10年成为最常用的编程语言。

示例代码展示基本的 DOM 操作:

// 获取页面元素
const button = document.getElementById('myButton');
// 添加点击事件监听器
button.addEventListener('click', function() {
    // 修改元素内容
    this.textContent = '已点击';
    // 改变样式
    this.style.backgroundColor = 'blue';
});

二、JavaScript 的引入方式

1. 内嵌式 JavaScript

内嵌式是最基础的 JavaScript 引入方式,直接在 HTML 文件中使用 <script> 标签包裹 JavaScript 代码。这种方式的优点是简单直接,适合快速测试和小型脚本。

典型应用场景

  • 快速原型开发
  • 小型网站或单页应用
  • 需要立即执行的初始化代码

示例代码详解

<!DOCTYPE html>
<html>
<head>
    <title>内嵌式JS</title>
    <script>
        // 这里是JavaScript代码
        function init() {
            alert("页面加载完成,这是内嵌式JavaScript");
            console.log("调试信息输出到控制台");
        }
        // 当DOM加载完成后执行
        document.addEventListener('DOMContentLoaded', init);
    </script>
</head>
<body>
    <!-- 页面内容 -->
</body>
</html>

特性说明

  • 代码会按照在HTML中的出现顺序执行
  • 可以放在<head><body>中,位置不同会影响执行时机
  • 适合少量脚本,但不利于代码维护和复用

2. 外链式 JavaScript

外链式是将 JavaScript 代码写在单独的.js文件中,然后在 HTML 中通过<script>标签引入。这是现代Web开发推荐的方式。

典型应用场景

  • 大中型项目开发
  • 需要复用的组件或库
  • 需要代码分离和模块化的项目

示例代码详解

HTML文件:

<!DOCTYPE html>
<html>
<head>
    <title>外链式JS</title>
    <!-- 推荐放在body结束前,避免阻塞渲染 -->
    <script src="assets/js/myScript.js" defer></script>
    <!-- 或者使用async属性实现异步加载 -->
</head>
<body>
    <!-- 页面内容 -->
</body>
</html>

myScript.js文件内容:

// 使用严格模式
'use strict';

// 定义模块
const App = {
    init: function() {
        alert("这是外链式JavaScript");
        this.bindEvents();
    },
    bindEvents: function() {
        // 事件绑定代码
    }
};

// 当DOM加载完成后初始化应用
document.addEventListener('DOMContentLoaded', function() {
    App.init();
});

最佳实践

  1. 文件命名要有意义,如main.jsapp.js
  2. 使用deferasync属性优化加载性能
  3. 合理组织目录结构,如js/assets/js/
  4. 考虑使用模块化开发工具(如Webpack、Rollup)

3. 行内式 JavaScript

行内式是将JavaScript代码直接写在HTML标签的事件属性中,这种方式虽然简单但不推荐大量使用。

典型应用场景

  • 快速测试某个事件处理
  • 简单的交互效果
  • 传统老式网站维护

示例代码详解

<!DOCTYPE html>
<html>
<head>
    <title>行内式JS</title>
    <style>
        .btn {
            padding: 10px 20px;
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <!-- 简单的事件处理 -->
    <button 
        class="btn" 
        onclick="alert('这是行内式JavaScript'); this.textContent='已点击'"
    >
        点击我
    </button>
    
    <!-- 表单验证示例 -->
    <form onsubmit="return validateForm()">
        <input type="text" id="username" required>
        <button type="submit">提交</button>
    </form>
    
    <script>
        // 可以配合使用的函数
        function validateForm() {
            const input = document.getElementById('username');
            if(input.value.length < 3) {
                alert('用户名至少3个字符');
                return false;
            }
            return true;
        }
    </script>
</body>
</html>

注意事项

  1. 这种方式使HTML和JavaScript高度耦合,不利于维护
  2. 违反了关注点分离(Separation of Concerns)原则
  3. 现代开发中推荐使用事件监听器而非行内事件
  4. 在React等框架中的"行内事件"实际上是语法糖,原理不同

三、变量

变量是用于存储数据的容器

在编程中,变量是存储数据的基本单元,相当于一个带标签的盒子,我们可以把数据放进去,需要时再取出来使用。JavaScript 提供了三种声明变量的方式:varletconst

1. 声明和赋值详解

1.1 使用 var 声明变量(ES5及之前的方式)

var 是 JavaScript 最早使用的变量声明方式,具有函数作用域:

// 1. 先声明后赋值
var a;    // 声明变量a,此时a的值为undefined
a = 10;   // 给变量a赋值为10

// 2. 声明同时赋值
var b = 20;  // 声明变量b并赋值为20

// 3. 变量提升现象
console.log(c); // 输出undefined,不会报错
var c = 30;

1.2 使用 let 声明变量(ES6新增)

let 是 ES6 引入的声明方式,具有块级作用域,解决了 var 的一些问题:

// 1. 基本用法
let x = 100;  // 声明并赋值

// 2. 块级作用域示例
{
    let y = 200;
    console.log(y); // 200
}
// console.log(y); // 报错,y不在作用域内

// 3. 不存在变量提升
console.log(z); // 报错
let z = 300;

1.3 使用 const 声明常量(ES6新增)

const 用于声明不可变的常量,必须初始化时就赋值:

// 1. 基本用法
const PI = 3.14159;  // 声明常量PI

// 2. 不可重新赋值
// PI = 3.14; // 报错,不能修改常量

// 3. 对于对象和数组
const colors = ['red', 'green'];
colors.push('blue'); // 允许,因为修改的是数组内容
// colors = ['yellow']; // 报错,不能重新赋值

2. 变量命名规则详解

JavaScript 变量命名需要遵循以下规则:

  1. 组成字符

    • 允许使用字母(a-z, A-Z)
    • 数字(0-9),但不能以数字开头
    • 下划线(_)
    • 美元符号($)
  2. 命名示例

    • 合法:userName, _count, $price, num1
    • 非法:1stPlace(数字开头),my-name(含连字符)
  3. 关键字限制

    • 不能使用 JavaScript 保留的关键字,如:
      // let if = 10;  // 报错,if是关键字
      // let for = 20; // 报错,for是关键字
      

  4. 大小写敏感

    • firstNameFirstName 是两个不同的变量
    • ageAGE 也是不同的变量
  5. 命名建议

    • 使用驼峰命名法(如 userAge
    • 常量通常全大写(如 MAX_SIZE
    • 使用有意义的名称(避免单字母变量名)

举例说明:

let userAge = 25;         // 正确
const MAX_USERS = 100;    // 正确
let $price = 99.9;        // 正确
// let 2ndPlace = "John"; // 错误:数字开头
// let first-name = "Tom";// 错误:包含连字符

四、数据类型

1. 基本数据类型

字符串(String)

字符串是表示文本的数据类型,可以用单引号('')、双引号("")或反引号(``)定义。ES6引入的模板字符串(反引号)支持多行文本和字符串插值。

let str1 = "Hello"; // 双引号
let str2 = 'World'; // 单引号
let str3 = `Hello ${str2}`; // 模板字符串,输出"Hello World"
let multiLine = `这是
多行
字符串`; // 支持换行

数字(Number)

JavaScript 只有一种数字类型,包括整数和浮点数,采用IEEE 754标准的64位双精度浮点数格式表示。

let num1 = 100;    // 整数
let num2 = 3.14;   // 浮点数
let num3 = 0.1 + 0.2; // 0.30000000000000004(浮点数精度问题)
let hex = 0xff;    // 十六进制
let oct = 0o10;    // 八进制
let bigNum = 1e6;  // 科学计数法,表示1000000

布尔值(Boolean)

布尔值表示逻辑实体,只有两个值:true和false。常用于条件判断。

let isTrue = true;    // 真值
let isFalse = false;  // 假值
let isGreater = 10 > 5; // 比较运算返回布尔值

空值(Null)

Null类型只有一个值null,表示一个空对象引用。通常用于表示变量有意为空值。

let nullValue = null; // 明确赋值为空
let element = document.getElementById('not-exist'); // 返回null

未定义(Undefined)

Undefined类型表示变量已声明但未赋值,或访问对象不存在的属性。

let undefinedValue; // 只声明未赋值
let obj = {};
console.log(obj.noProperty); // 访问不存在的属性返回undefined

Symbol(ES6新增)

Symbol表示唯一的、不可变的值,主要用作对象属性的标识符。

let sym1 = Symbol('description');
let sym2 = Symbol('description');
console.log(sym1 === sym2); // false,每个Symbol都是唯一的

BigInt(ES2020新增)

BigInt表示任意精度的整数,可以表示大于2^53的整数。

let bigInt = 9007199254740991n; // 末尾加n表示BigInt
let bigInt2 = BigInt("9007199254740991"); // 使用BigInt函数

2. 引用数据类型

对象(Object)

对象是键值对的集合,用于存储复杂数据结构。对象属性可以是基本类型值或其他对象。

let person = {
  name: "张三",
  age: 20,
  address: {
    city: "北京",
    street: "长安街"
  },
  sayHello: function() {
    console.log("你好!");
  }
};

// 访问属性
console.log(person.name); // 点表示法
console.log(person['age']); // 方括号表示法
person.sayHello(); // 调用方法

数组(Array)

数组是有序的值集合,可以包含不同类型的元素,长度动态可变。

let arr = [1, "two", true, {name: "数组元素"}];
arr.push(5); // 添加元素到末尾
arr.unshift(0); // 添加元素到开头
let first = arr[0]; // 通过索引访问
let length = arr.length; // 获取数组长度

// 数组遍历
arr.forEach(function(item) {
  console.log(item);
});

函数(Function)

函数是可执行代码块,可以接收参数并返回值。JavaScript中函数是一等公民。

// 函数声明
function add(a, b) {
  return a + b;
}

// 函数表达式
let multiply = function(x, y) {
  return x * y;
};

// 箭头函数(ES6)
let divide = (x, y) => x / y;

// 调用函数
let sum = add(5, 3); // 8
let product = multiply(4, 6); // 24
let quotient = divide(10, 2); // 5

其他引用类型

还包括Date、RegExp、Map、Set等特殊对象类型。

// Date对象
let now = new Date();
console.log(now.getFullYear());

// RegExp正则表达式
let pattern = /hello/i;
console.log(pattern.test("Hello World")); // true

// Map集合
let map = new Map();
map.set('name', '李四');
console.log(map.get('name'));

// Set集合
let set = new Set([1, 2, 3, 3, 4]);
console.log(set.size); // 4(自动去重)

3. 数据类型检测与转换

类型检测

// typeof运算符
console.log(typeof "Hello"); // "string"
console.log(typeof 100); // "number"
console.log(typeof true); // "boolean"
console.log(typeof null); // "object"(历史遗留问题)
console.log(typeof undefined); // "undefined"
console.log(typeof {name: "张三"}); // "object"
console.log(typeof [1, 2, 3]); // "object"(数组也是对象)
console.log(typeof function(){}); // "function"

// instanceof运算符(检测对象类型)
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(function(){} instanceof Function); // true

// Object.prototype.toString方法(更准确的类型检测)
console.log(Object.prototype.toString.call([])); // "[object Array]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"

类型转换

// 显式类型转换
let numStr = "123";
let num = Number(numStr); // 字符串转数字
let str = String(123); // 数字转字符串
let bool = Boolean(1); // 真值转换

// 隐式类型转换
let result = "5" + 2; // "52"(字符串拼接)
let sum = "5" - 2; // 3(数字运算)
let isTrue = !!1; // true(布尔转换)

// 特殊转换案例
console.log(Number("")); // 0
console.log(Number("123abc")); // NaN
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN

五、运算符

1. 算术运算符

JavaScript 提供了多种算术运算符用于数值计算,包括:

  • + 加法运算
  • - 减法运算
  • * 乘法运算
  • / 除法运算
  • % 取余运算(求模)
  • ++ 自增运算(分为前自增和后自增)
  • -- 自减运算(分为前自减和后自减)

示例代码

let a = 10;
let b = 5;

// 基本运算
console.log(a + b); // 15,加法运算
console.log(a - b); // 5,减法运算
console.log(a * b); // 50,乘法运算
console.log(a / b); // 2,除法运算
console.log(a % b); // 0,取余运算(10除以5余0)

// 自增运算(后自增)
a++; // 等价于 a = a + 1
console.log(a); // 11

// 自减运算(后自减)
b--; // 等价于 b = b - 1
console.log(b); // 4

// 前自增与前自减
let x = 5;
let y = ++x; // 先自增再赋值
console.log(y); // 6

let m = 5;
let n = m++; // 先赋值再自增
console.log(n); // 5
console.log(m); // 6

2. 赋值运算符

赋值运算符用于给变量赋值,包括:

  • = 基本赋值
  • += 加后赋值
  • -= 减后赋值
  • *= 乘后赋值
  • /= 除后赋值
  • %= 取余后赋值

示例代码

let c = 10; // 基本赋值

// 复合赋值运算
c += 5; // 相当于 c = c + 5,结果为15
console.log(c); // 15

c -= 3; // 相当于 c = c - 3,结果为12
console.log(c); // 12

c *= 2; // 相当于 c = c * 2,结果为24
console.log(c); // 24

c /= 4; // 相当于 c = c / 4,结果为6
console.log(c); // 6

c %= 2; // 相当于 c = c % 2,结果为0
console.log(c); // 0

// 字符串连接赋值
let str = "Hello";
str += " World"; // 相当于 str = str + " World"
console.log(str); // "Hello World"

3. 比较运算符

比较运算符用于比较两个值的大小或相等性,返回布尔值:

  • == 等于(会自动类型转换)
  • === 严格等于(不进行类型转换)
  • != 不等于
  • !== 严格不等于
  • > 大于
  • < 小于
  • >= 大于等于
  • <= 小于等于

示例代码

// 松散比较(会进行类型转换)
console.log(10 == "10"); // true,因为字符串"10"会被转换为数字10
console.log(1 == true); // true,true会被转换为1

// 严格比较(不进行类型转换)
console.log(10 === "10"); // false,类型不同
console.log(10 === 10); // true,值和类型都相同

// 数值比较
console.log(10 > 5); // true
console.log(10 < 5); // false
console.log(10 >= 10); // true
console.log(10 <= 9); // false

// 不等于比较
console.log(10 != "10"); // false,因为松散比较会转换类型
console.log(10 !== "10"); // true,因为严格比较类型不同

// 特殊比较
console.log(null == undefined); // true
console.log(null === undefined); // false
console.log(NaN == NaN); // false,NaN不等于任何值包括它自己

4. 逻辑运算符

逻辑运算符用于处理布尔值,包括:

  • && 逻辑与(两个操作数都为true时返回true)
  • || 逻辑或(至少一个操作数为true时返回true)
  • ! 逻辑非(反转操作数的布尔值)

示例代码

// 逻辑与
console.log(true && false); // false
console.log(true && true); // true
console.log(false && false); // false

// 逻辑或
console.log(true || false); // true
console.log(false || false); // false
console.log(true || true); // true

// 逻辑非
console.log(!true); // false
console.log(!false); // true

// 短路求值特性
let x = 10;
(x > 5) && console.log("x大于5"); // 会输出,因为第一个条件为true
(x < 5) && console.log("这不会输出"); // 不会输出,因为第一个条件为false

// 实际应用:条件赋值
let name = "";
let displayName = name || "匿名用户";
console.log(displayName); // "匿名用户"

let user = { name: "张三" };
let userName = user && user.name;
console.log(userName); // "张三"

六、流程控制

JavaScript 条件语句和循环语句详解

1. 条件语句

if 语句

if 语句是最基本的条件控制结构,用于根据条件执行不同的代码块。

let score = 80;

if (score >= 60) {
    console.log("及格了");  // 当score>=60时执行
} else {
    console.log("没及格");  // 当score<60时执行
}

应用场景:判断用户是否登录、表单验证、成绩评定等。

if-else if-else 语句

当需要判断多个条件时,可以使用多分支结构。

let grade = 85;

if (grade >= 90) {
    console.log("优秀");     // 90分以上
} else if (grade >= 80) {
    console.log("良好");     // 80-89分
} else if (grade >= 60) {
    console.log("及格");     // 60-79分
} else {
    console.log("不及格");   // 60分以下
}

执行流程:从上到下依次判断条件,当某个条件满足时执行对应代码块并退出整个判断结构。

switch 语句

switch 语句适用于多条件等值判断的场景,比多个if-else更清晰。

let day = 3;

switch (day) {
    case 1:
        console.log("星期一");
        break;  // 必须使用break退出switch结构
    case 2:
        console.log("星期二");
        break;
    case 3:
        console.log("星期三");  // 本例会输出这个结果
        break;
    default:
        console.log("其他星期");  // 当所有case都不匹配时执行
}

注意事项:

  1. 每个case后面必须加break,否则会继续执行下一个case
  2. default分支是可选的,用于处理未匹配的情况
  3. switch使用严格比较(===)

2. 循环语句

for 循环

for循环是最常用的循环结构,适合已知循环次数的场景。

for (let i = 0; i < 5; i++) {
    console.log(i); // 依次输出0、1、2、3、4
}

循环步骤:

  1. 初始化变量(let i = 0)
  2. 检查条件(i < 5)
  3. 执行循环体
  4. 更新变量(i++)
  5. 重复步骤2-4直到条件不满足

while 循环

while循环在不确定循环次数时使用,先判断条件再执行循环体。

let j = 0;
while (j < 5) {
    console.log(j); // 依次输出0、1、2、3、4
    j++;
}

应用场景:读取文件直到结束、处理用户输入直到满足条件等。

do-while 循环

do-while循环至少执行一次循环体,然后再判断条件。

let k = 0;
do {
    console.log(k); // 先输出0,然后输出1、2、3、4
    k++;
} while (k < 5);

特点:无论条件是否成立,循环体至少执行一次。适合需要先执行操作再检查条件的场景,如菜单显示和用户交互。

七、函数

函数的概念与重要性

函数是编程中的基本构建块,它是一段可重复使用的代码,用于执行特定的任务。合理使用函数可以带来多重好处:

  • 提高代码可维护性:将复杂逻辑分解为多个小函数,便于理解和修改
  • 增强代码复用性:避免重复代码,一处定义多处调用
  • 降低耦合度:函数间通过明确接口通信,减少相互依赖
  • 便于调试:可以单独测试每个函数的功能

1. 函数的声明和调用

在JavaScript中,函数可以通过多种方式声明和调用,每种方式都有其适用场景。

基本函数声明

// 声明一个名为sayHello的函数
// 它接受一个name参数,用于个性化问候
function sayHello(name) {
    // 使用console.log输出问候语
    // 字符串拼接可以使用+运算符
    console.log("Hello, " + name + "!");
    
    // 也可以使用ES6的模板字符串
    console.log(`Hello, ${name}!`);
}

// 调用函数并传入参数"张三"
sayHello("张三"); 
// 输出: 
// Hello, 张三!
// Hello, 张三!

// 再次调用传入不同参数
sayHello("李四"); 
// 输出: 
// Hello, 李四!
// Hello, 李四!

带多个参数的函数

// 声明一个包含多个参数的函数
// 参数可以是任意类型:字符串、数字、布尔值、对象等
function introduce(name, age, job) {
    // 使用模板字符串构建自我介绍
    console.log(`大家好,我是${name},今年${age}岁,职业是${job}。`);
    
    // 可以添加更多逻辑
    if (age < 18) {
        console.log("我还是个未成年人~");
    }
}

// 调用函数并传入三个参数
introduce("王五", 28, "工程师"); 
// 输出:
// 大家好,我是王五,今年28岁,职业是工程师。

introduce("小明", 16, "学生");
// 输出:
// 大家好,我是小明,今年16岁,职业是学生。
// 我还是个未成年人~

2. 函数的返回值

函数可以使用return语句返回一个值,这个值可以被其他代码使用。返回值可以是任何JavaScript数据类型。

简单返回值

// 声明一个求两数之和的函数
function sum(a, b) {
    // 返回a和b的和
    // return语句会立即结束函数执行
    return a + b;
    
    // 这行代码永远不会执行
    console.log("This won't be printed");
}

// 调用函数并将返回值赋给result变量
let result = sum(3, 5);
console.log(result); // 输出8

// 可以直接在表达式中使用函数调用
console.log(sum(10, 20) * 2); // 输出60

// 也可以作为其他函数的参数
console.log(sum(sum(2,3), sum(4,5))); // 输出14

复杂返回值

// 返回复杂数据结构的函数
function createUser(name, age) {
    // 返回一个对象
    // 可以根据参数动态计算属性值
    return {
        username: name,
        userAge: age,
        isAdult: age >= 18,
        registeredAt: new Date(), // 添加注册时间
        // 方法也可以作为返回值
        greet: function() {
            return `Hi, I'm ${this.username}`;
        }
    };
}

let user = createUser("赵六", 25);
console.log(user); 
/* 输出:
{
  username: "赵六",
  userAge: 25,
  isAdult: true,
  registeredAt: [当前日期时间],
  greet: [Function: greet]
}
*/
console.log(user.greet()); // 输出: Hi, I'm 赵六

let teen = createUser("小红", 16);
console.log(teen.isAdult); // false

3. 函数表达式

函数也可以作为表达式赋值给变量,这种形式更加灵活。

匿名函数表达式

// 将匿名函数赋值给multiply变量
// 函数表达式不会被提升,必须先定义后使用
let multiply = function(a, b) {
    return a * b;
};

// 调用函数表达式
let product = multiply(4, 5);
console.log(product); // 输出20

// 立即调用的函数表达式(IIFE)
// 常用于创建独立作用域
let result = (function(x, y) {
    return x / y;
})(10, 2);
console.log(result); // 输出5

// 带参数的IIFE
(function(config) {
    console.log(`App starting with ${config.env} mode`);
})({ env: 'development', debug: true });

箭头函数(ES6)

// 使用箭头函数简化语法
// 当只有一个参数和一条语句时最简洁
let square = x => x * x;
console.log(square(5)); // 输出25

// 多参数箭头函数
// 需要括号包裹参数
let greet = (name, time) => {
    return `Good ${time}, ${name}!`;
};
console.log(greet("张三", "morning")); // 输出"Good morning, 张三!"

// 箭头函数与this绑定
const counter = {
    count: 0,
    increment: function() {
        setInterval(() => {
            // 箭头函数不绑定自己的this,继承自外围
            this.count++;
            console.log(this.count);
        }, 1000);
    }
};
counter.increment();

实际应用场景

数据处理

// 格式化货币金额
function formatCurrency(amount) {
    // 添加货币符号并保留两位小数
    return "¥" + amount.toFixed(2);
}
console.log(formatCurrency(29.9)); // 输出"¥29.90"
console.log(formatCurrency(123.456)); // 输出"¥123.46"

// 更复杂的格式化函数
function formatPrice(amount, currency = '¥', decimal = 2) {
    const formatted = amount.toFixed(decimal);
    if (currency === '¥') {
        return currency + formatted;
    } else {
        return formatted + ' ' + currency;
    }
}
console.log(formatPrice(29.9)); // "¥29.90"
console.log(formatPrice(99.99, '$')); // "99.99 $"

表单验证

// 验证邮箱格式
function validateEmail(email) {
    // 使用正则表达式验证基本邮箱格式
    const re = /\S+@\S+\.\S+/;
    return re.test(email);
}
console.log(validateEmail("test@example.com")); // true
console.log(validateEmail("invalid.email")); // false

// 更全面的验证函数
function validateForm(data) {
    const errors = {};
    
    if (!data.username) {
        errors.username = '用户名不能为空';
    }
    
    if (!validateEmail(data.email)) {
        errors.email = '邮箱格式不正确';
    }
    
    if (data.password.length < 6) {
        errors.password = '密码至少需要6个字符';
    }
    
    return Object.keys(errors).length === 0 ? null : errors;
}

const formData = {
    username: '张三',
    email: 'test@',
    password: '123'
};
console.log(validateForm(formData));
/* 输出:
{
  email: '邮箱格式不正确',
  password: '密码至少需要6个字符'
}
*/

业务逻辑封装

// 计算订单总金额
function calculateTotal(items, taxRate) {
    // 计算小计
    let subtotal = items.reduce((sum, item) => sum + item.price, 0);
    
    // 计算税费
    const tax = subtotal * taxRate;
    
    // 返回包含明细的对象
    return {
        subtotal: subtotal,
        tax: tax,
        total: subtotal + tax,
        itemsCount: items.length
    };
}

const cartItems = [
    { id: 1, name: '商品A', price: 100 },
    { id: 2, name: '商品B', price: 200 },
    { id: 3, name: '商品C', price: 150 }
];

const orderTotal = calculateTotal(cartItems, 0.1);
console.log(orderTotal);
/* 输出:
{
  subtotal: 450,
  tax: 45,
  total: 495,
  itemsCount: 3
}
*/

// 更复杂的业务逻辑函数
function processOrder(order, paymentMethod) {
    // 验证订单
    if (!order.items || order.items.length === 0) {
        throw new Error('订单中没有商品');
    }
    
    // 计算总金额
    const total = calculateTotal(order.items, order.taxRate || 0.1);
    
    // 处理支付
    const paymentResult = processPayment(total.total, paymentMethod);
    
    // 记录订单
    const orderRecord = createOrderRecord(order, total, paymentResult);
    
    // 发送确认邮件
    sendConfirmationEmail(order.customerEmail, orderRecord);
    
    return orderRecord;
}

通过合理使用函数,可以使代码更加模块化、易于维护和测试。

八、数组

数组基础概念

数组是编程中最常用的数据结构之一,它是一种用于存储多个值的有序集合。数组中的每个元素都有一个对应的索引(从0开始的整数),通过这个索引可以快速访问或修改特定位置的元素。数组在内存中是连续存储的,这使得它的访问效率非常高。

数组的特点

  1. 有序集合:数组中的元素按照插入顺序排列
  2. 索引访问:每个元素都有对应的从0开始的整数索引
  3. 连续内存:数组元素在内存中是连续存储的
  4. 动态大小:在JavaScript中数组长度可以动态变化
  5. 混合类型:可以存储不同类型的数据

数组的创建方式

1. 字面量方式(推荐)

// 创建数字数组
let numbers = [1, 2, 3, 4, 5]; 

// 创建字符串数组
let fruits = ['apple', 'banana', 'orange'];

// 创建混合类型数组
let mixed = [1, 'text', true, null, {name: 'John'}];

// 创建空数组
let empty = [];

2. 构造函数方式

// 创建包含元素的数组
let arr = new Array(6, 7, 8, 9, 10);

// 创建指定长度的空数组
let emptyArr = new Array(5); // 长度为5,元素都是undefined

// 创建单元素数组时要特别注意
let single = new Array(5); // 创建的是长度为5的空数组
let singleCorrect = [5]; // 这才是包含数字5的数组

数组的访问和修改

let products = ['手机', '电脑', '平板', '耳机', '鼠标'];

// 访问元素
console.log(products[0]); // 输出:"手机"
console.log(products[2]); // 输出:"平板"

// 访问不存在的索引
console.log(products[10]); // 输出:undefined

// 修改元素
products[1] = '笔记本电脑'; // 修改第二个元素
products[3] = '蓝牙耳机'; // 修改第四个元素

// 添加新元素
products[5] = '键盘'; // 添加新元素
products[10] = '显示器'; // 这会创建空位(empty items)

console.log(products); 
// 输出:['手机', '笔记本电脑', '平板', '蓝牙耳机', '鼠标', '键盘', 空 × 4, '显示器']

数组的常用方法和属性

1. 添加/删除元素

// 初始化数组
let tasks = ['学习', '运动'];

// push() - 末尾添加
tasks.push('阅读'); // 添加单个
tasks.push('购物', '做饭'); // 添加多个

// pop() - 末尾删除
let lastTask = tasks.pop(); // 删除并返回'做饭'

// unshift() - 开头添加
tasks.unshift('起床'); // 添加单个
tasks.unshift('刷牙', '洗脸'); // 添加多个

// shift() - 开头删除
let firstTask = tasks.shift(); // 删除并返回'刷牙'

console.log(tasks); // 输出:['洗脸', '起床', '学习', '运动', '阅读']

2. 数组长度

let colors = ['red', 'green', 'blue'];

// 获取长度
console.log(colors.length); // 输出:3

// 修改长度
colors.length = 5; // 扩展数组
console.log(colors); // 输出:['red', 'green', 'blue', empty × 2]

colors.length = 2; // 截断数组
console.log(colors); // 输出:['red', 'green']

// 清空数组
colors.length = 0;
console.log(colors); // 输出:[]

3. 其他常用方法

// 初始化数组
let numbers = [1, 2, 3, 4, 5];

// slice() - 提取子数组
let subArr = numbers.slice(1, 4); // [2, 3, 4]
let lastTwo = numbers.slice(-2); // [4, 5]

// splice() - 修改数组
// 删除
numbers.splice(2, 1); // 从索引2开始删除1个 → [1, 2, 4, 5]
// 插入
numbers.splice(2, 0, 3); // 在索引2插入3 → [1, 2, 3, 4, 5]
// 替换
numbers.splice(1, 2, 'a', 'b'); // [1, 'a', 'b', 4, 5]

// concat() - 合并数组
let arr1 = [1, 2];
let arr2 = [3, 4];
let combined = arr1.concat(arr2, [5, 6]); // [1, 2, 3, 4, 5, 6]

实际应用示例

示例1:购物车商品管理

// 初始化购物车
let cart = [];

// 添加商品
function addProduct(id, name, price) {
    cart.push({id, name, price});
    console.log(`已添加: ${name}`);
}

// 移除商品
function removeProduct(index) {
    if (index >= 0 && index < cart.length) {
        let removed = cart.splice(index, 1);
        console.log(`已移除: ${removed[0].name}`);
    } else {
        console.log('无效的索引');
    }
}

// 计算总价
function calculateTotal() {
    return cart.reduce((total, item) => total + item.price, 0);
}

// 使用示例
addProduct(1, 'iPhone 13', 5999);
addProduct(2, 'AirPods Pro', 1499);
addProduct(3, 'MacBook Pro', 12999);

console.log('当前购物车:');
cart.forEach((item, index) => {
    console.log(`${index + 1}. ${item.name} - ¥${item.price}`);
});

removeProduct(1); // 移除AirPods Pro

console.log(`总价: ¥${calculateTotal()}`);

示例2:成绩处理系统

// 初始化成绩数组
let scores = [85, 90, 78, 92, 88];

// 计算统计信息
function calculateStats(scores) {
    if (scores.length === 0) return null;
    
    // 计算总分和平均分
    let sum = scores.reduce((a, b) => a + b, 0);
    let average = sum / scores.length;
    
    // 计算最高分和最低分
    let max = Math.max(...scores);
    let min = Math.min(...scores);
    
    // 找出不及格的成绩
    let failed = scores.filter(score => score < 60);
    
    return {
        count: scores.length,
        sum,
        average: average.toFixed(2),
        max,
        min,
        failedCount: failed.length,
        failedScores: failed
    };
}

// 添加新成绩
function addScore(newScore) {
    scores.push(newScore);
    console.log(`已添加成绩: ${newScore}`);
}

// 删除最低分
function removeLowest() {
    let minIndex = scores.indexOf(Math.min(...scores));
    if (minIndex !== -1) {
        let removed = scores.splice(minIndex, 1);
        console.log(`已移除最低分: ${removed[0]}`);
    }
}

// 使用示例
console.log('初始成绩:', scores.join(', '));
addScore(95);
removeLowest();

let stats = calculateStats(scores);
console.log('统计信息:');
console.log(`数量: ${stats.count}`);
console.log(`总分: ${stats.sum}`);
console.log(`平均分: ${stats.average}`);
console.log(`最高分: ${stats.max}`);
console.log(`最低分: ${stats.min}`);
console.log(`不及格数量: ${stats.failedCount}`);

九、对象

对象的基本概念

对象是JavaScript中最重要的数据类型之一,它是一个无序的键值对集合,用于存储和表示复杂的数据结构。在对象中:

  1. 键(key):称为属性(property),必须是字符串或Symbol类型。属性名遵循标识符命名规则,但也可以使用引号包裹特殊字符作为属性名。

  2. 值(value):称为属性值,可以是任意JavaScript数据类型,包括:

    • 基本类型:字符串、数字、布尔值、null、undefined
    • 复杂类型:数组、函数、其他对象
    • 特殊类型:Symbol、BigInt
  3. 属性描述符:每个属性还有一组描述其特性的属性描述符,包括:

    • value:属性的值
    • writable:是否可修改(默认true)
    • enumerable:是否可枚举(默认true)
    • configurable:是否可配置(默认true)
    • get/set:访问器函数

例如,在浏览器环境中,document对象就是一个典型的JavaScript对象,它包含了大量属性和方法来操作DOM。

对象的创建方式

1. 对象字面量方式

这是最常用的创建对象方式,使用大括号{}语法,适合创建简单的、一次性的对象:

let person = {
    // 基本类型属性
    name: "张三",      // 字符串属性
    age: 20,          // 数字属性
    isStudent: true,  // 布尔属性
    
    // 复杂类型属性
    courses: ["数学", "语文", "英语"],  // 数组属性
    address: {        // 嵌套对象
        city: "北京",
        street: "中关村大街",
        getFullAddress() {
            return `${this.city} ${this.street}`
        }
    },
    
    // 方法属性
    sayHello: function() {
        console.log("Hello, I'm " + this.name);
    },
    
    // ES6简写方法
    introduce() {
        console.log(`我叫${this.name},今年${this.age}岁`);
    },
    
    // 计算属性名
    ["id_" + Math.random().toString(36).substr(2)]: "随机ID"
};

// 使用对象
person.sayHello();  // 输出: Hello, I'm 张三
console.log(person.address.getFullAddress());  // 输出: 北京 中关村大街

2. 使用Object构造函数

通过new Object()创建空对象,再动态添加属性,适合需要动态构建对象的场景:

let student = new Object();  // 创建一个空对象

// 添加属性
student.name = "李四";
student.age = 18;

// 添加方法
student.study = function(subject) {
    console.log(`${this.name}正在学习${subject}`);
};

// 添加嵌套对象
student.scores = new Object();
student.scores.math = 90;
student.scores.english = 85;

// 使用对象
student.study("数学");  // 输出: 李四正在学习数学
console.log(student.scores.math);  // 输出: 90

3. 使用Object.create()方法

可以指定原型对象创建新对象,适合需要继承的场景:

let animal = {
    type: "动物",
    makeSound() {
        console.log("发出声音");
    }
};

let dog = Object.create(animal);
dog.type = "狗";
dog.breed = "金毛";
dog.bark = function() {
    console.log("汪汪!");
};

dog.makeSound();  // 继承自animal: 发出声音
dog.bark();       // 输出: 汪汪!

4. 使用构造函数和new操作符

适合需要创建多个相似对象的场景:

function Car(make, model, year) {
    this.make = make;
    this.model = model;
    this.year = year;
    this.displayInfo = function() {
        console.log(`${this.year} ${this.make} ${this.model}`);
    };
}

let myCar = new Car("Toyota", "Camry", 2020);
myCar.displayInfo();  // 输出: 2020 Toyota Camry

对象属性的访问和操作

1. 点表示法访问

最常用的属性访问方式,简洁直观:

let person = {
    name: "张三",
    age: 20,
    "first-name": "张"  // 包含特殊字符的属性名
};

// 访问属性
console.log(person.name);  // 输出"张三"

// 修改属性
person.age = 21;  // 修改age属性
console.log(person.age);  // 输出21

// 添加新属性
person.gender = "男";
console.log(person.gender);  // 输出"男"

// 无法访问特殊字符属性
// console.log(person.first-name);  // 报错

2. 方括号表示法访问

当属性名包含特殊字符或需要动态计算时使用:

let person = {
    "first-name": "张",
    "last-name": "三",
    1: "数字作为属性名",
    [Symbol("id")]: "symbol作为属性名"
};

// 访问特殊字符属性
console.log(person["first-name"]);  // 输出"张"

// 动态访问属性
let propName = "last-name";
console.log(person[propName]);  // 输出"三"

// 数字属性名
console.log(person[1]);  // 输出"数字作为属性名"
console.log(person["1"]); // 同上

// Symbol属性名
let sym = Symbol("id");
console.log(person[sym]);  // 输出"symbol作为属性名"

// 添加计算属性
let dynamicProp = "score_" + Math.floor(Math.random() * 10);
person[dynamicProp] = 95;
console.log(person[dynamicProp]);

3. 属性操作

删除属性

使用delete操作符删除对象属性:

let person = {
    name: "张三",
    age: 20,
    gender: "男"
};

console.log("age" in person);  // 输出true
delete person.age;
console.log(person.age);       // 输出undefined
console.log("age" in person);  // 输出false

检查属性存在
let person = {name: "张三"};

// in操作符检查属性是否存在(包括原型链)
console.log("name" in person);      // true
console.log("toString" in person);  // true (继承自Object.prototype)

// hasOwnProperty检查自有属性
console.log(person.hasOwnProperty("name"));      // true
console.log(person.hasOwnProperty("toString"));  // false

遍历属性
let person = {
    name: "张三",
    age: 20,
    gender: "男"
};

// for...in循环(包含继承的可枚举属性)
for (let key in person) {
    console.log(key + ": " + person[key]);
}

// Object.keys()获取自有可枚举属性
let keys = Object.keys(person);
console.log(keys);  // ["name", "age", "gender"]

// Object.getOwnPropertyNames()获取所有自有属性
let allProps = Object.getOwnPropertyNames(person);
console.log(allProps);

// Object.getOwnPropertySymbols()获取Symbol属性
let symbols = Object.getOwnPropertySymbols(person);
console.log(symbols);

对象方法的定义和调用

1. 定义方法

方法本质上是一个函数类型的属性,有多种定义方式:

let calculator = {
    // 传统函数表达式
    add: function(a, b) {
        return a + b;
    },
    
    // ES6方法简写
    subtract(a, b) {
        return a - b;
    },
    
    // 箭头函数(注意this的指向问题)
    multiply: (a, b) => a * b,
    
    // 生成器方法
    *generateSequence(start, end) {
        for (let i = start; i <= end; i++) {
            yield i;
        }
    },
    
    // 异步方法
    async fetchData(url) {
        let response = await fetch(url);
        return await response.json();
    }
};

2. 调用方法

使用点表示法或方括号表示法调用对象方法:

let person = {
    name: "张三",
    age: 20,
    // 方法定义
    sayHello() {
        console.log(`你好,我是${this.name},今年${this.age}岁`);
    },
    // 另一个方法
    celebrateBirthday() {
        this.age++;
        console.log(`庆祝生日!现在${this.age}岁了`);
    }
};

// 直接调用
person.sayHello();              // 输出: 你好,我是张三,今年20岁

// 动态调用
let methodName = "sayHello";
person[methodName]();           // 同上

// 链式调用
person.celebrateBirthday().sayHello();  // 先增加年龄,再打招呼

3. 方法中的this

在对象方法中,this指向调用该方法的对象,但需要注意一些特殊情况:

let person = {
    name: "Alice",
    greet: function() {
        console.log("Hi, I'm " + this.name);
    },
    greetArrow: () => {
        console.log("Hi, I'm " + this.name);  // 箭头函数没有自己的this
    }
};

// 直接调用
person.greet();        // 输出: Hi, I'm Alice
person.greetArrow();   // 输出: Hi, I'm undefined (或全局name)

// 方法赋值给变量
let greetFunc = person.greet;
greetFunc();           // 输出: Hi, I'm undefined (this丢失)

// 使用bind绑定this
let boundGreet = person.greet.bind(person);
boundGreet();          // 输出: Hi, I'm Alice

// 作为回调函数
setTimeout(person.greet, 1000);             // this丢失
setTimeout(person.greet.bind(person), 1000); // 正确绑定

4. Getter和Setter

可以使用getter和setter定义访问器属性:

let user = {
    firstName: "张",
    lastName: "三",
    
    // getter
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    },
    
    // setter
    set fullName(value) {
        [this.firstName, this.lastName] = value.split(" ");
    }
};

console.log(user.fullName);  // 输出: 张 三
user.fullName = "李 四";
console.log(user.firstName); // 输出: 李
console.log(user.lastName);  // 输出: 四

对象的高级特性

1. 属性描述符

可以使用Object.defineProperty定义或修改属性特性:

let obj = {};

Object.defineProperty(obj, "readOnlyProp", {
    value: 42,
    writable: false,
    enumerable: true,
    configurable: false
});

console.log(obj.readOnlyProp);  // 42
obj.readOnlyProp = 100;        // 静默失败(严格模式下报错)
console.log(obj.readOnlyProp); // 仍然是42

// 获取属性描述符
let descriptor = Object.getOwnPropertyDescriptor(obj, "readOnlyProp");
console.log(descriptor);

2. 对象冻结

可以限制对象的修改:

let person = {
    name: "张三",
    age: 20
};

// 1. Object.preventExtensions: 禁止添加新属性
Object.preventExtensions(person);
person.gender = "男";  // 静默失败(严格模式下报错)

// 2. Object.seal: 禁止添加/删除属性
Object.seal(person);
delete person.name;    // 静默失败

// 3. Object.freeze: 完全冻结对象
Object.freeze(person);
person.age = 21;      // 静默失败

// 检查对象状态
console.log(Object.isExtensible(person));  // false
console.log(Object.isSealed(person));      // true
console.log(Object.isFrozen(person));       // true

3. 原型和继承

JavaScript使用原型链实现继承:

// 父类
function Animal(name) {
    this.name = name;
}
Animal.prototype.speak = function() {
    console.log(`${this.name} makes a noise.`);
};

// 子类
function Dog(name) {
    Animal.call(this, name);  // 调用父类构造函数
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
    console.log(`${this.name} barks.`);
};

let d = new Dog("Rex");
d.speak();  // 输出: Rex barks.

4. ES6类语法

ES6引入了更简洁的类语法:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    
    greet() {
        console.log(`Hello, I'm ${this.name}`);
    }
    
    static createAnonymous() {
        return new Person("Anonymous", 0);
    }
}

class Student extends Person {
    constructor(name, age, grade) {
        super(name, age);  // 调用父类构造函数
        this.grade = grade;
    }
    
    study() {
        console.log(`${this.name} is studying`);
    }
}

let student = new Student("张三", 20, "A");
student.greet();  // 输出: Hello, I'm 张三
student.study();  // 输出: 张三 is studying

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值