JavaScript进阶核心知识(1)

JavaScript 进阶

1. 作用域

作用域(Scope)是指程序中定义变量的区域。JavaScript 中主要有局部作用域和全局作用域。

1.1 局部作用域

局部作用域指的是在特定的代码块或函数中定义的变量,这些变量在外部不可访问。

1.1.1 函数作用域

函数作用域是指变量在函数内部声明,它们仅在函数内部可见。

function myFunction() {
    var x = 10; // x 仅在函数内部可见
    console.log(x); // 10
}
myFunction();
console.log(x); // Uncaught ReferenceError: x is not defined
1.1.2 块作用域

块作用域是指变量在特定的代码块(例如 iffor 语句)中定义,它们仅在该代码块内部可见。letconst 关键字用于声明块作用域变量。

if (true) {
    let y = 20; // y 仅在块内部可见
    console.log(y); // 20
}
console.log(y); // Uncaught ReferenceError: y is not defined

1.2 全局作用域

全局作用域是指在任何地方都可以访问的变量。这些变量在代码的任何部分都可见。

var z = 30; // 全局变量
function myFunction() {
    console.log(z); // 30
}
myFunction();
console.log(z); // 30

1.3 作用域链

当一个变量被引用时,JavaScript 引擎会从当前作用域开始查找变量,如果没有找到,则继续查找父级作用域,直到找到该变量或达到全局作用域。这种查找机制称为作用域链。

var globalVar = "global";

function outerFunction() {
    var outerVar = "outer";

    function innerFunction() {
        var innerVar = "inner";
        console.log(innerVar); // "inner"
        console.log(outerVar); // "outer"
        console.log(globalVar); // "global"
    }
    
    innerFunction();
}

outerFunction();

1.4 垃圾回收(Garbage Collection, GC)

垃圾回收是编程语言的一种特性,旨在自动释放不再使用的内存,使开发者无需手动管理内存分配和释放。

1.4.1 内存生命周期
  • 分配:当创建对象、变量时分配内存。
  • 使用:执行代码,读写分配的内存。
  • 释放:当对象不再被引用时,释放内存。
1.4.2 标记和清除算法(Mark-and-Sweep)
标记阶段

垃圾回收器会遍历所有的根对象(root objects,例如全局对象、局部变量、闭包中的变量等),并标记所有从根对象可达的对象。

清除阶段

在标记完成后,垃圾回收器会清除所有未被标记的对象,即这些对象不再被引用的对象,并释放其占用的内存。

1.4.3 引用计数(Reference Counting)
原理

每个对象维护一个引用计数,记录有多少地方引用了该对象。当引用计数变为0时,表示该对象不再被使用,可以被回收。

缺陷

引用计数无法处理循环引用的问题,例如两个对象相互引用,导致无法被回收。

1.4.4 垃圾回收策略
分代垃圾回收(Generational Garbage Collection)

将内存分为几代,如新生代(Young Generation)和老生代(Old Generation)。新生代对象通常生命周期较短,老生代对象生命周期较长。不同代使用不同的垃圾回收算法,提升效率。

增量垃圾回收(Incremental Garbage Collection)

将垃圾回收过程分成多个小步骤,避免一次性长时间暂停应用程序,提高应用程序的响应速度。

1.4.5 代码示例
标记和清除算法示例
let obj1 = { name: "Object 1" };
let obj2 = { name: "Object 2" };

obj1.linkedObj = obj2;
obj2.linkedObj = obj1;

// 手动断开引用,避免循环引用
obj1 = null;
obj2 = null;

// 垃圾回收器会在适当的时间回收obj1和obj2
引用计数示例
let objA = { name: "Object A" };
let objB = { name: "Object B" };

objA.ref = objB;
objB.ref = objA;

// 循环引用问题:即使设置为null,引用计数不为0,导致内存泄漏
objA = null;
objB = null;

// 垃圾回收器无法回收objA和objB,造成内存泄漏
分代垃圾回收示例
function createObjects() {
    let newObj1 = { name: "New Object 1" };
    let newObj2 = { name: "New Object 2" };

    // 对象会被分配到新生代
    return [newObj1, newObj2];
}

let objects = createObjects();

// 对象在短时间内可能会被回收
objects = null;

// 经过一定时间,对象会被移到老生代,进行更少的垃圾回收操作
1.4.6 总结

JavaScript的垃圾回收机制极大地简化了内存管理工作。标记和清除算法、引用计数和分代垃圾回收等策略共同作用,确保了内存的高效使用。理解这些机制有助于编写高效的代码,避免内存泄漏和性能问题。

1.5 闭包

闭包是指在函数内部定义的函数可以访问其外部函数的变量,即使外部函数已经执行完毕。

function outerFunction() {
    var outerVar = "outer";

    function innerFunction() {
        console.log(outerVar);
    }

    return innerFunction;
}

var myClosure = outerFunction();
myClosure(); // "outer"
总结:

1.怎么理解闭包?

  • 闭包 = 内层函数 + 外层函数的变量

2.闭包的作用?

  • 封闭数据,实现数据私有,外部也可以访问函数内部的变量
  • 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来

3.闭包可能引起的问题?

  • 内存泄漏

1.6 变量提升

变量提升是指变量声明会被提升到其所在作用域的顶部。需要注意的是,变量初始化不会被提升。

console.log(a); // undefined
var a = 5;
console.log(a); // 5

function hoistExample() {
    console.log(b); // undefined
    var b = 10;
    console.log(b); // 10
}
hoistExample();

总结

理解作用域、作用域链和闭包是掌握 JavaScript 的关键。变量提升是 JavaScript 的特性之一,需要注意它对代码行为的影响。

2. 函数

JavaScript 函数是实现代码复用的重要手段。

2.1 函数提升

函数声明会被提升到作用域的顶部,这意味着可以在函数声明之前调用它。

sayHello();

function sayHello() {
    console.log("Hello!");
}

2.2 函数参数

JavaScript 函数可以接受参数,参数可以有默认值,也可以通过动态参数和剩余参数实现灵活调用。

2.2.1 默认值

函数参数可以有默认值,当没有提供对应参数时会使用默认值。

function greet(name = "Guest") {
    console.log(`Hello, ${name}!`);
}
greet(); // Hello, Guest!
greet("John"); // Hello, John!
2.2.2 动态参数

通过 arguments 对象可以访问函数的所有传入参数。

function sum() {
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
        total += arguments[i];
    }
    return total;
}
console.log(sum(1, 2, 3)); // 6
2.2.3 剩余参数

剩余参数语法允许我们将不定数量的参数表示为一个数组。

function sum(...numbers) {
    return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6

作为前端老师,我会详细介绍JavaScript中的展开运算符(Spread Operator)的主要知识点,并提供相应的代码示例来帮助理解。

2.2.4展开运算符

展开运算符使用三个点 (...) 表示,可以用于对象和数组。它主要用于展开可迭代对象(如数组、字符串)或对象的属性。
在这里插入图片描述

1.在数组中的应用
(1)创建新数组

展开运算符可以将一个数组的元素展开到另一个数组中,创建一个新数组。

const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5, 6];

console.log(arr2); // 输出: [1, 2, 3, 4, 5, 6]
(2)数组合并

可以用展开运算符合并多个数组。

const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [5, 6];
const combinedArr = [...arr1, ...arr2, ...arr3];

console.log(combinedArr); // 输出: [1, 2, 3, 4, 5, 6]
(3)拷贝数组

展开运算符可以用于浅拷贝数组。

const originalArr = [1, 2, 3];
const copiedArr = [...originalArr];

console.log(copiedArr); // 输出: [1, 2, 3]
console.log(copiedArr === originalArr); // 输出: false
2. 在对象中的应用
(1)创建新对象

展开运算符可以将一个对象的属性展开到另一个对象中,创建一个新对象。

const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };

console.log(obj2); // 输出: { a: 1, b: 2, c: 3 }
(2)对象合并

可以用展开运算符合并多个对象。

const obj1 = { a: 1 };
const obj2 = { b: 2 };
const obj3 = { c: 3 };
const combinedObj = { ...obj1, ...obj2, ...obj3 };

console.log(combinedObj); // 输出: { a: 1, b: 2, c: 3 }
(3)对象浅拷贝

展开运算符可以用于浅拷贝对象。

const originalObj = { a: 1, b: 2 };
const copiedObj = { ...originalObj };

console.log(copiedObj); // 输出: { a: 1, b: 2 }
console.log(copiedObj === originalObj); // 输出: false
3. 在函数调用中的应用

展开运算符可以将数组元素作为函数参数传递。

function sum(x, y, z) {
    return x + y + z;
}

const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 输出: 6
4. 在字符串中的应用

展开运算符可以将字符串展开成字符数组。

const str = "hello";
const chars = [...str];

console.log(chars); // 输出: ['h', 'e', 'l', 'l', 'o']
5. 代码案例
案例1:数组去重

使用展开运算符和 Set 来去除数组中的重复元素。

const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)];

console.log(uniqueNumbers); // 输出: [1, 2, 3, 4, 5]
案例2:合并多个对象并添加新属性

合并多个对象并添加新属性,展示对象合并和新属性添加。

const user = { name: 'Alice', age: 25 };
const job = { title: 'Developer', company: 'Tech Co.' };
const additionalInfo = { location: 'New York', active: true };

const userProfile = { ...user, ...job, ...additionalInfo, isAdmin: false };

console.log(userProfile);
// 输出: { name: 'Alice', age: 25, title: 'Developer', company: 'Tech Co.', location: 'New York', active: true, isAdmin: false }
案例3:复制并修改数组

复制一个数组并添加新元素,同时不修改原数组。

const originalArr = [1, 2, 3];
const modifiedArr = [...originalArr, 4, 5];

console.log(originalArr); // 输出: [1, 2, 3]
console.log(modifiedArr); // 输出: [1, 2, 3, 4, 5]

2.3 箭头函数

箭头函数是 ES6 引入的一种更简洁的函数语法。

2.3.1 箭头函数参数

箭头函数的参数和传统函数类似,但语法更简洁。

const add = (a, b) => a + b;
console.log(add(2, 3)); // 5
2.3.2 箭头函数 this

箭头函数没有自己的 this,它会捕获其所在上下文的 this 值。

function Person() {
    this.age = 0;

    setInterval(() => {
        this.age++;
        console.log(this.age);
    }, 1000);
}

let p = new Person();
// 输出 1, 2, 3, ...

总结

掌握函数提升、函数参数(包括默认值、动态参数和剩余参数)、箭头函数及其 this 绑定特性,可以提升代码的简洁性和可维护性。

3. 解构赋值

解构赋值是 ES6 引入的语法,可以从数组或对象中提取值并赋值给变量。

3.1 数组解构

数组解构允许我们从数组中提取值,并将其赋值给变量。

let [a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2

let [x, , y] = [1, 2, 3];
console.log(x); // 1
console.log(y); // 3

3.2 对象解构

对象解构允许我们从对象中提取属性,并将其赋值给变量。

let { name, age } = { name: "John", age: 30 };
console.log(name); // John
console.log(age); // 30

let { name: n, age: a } = { name: "John", age: 30 };
console.log(n); // John
console.log(a); // 30

总结

解构赋值使得从数组和对象中提取数据更加简洁和直观,有助于提高代码的可读性和简洁性。

4. 综合案例

通过综合使用以上知识点,我们可以编写更简洁、可维护性更高的代码。

4.1 forEach 遍历数组

forEach 方法用于遍历数组中的每个元素。

const numbers = [1, 2, 3, 4, 5];
numbers.forEach(num => {
    console.log(num);
});
// 输出 1, 2, 3, 4, 5

4.2 filter 筛选数组

filter 方法创建一个新数组,其包含通过提供函数实现的测试的所有元素。

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]

综合案例:验证注册页面表单

<!DOCTYPE html>
<html>
<head>
    <title>Registration Form</title>
</head>
<body>
    <form id="registrationForm">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username"><br>
        <label for="email">Email:</label>
        <input type="email" id="email" name="email"><br>
        <label for="password">Password:</label>
        <input type="password" id="password" name="password"><br>
        <button type="submit">Register</button>
    </form>

    <script>
        document.getElementById('registrationForm').addEventListener('submit', function(event) {
            event.preventDefault();

            const username = document.getElementById('username').value;
            const email = document.getElementById('email').value;
            const password = document.getElementById('password').value;

            // 验证用户名
            const usernameRegex = /^[a-zA-Z0-9_]{3,10}$/;
            if (!usernameRegex.test(username)) {
                console.log("Invalid username");
                return;
            }

            // 验证邮箱
            const emailRegex = /^[

^\s@]+@[^\s@]+\.[^\s@]+$/;
            if (!emailRegex.test(email)) {
                console.log("Invalid email");
                return;
            }

            // 验证密码
            const passwordRegex = /^[a-zA-Z0-9]{6,20}$/;
            if (!passwordRegex.test(password)) {
                console.log("Invalid password");
                return;
            }

            console.log("Registration successful");
        });
    </script>
</body>
</html>

总结

通过以上内容的学习,理解并使用 JavaScript 中的作用域、函数、解构赋值等高级特性。继续加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值