您的位置:首页 > 教育 > 培训 > 企业seo排名服务_个人网站有哪些网站_学做电商需要多少钱_西安seo诊断

企业seo排名服务_个人网站有哪些网站_学做电商需要多少钱_西安seo诊断

2025/5/21 7:51:44 来源:https://blog.csdn.net/weixin_46225503/article/details/146382389  浏览:    关键词:企业seo排名服务_个人网站有哪些网站_学做电商需要多少钱_西安seo诊断
企业seo排名服务_个人网站有哪些网站_学做电商需要多少钱_西安seo诊断

学习来源:尚硅谷JavaScript基础&实战丨JS入门到精通全套完整版
笔记来源:在这位大佬的基础上添加了一些东西,欢迎大家支持原创,大佬太棒了:JavaScript |(二)JavaScript自定义对象及函数 | 尚硅谷JavaScript基础&实战

JavaScript |(二)JavaScript自定义对象及函数 | 尚硅谷JavaScript基础&实战

  • 🔴 自定义对象
    • 🟡 对象的分类
      • 1️⃣ 内建对象(Built-in Objects)
      • 2️⃣ 宿主对象(Host Objects)
      • 3️⃣ 自定义对象(User-defined Objects)
    • 🟡 对象基本操作
    • 🟡 对象的属性
      • 1️⃣ 属性名(Property Name)
        • 📌 示例 1:标准属性名
        • 📌 示例 2:特殊字符的属性名
        • 📌 示例 3:动态属性名
      • 2️⃣ 属性值(Property Value)
        • 📌 示例 4:属性值是不同的数据类型
    • 🟡 基本和引用数据类型
    • 🟡 对象字面量
  • 🔴 函数(function)
    • 🟡 函数创建及调用
      • 1️⃣ 形参(参数)
        • 📌 示例 1:函数定义时声明形参
      • 2️⃣ 实参(传入的值)
        • 📌 示例 2:调用函数时传入实参
      • 3️⃣ 实参的类型不会被检查
        • 📌 示例 3:传入错误类型的参数
      • 4️⃣ 函数的实参可以是任意数据类型
        • 📌 示例 4:传递不同类型的实参
      • 5️⃣ 多余的实参不会被赋值
        • 📌 示例 5:多余的实参
      • 6️⃣ 少于形参的数量,未传入的参数是 `undefined`
        • 📌 示例 6:少于形参数量
    • 🟡 函数的返回值
    • 🟡 立即执行函数
    • 🟡 函数——对象的方法
    • 🟡 变量提升(Hoisting)
      • 🎯 变量的声明提前(Hoisting for Variables)
        • 📌 示例 1:`var` 声明变量会提升
        • 📌 示例 2:不使用 `var` 的变量不会被提升
        • 📌 示例 3:`let` 和 `const` 不会被初始化
      • 🎯 函数的声明提前(Hoisting for Functions)
        • ✅ 规则:
        • 📌 示例 4:函数声明会被提升
        • 📌 示例 5:函数表达式不会被提升
    • 🟡 作用域
      • 🎯 全局作用域
      • 🎯 函数作用域(Function Scope)
        • 📌 示例 1:函数作用域中的变量访问
        • 📌 示例 2:作用域链
        • 📌 示例 3:函数中的变量声明提升
        • 📌 示例 4:函数中不使用 `var` 声明的变量会变成全局变量
        • 📌 示例 5:使用 `window` 访问全局变量
    • 🟡 this
      • 🎯 `this` 解析:不同调用方式的 `this` 指向
        • ✅ 1. 以「普通函数」调用,`this` 指向 `window`
        • ✅ 2. 以「方法」调用,`this` 指向调用方法的对象
        • ✅ 3. 赋值后调用(丢失 `this` 绑定)
        • ✅ 4. `this` 在构造函数中指向新对象
        • ✅ 5. `this` 在箭头函数中继承外层作用域
        • ✅ 6. `call()`、`apply()` 和 `bind()` 改变 `this`
      • 🎯 `this` 绑定规则总结
    • 🟡 工厂动态创对象
    • 🟡 构造函数
    • 🟡 原型函数
    • 🟡 toString()方法
      • 1️⃣ 默认情况下,`toString()` 方法
      • 2️⃣ 覆盖 `toString()` 方法
      • 3️⃣ 在类的 `prototype` 中添加 `toString()`
      • 4️⃣ `Object.prototype.toString.call()` 识别数据类型
    • 🟡 垃圾回收(GC)
      • 🔄 JavaScript 垃圾回收(GC)机制
      • 1️⃣ JavaScript 内存管理
      • 2️⃣ JavaScript 的垃圾回收算法
        • 🔹(1)标记清除(Mark-and-Sweep)
        • 🔹(2)引用计数(Reference Counting)
      • 3️⃣ 如何优化垃圾回收?
        • ✅ 1. 手动释放对象
        • ✅ 2. 避免全局变量
        • ✅ 3. 使用 `WeakMap` / `WeakSet`
        • ✅ 4. 避免 DOM 泄漏
        • 💡 JavaScript GC 优化技巧

🔴 自定义对象

  • 在JS中来表示一个人的信息(name gender age):

    • var name = "孙悟空";
    • var gender = "男";
    • var age = 18;
  • 如果使用基本数据类型的数据,我们所创建的变量都是独立,不能成为一个整体。

  • 对象属于一种复合的数据类型,在对象中可以保存多个不同数据类型的属性。

🟡 对象的分类

在 JavaScript 中,对象可以分为 三大类,它们的作用和来源各不相同:


1️⃣ 内建对象(Built-in Objects)

这些对象是 ECMAScript(ES)标准 定义的,在任何 JavaScript 运行环境中都可以使用。它们是语言本身提供的工具,帮助开发者处理各种数据类型和功能。

🔹 常见的内建对象

  • 数学和数值处理MathNumberBigInt
  • 字符串操作String
  • 布尔值Boolean
  • 函数相关Function
  • 对象相关ObjectArraySetMap
  • 日期时间Date
  • 正则表达式RegExp
  • 错误处理ErrorSyntaxError
  • JSON 处理JSON

📌 示例

console.log(Math.PI); // 3.141592653589793
console.log(Number.isInteger(10.5)); // false
console.log("hello".toUpperCase()); // "HELLO"

2️⃣ 宿主对象(Host Objects)

宿主对象是 JavaScript 运行环境(例如 浏览器Node.js)提供的对象,它们不属于 JavaScript 语言本身,而是由宿主环境定义的。

🔹 在浏览器中的宿主对象

  • BOM(浏览器对象模型)
    • window(全局对象)
    • document(DOM 的入口)
    • navigator(浏览器信息)
    • location(地址栏信息)
    • history(浏览记录)
  • DOM(文档对象模型)
    • document.getElementById()
    • document.querySelector()
    • document.createElement()

📌 示例

console.log(window.innerWidth); // 浏览器窗口的宽度
console.log(document.title); // 获取网页标题
console.log(navigator.userAgent); // 获取浏览器的用户代理信息

3️⃣ 自定义对象(User-defined Objects)

自定义对象是 开发人员自己创建的,可以根据需求定义属性和方法。

🔹 创建自定义对象的方式

  1. 对象字面量
  2. 构造函数
  3. ES6 类(class)

📌 示例

// 方式 1:对象字面量
let person = {name: "Alice",age: 25,sayHello: function() {console.log(`Hello, my name is ${this.name}`);}
};
person.sayHello(); // 输出:Hello, my name is Alice// 方式 2:构造函数
function Car(brand, model) {this.brand = brand;this.model = model;
}
let myCar = new Car("Toyota", "Corolla");
console.log(myCar.brand); // "Toyota"// 方式 3:ES6 类
class Animal {constructor(name) {this.name = name;}speak() {console.log(`${this.name} makes a sound.`);}
}
let dog = new Animal("Dog");
dog.speak(); // "Dog makes a sound."

类型由谁提供?作用示例
内建对象JavaScript 语言本身提供基础的数据处理功能MathStringArray
宿主对象运行环境(浏览器/Node.js)提供与环境相关的功能windowdocumentconsole
自定义对象开发者根据需求创建对象let obj = {}class Person {}

📝 记住内建对象是标准的,宿主对象因环境而异,自定义对象由开发者自由创建! 🚀


🟡 对象基本操作

  • 创建对象var obj = new Object();

  • 添加属性对象.属性名 = 属性值

  • 读取属性对象.属性名

  • 修改属性对象.属性名 = 新值

  • 删除属性delete 对象.属性名

    <!DOCTYPE html>
    <html><head><meta charset="UTF-8"><title></title><script type="text/javascript">//创建对象/** 使用new关键字调用的函数,是构造函数constructor* 构造函数是专门用来创建对象的函数* 使用typeof检查一个对象时,会返回object*/var obj = new Object();/** 在对象中保存的值称为属性* 向对象添加属性* 语法:对象.属性名 = 属性值;*///向obj中添加一个name属性obj.name = "孙悟空";//向obj中添加一个gender属性obj.gender = "男";//向obj中添加一个age属性obj.age = 18;/** 读取对象中的属性* 语法:对象.属性名* 如果读取对象中没有的属性,不会报错而是会返回undefined*/console.log(obj.gender);console.log(obj.hello);/** 修改对象的属性值* 语法:对象.属性名 = 新值*/obj.name = "tom";/** 删除对象的属性* 语法:delete 对象.属性名*/delete obj.name;</script></head><body></body>
    </html>
    

    在这里插入图片描述

🟡 对象的属性

在 JavaScript 中,对象的 属性名属性值 具有很大的灵活性,具体如下:


1️⃣ 属性名(Property Name)

JavaScript 对象的 属性名 不强制遵守标识符命名规范,可以使用任意字符串(包括特殊字符、数字等),但推荐遵守标识符规范(如 camelCase 命名法)。

  • 使用 . 语法(仅适用于符合变量命名规范的属性)
  • 使用 [] 语法(适用于所有属性名,尤其是特殊字符或动态属性)

📌 示例 1:标准属性名
let person = {name: "Alice",age: 25
};console.log(person.name);  // "Alice"
console.log(person.age);   // 25
console.log(person["name"]); // 也可以使用 [] 访问

📌 示例 2:特殊字符的属性名
let obj = {"first-name": "John","123": "Number as key","@key": "Special character"
};// console.log(obj.first-name); // ❌ 报错,不能使用 . 访问
console.log(obj["first-name"]); // ✅ "John"
console.log(obj["123"]); // ✅ "Number as key"
console.log(obj["@key"]); // ✅ "Special character"

📌 示例 3:动态属性名
let prop = "dynamicKey";
let myObj = {[prop]: "Hello World"
};
console.log(myObj.dynamicKey); // "Hello World"
console.log(myObj["dynamicKey"]); // "Hello World"

✅ 解释:

  • [] 里可以放一个 变量,变量的值会作为属性名。

2️⃣ 属性值(Property Value)

JavaScript 对象的 属性值 可以是 任意数据类型,包括:

  1. 基本类型stringnumberboolean 等)
  2. 对象(嵌套对象)
  3. 数组
  4. 函数(方法)

📌 示例 4:属性值是不同的数据类型
let person = {name: "Alice",  // 字符串age: 25,  // 数字isStudent: false,  // 布尔值scores: [90, 85, 88],  // 数组address: { city: "New York", zip: "10001" },  // 对象greet: function() {  // 方法console.log("Hello, my name is " + this.name);}
};console.log(person.scores[1]); // 85
console.log(person.address.city); // "New York"
person.greet(); // "Hello, my name is Alice"

特点解释示例
属性名可以是任意字符串obj["first-name"] = "John";
. 访问只能用于 符合标识符规则 的属性名obj.name ✅,obj["first-name"] ✅,obj.first-name
[] 访问可用于任何属性名,支持动态属性obj["@key"] ✅,obj[variable]
属性值可以是 任何数据类型obj.age = 30; obj.list = [1, 2, 3]; obj.child = {}

最佳实践

  1. 属性名遵循命名规范(避免使用特殊字符)。
  2. 使用 [] 访问特殊或动态属性
  3. 属性值可以是任意类型,包括 数组、对象、方法,灵活使用。

  • in运算符:通过该运算符可以检查一个对象中是否含有指定的属性。如果有则返回true,没有则返回false。
    • 语法:"属性名" in 对象

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">var obj = new Object();//向对象中添加属性obj.name = "喜羊羊";// 使用特殊的属性名obj["123"] = 789;obj["nihao"] = "你好";var n = "nihao";console.log(obj["123"]);//输出789console.log(n);//输出你好obj.test = true;obj.test = null;obj.test = undefined;//创建一个对象var obj2 = new Object();obj2.name = "美羊羊";//将obj2设置为obj的属性obj.test = obj2;console.log(obj.test.name);//输出美羊羊console.log("name" in obj);//输出true</script></head><body></body>
</html>

🟡 基本和引用数据类型

  • 基本数据类型:String Number Boolean Null Undefined
  • 引用数据类型:Object
    • JS中的变量都是保存到栈内存中的,基本数据类型的值直接在栈内存中存储,值与值之间是独立存在,修改一个变量不会影响其他的变量
    • 对象是保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间,而变量保存的是对象的内存地址(对象的引用),如果两个变量保存的是同一个对象引用,当一个通过一个变量修改属性时,另一个也会受到影响。
  • 当比较两个基本数据类型的值时,就是比较值。而比较两个引用数据类型时,它是比较的对象的内存地址如果两个对象是一摸一样的,但是地址不同,它也会返回false
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">//基本数据类型的值直接在栈内存中存储,值与值之间是独立存在//修改一个变量不会影响其他的变量。var a = 123;var b = a;a++;console.log("a = "+a);//a=124console.log("b = "+b);//b=123//对象是保存到堆内存中的//如果两个变量保存的是同一个对象引用,当一个通过一个变量修改属性时,另一个也会受到影响。var obj = new Object();obj.name = "喜羊羊";var obj2 = obj;//修改obj的name属性obj.name = "美羊羊";console.log(obj.name);//美羊羊console.log(obj2.name);//美羊羊//设置obj2为nullobj2 = null;console.log(obj);//objectconsole.log(obj2);//null//比较基本数据类型的值,就是比较值var c = 10;var d = 10;console.log(c == d);//true//两个引用数据类型时,它是比较的对象的内存地址//如果两个对象是一摸一样的,但是地址不同,它也会返回falsevar obj3 = new Object();var obj4 = new Object();obj3.name = "懒羊羊";obj4.name = "懒羊羊";console.log(obj3 == obj4);//false</script></head><body></body>
</html>

🟡 对象字面量

  • 使用对象字面量,可以在创建对象时,直接指定对象中的属性
  • 语法:{属性名:属性值,属性名:属性值....}
  • 对象字面量的属性名可以加引号也可以不加,建议不加,如果要使用一些特殊的名字,则必须加引号
  • 属性名和属性值是一组一组的名值对结构,
  • 名和值之间使用:连接,多个名值对之间使用,隔开。如果一个属性之后没有其他的属性了,就不要写,
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">//使用对象字面量来创建一个对象var obj = {name:"猪八戒",age:13,gender:"男",test:{name:"沙僧"}};console.log(obj.test);</script></head><body></body>
</html>

在这里插入图片描述


🔴 函数(function)

  • 函数也是一个对象
  • 函数中可以封装一些功能(代码),在需要时可以执行这些功能(代码)
  • 函数中可以保存一些代码在需要的时候调用
  • 使用typeof检查一个函数对象时,会返回function

🟡 函数创建及调用

在 JavaScript 中,函数的形参(参数)实参(传入的值) 具有很大的灵活性,具体规则如下:


1️⃣ 形参(参数)

  • 函数定义 时,可以在 () 内指定一个或多个形参(形式参数)。
  • 多个形参 之间使用 , 隔开
  • 形参在函数内部相当于局部变量,但默认值是 undefined,直到调用函数时赋值。
📌 示例 1:函数定义时声明形参
function greet(name, age) {console.log("Hello, " + name + "! You are " + age + " years old.");
}

2️⃣ 实参(传入的值)

  • 调用函数 时,可以在 () 指定实参(实际参数)。
  • 实参将按照顺序赋值对应的形参
📌 示例 2:调用函数时传入实参
greet("Alice", 25); 
// 输出:Hello, Alice! You are 25 years old.

3️⃣ 实参的类型不会被检查

  • JavaScript 不会 在调用函数时检查实参的数据类型,所以要注意可能会传入非法参数
📌 示例 3:传入错误类型的参数
greet(123, "Hello"); 
// 输出:Hello, 123! You are Hello years old. ❌(可能导致逻辑错误)

解决方案:可以手动检查类型

function greet(name, age) {if (typeof name !== "string" || typeof age !== "number") {console.log("Invalid input!");return;}console.log("Hello, " + name + "! You are " + age + " years old.");
}
greet(123, "Hello"); // 输出:Invalid input!

4️⃣ 函数的实参可以是任意数据类型

  • 实参可以是 字符串、数字、对象、数组、函数等
  • 甚至可以传递函数作为参数(回调函数)。
📌 示例 4:传递不同类型的实参
function printData(data) {console.log("Received:", data);
}printData(42);         // 传递数字
printData("Hello");    // 传递字符串
printData([1, 2, 3]);  // 传递数组
printData({ key: "value" }); // 传递对象

5️⃣ 多余的实参不会被赋值

  • 如果传递的实参 超过 形参数量,多出的不会被赋值,但也不会报错。
📌 示例 5:多余的实参
function show(a, b) {console.log(a, b);
}show(1, 2, 3, 4); // 1 2 (3 和 4 被忽略)

6️⃣ 少于形参的数量,未传入的参数是 undefined

  • 如果传递的实参 少于 形参数量,未被赋值的形参默认为 undefined
📌 示例 6:少于形参数量
function show(a, b, c) {console.log(a, b, c);
}show(1, 2); // 1 2 undefined (c 没有赋值)

解决方案:可以使用默认参数

function show(a, b, c = "default value") {console.log(a, b, c);
}show(1, 2); // 1 2 "default value"

规则说明示例
形参在函数定义时声明形参是函数内部的局部变量function sum(a, b) {}
调用函数时传递实参实参赋值给形参sum(3, 5);
不会检查实参类型可能会导致错误sum("hello", 10);
实参可以是任何类型包括数组、对象、函数sum([1,2,3], {x:10});
多余的实参会被忽略解析器不会报错sum(1, 2, 3, 4);
少于形参的数量时,缺失的参数是 undefined可能导致 NaNsum(1);
可以使用默认参数避免 undefined 问题function sum(a, b=10) {}

最佳实践

  • 使用默认参数 避免 undefined
  • 检查参数类型,确保输入合法。
  • 尽量匹配实参与形参的数量,避免逻辑错误。

🟡 函数的返回值

  • 可以使用 return 来设置函数的返回值,语法:return 值
  • return后的值将会作为函数的执行结果返回,可以定义一个变量,来接收该结果。
  • 在函数中return后的语句都不会执行
  • 如果return语句后不跟任何值就相当于返回一个undefined,如果函数中不写return,则也会返回undefined。
  • return后可以跟任意类型的值。
<!DOCTYPE html>
<html><head><meta charset="utf-8" /><title></title><script type="text/javascript">// 创建一个函数,用来计算三个数的和function sum(a , b , c){var d = a + b + c;return d;}//调用函数//变量result的值就是函数的执行结果//函数返回什么result的值就是什么var result = sum(4,7,8);console.log("result = "+result);</script></head><body></body>
</html>

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">/** 返回值可以是任意的数据类型* 也可以是一个对象,也可以是一个函数*/function fun1(){//返回一个对象return {name:"沙和尚"};}var a = fun1();console.log("a = "+a);function fun2(){//在函数内部再声明一个函数function fun3(){alert("我是fun3");}//将fun3函数对象作为返回值返回return fun3;}a = fun2();console.log(a);a();</script></head><body></body>
</html>

在这里插入图片描述

在 JavaScript 中,当你执行:

console.log("a=" + a);

它等价于:

console.log("a=" + a.toString());

🔍 为什么 a 变成了 [object Object]

  1. a 是一个对象console.log(a) 直接打印 a,它会以对象的格式显示 {name: "沙和尚"}
  2. 字符串拼接时,a 需要转换为字符串,此时 JavaScript 调用了 a.toString() 方法
  3. 默认情况下,对象的 toString() 方法返回 "[object Object]",这是 Object.prototype.toString() 的默认行为。

✅ 如何让 a 显示 {name: '沙和尚'}
如果你想在字符串拼接时正确显示对象内容,可以使用 JSON.stringify(a)

console.log("a=" + JSON.stringify(a));

输出:

a={"name":"沙和尚"}

🛠 解决方案
方法 1️⃣:使用 JSON.stringify()

console.log("a=" + JSON.stringify(a)); // a={"name":"沙和尚"}

方法 2️⃣:手动定义 toString() 方法

var obj = {name: "沙和尚",toString: function() {return `{name: '${this.name}'}`;}
};
console.log("obj=" + obj); // obj={name: '沙和尚'}
  • 默认情况下,对象在字符串拼接时会调用 toString(),返回 [object Object]
  • 如果想让对象以 JSON 格式显示,可以用 JSON.stringify()
  • 可以自定义 toString() 方法来控制对象的输出格式

这样就可以更好地控制对象的输出格式啦!🚀


🟡 立即执行函数

立即执行函数:函数定义完,立即被调用,这种函数叫做立即执行函数。立即执行函数往往只会执行一次

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">(function(){alert("我是一个匿名函数~~~");})();(function(a,b){console.log("a = "+a);console.log("b = "+b);})(123,456);	</script></head><body></body>
</html>

在这里插入图片描述
在这里插入图片描述


🟡 函数——对象的方法

  • 函数也可以称为对象的属性,如果一个函数作为一个对象的属性保存,那么我们称这个函数是这个对象的方法
  • 调用这个函数就说调用对象的方法(method)。
  • 但是它只是名称上的区别没有其他的区别。
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">//创建一个对象var obj = new Object();//向对象中添加属性obj.name = "喜羊羊";obj.age = 18;//对象的属性值可以是任何的数据类型,也可以是个函数obj.sayName = function(){console.log(obj.name);};function fun(){console.log(obj.name);};//调用方法obj.sayName();//调用函数fun();/** 函数也可以称为对象的属性,* 	如果一个函数作为一个对象的属性保存,* 	那么我们称这个函数时这个对象的方法* 	调用这个函数就说调用对象的方法(method)*/var obj2 = {name:"美羊羊",age:18,sayName:function(){console.log(obj2.name);}};obj2.sayName();</script></head><body></body>
</html>

在这里插入图片描述


枚举对象中的属性

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">var obj = {name:"喜羊羊",age:18,gender:"男",address:"青青草原"}; //枚举对象中的属性,使用for ... in 语句/** 语法:* 	for(var 变量 in 对象){* 	*  }* * 	for...in语句 对象中有几个属性,循环体就会执行几次* 	每次执行时,会将对象中的一个属性的名字赋值给变量*/for(var n in obj){console.log("属性名:"+n);console.log("属性值:"+obj[n]);}</script></head><body></body>
</html>

在这里插入图片描述


🟡 变量提升(Hoisting)

在 JavaScript 中,变量提升(Hoisting) 是一个重要的概念,它会影响变量和函数的执行顺序。以下是详细解析:


🎯 变量的声明提前(Hoisting for Variables)

✅ 规则:

  1. 使用 var 关键字声明的变量,会在代码执行前提升到作用域的顶部,但不会赋值
  2. 不使用 varletconst 声明的变量(即隐式全局变量)不会被提升。
  3. letconst 声明的变量 也会被提升(但不会初始化,访问会报错)

📌 示例 1:var 声明变量会提升
console.log(a); // 输出:undefined (因为 a 被提升,但未赋值)
var a = 10;
console.log(a); // 输出:10

等价于

var a;         // 变量提升,但未赋值,默认是 undefined
console.log(a);
a = 10;
console.log(a);

📌 示例 2:不使用 var 的变量不会被提升
console.log(b); // ❌ ReferenceError: b is not defined
b = 20;

解释b 直接赋值,没有使用 var,不会提升。


📌 示例 3:letconst 不会被初始化
console.log(c); // ❌ ReferenceError: Cannot access 'c' before initialization
let c = 30;

解释c 虽然被提升,但不会自动初始化,访问时会报错。


🎯 函数的声明提前(Hoisting for Functions)

✅ 规则:
  1. 函数声明 function myFunc() {} —— 会被提升,所以可以在声明前调用
  2. 函数表达式 var myFunc = function() {} —— 不会被提升,所以不能在声明前调用

📌 示例 4:函数声明会被提升
sayHello(); // ✅ 可以调用,输出 "Hello, world!"function sayHello() {console.log("Hello, world!");
}

解释sayHello() 作为函数声明,会被提升到作用域顶部。


📌 示例 5:函数表达式不会被提升
sayHi(); // ❌ TypeError: sayHi is not a functionvar sayHi = function() {console.log("Hi!");
};

等价于

var sayHi;
sayHi(); // ❌ sayHi 变量已提升,但值是 undefined,调用 undefined 会报错
sayHi = function() {console.log("Hi!");
};

🎯 总结

提升规则是否提升?是否初始化?是否能在声明前调用?
var 变量✅ 变量名被提升❌ 不会初始化(默认 undefined❌ 访问时是 undefined
let / const 变量✅ 变量名被提升❌ 不会初始化(访问会报错)❌ 访问时报 ReferenceError
函数声明 function fn() {}✅ 提升整个函数✅ 会初始化✅ 可以在声明前调用
函数表达式 var fn = function() {}✅ 变量名被提升❌ 不会初始化(默认 undefined❌ 访问时报 TypeError

🎯 最佳实践
推荐使用 letconst,避免 var 变量提升带来的 undefined 问题。
尽量使用函数声明,如果用函数表达式,要确保先声明再调用。

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">console.log("a = "+a);var a = 123;fun();//函数声明,会被提前创建function fun(){console.log("我是一个fun函数");}//函数表达式,不会被提前创建var fun2 = function(){console.log("我是fun2函数");};fun2();	</script></head><body></body>
</html>

在这里插入图片描述

🟡 作用域

作用域:指一个变量的作用的范围。在JS中一共有两种作用域:

🎯 全局作用域

  • 直接编写在script标签中的JS代码都在全局作用域
  • 全局作用域在页面打开时创建在页面关闭时销毁
  • 在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建我们可以直接使用。
  • 在全局作用域中:
    • 创建的变量都会作为window对象的属性保存
    • 创建的函数都会作为window对象的方法保存
    • 全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问的到。

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">var a = 10;var b = 20;var c = "hello";console.log(window.c);function fun(){console.log("我是fun函数");}window.fun();</script></head><body></body>
</html>

在这里插入图片描述


🎯 函数作用域(Function Scope)

在 JavaScript 中,函数作用域 是一个重要的概念,它决定了变量的可见性和生命周期。以下是详细解析:


✅ 作用域规则:

  1. 每调用一次函数,就会创建一个新的函数作用域,执行完毕后,作用域销毁。
  2. 函数内部可以访问全局变量,但全局作用域无法访问函数内部的变量
  3. 作用域链(Scope Chain):在函数内部使用变量时,会先从当前作用域寻找,如果找不到,就逐层向上查找,直到全局作用域
  4. 变量声明提升(Hoisting) 也适用于函数作用域,var 声明的变量会被提升,但不会初始化。
  5. 如果在函数内部未使用 varletconst 声明变量,该变量会变成全局变量
  6. 函数的形参(参数)相当于在函数作用域内声明了变量

📌 示例 1:函数作用域中的变量访问
var globalVar = "I am global"; // 全局变量function myFunction() {var localVar = "I am local"; // 函数作用域变量console.log(globalVar); // ✅ 可以访问全局变量console.log(localVar);  // ✅ 可以访问函数内部变量
}myFunction();console.log(globalVar); // ✅ 可以访问全局变量
console.log(localVar);  // ❌ ReferenceError: localVar is not defined(函数作用域变量不能被外部访问)

📌 示例 2:作用域链
var name = "Global";function outer() {var name = "Outer";function inner() {var name = "Inner";console.log(name); // 输出:Inner(先在自身作用域找)}inner();console.log(name); // 输出:Outer(自身作用域有 name)
}outer();
console.log(name); // 输出:Global(访问全局变量)

解释

  • inner() 内部访问 name,它先查找自身作用域,找到 "Inner",所以打印 "Inner"
  • outer() 内部访问 name,找到 "Outer",所以打印 "Outer"
  • 全局访问 name,找到 "Global",所以打印 "Global"

📌 示例 3:函数中的变量声明提升
function test() {console.log(a); // 输出:undefined(变量声明提升,但未赋值)var a = 10;console.log(a); // 输出:10
}test();

等价于

function test() {var a;         // 变量声明被提升console.log(a); // undefineda = 10;console.log(a); // 10
}
  • var a 在函数执行前被提升,但 不会赋值,所以 console.log(a) 输出 undefined

📌 示例 4:函数中不使用 var 声明的变量会变成全局变量
function myFunc() {x = 100; // 没有使用 var/let/const 声明,会变成全局变量!
}myFunc();
console.log(x); // 输出:100(x 变成了全局变量)

解释

  • x = 100; 没有 varletconst,所以 JavaScript 自动创建了全局变量 x
  • 这是一种不推荐的做法,因为它会污染全局作用域。

最佳实践:始终使用 varletconst 来声明变量,避免意外创建全局变量。


📌 示例 5:使用 window 访问全局变量
var globalVar = "Hello, World!";function myFunction() {console.log(window.globalVar); // ✅ 可以通过 window 访问全局变量
}myFunction();

解释

  • 在浏览器环境下,全局变量会成为 window 对象的属性
  • 所以可以用 window.globalVar 来访问。

🎯 总结

作用域规则解释
函数执行时创建作用域,结束后销毁每次调用函数都会生成新的作用域,不会共享
函数内部可以访问全局变量,全局不能访问局部变量局部变量只在函数内部可见
作用域链变量在自身作用域找不到,会向上查找
变量声明提升var 声明的变量会被提升,但不会初始化
未声明变量会变成全局变量x = 100; 会创建全局变量(⚠️ 不推荐)
形参也是局部变量函数参数等同于在函数内部声明了变量
可以用 window 访问全局变量window.globalVar 访问全局变量

最佳实践

  1. 避免在函数中不使用 varletconst 声明变量,防止污染全局作用域。
  2. 使用 letconst 代替 var,避免变量提升带来的 undefined 问题。
  3. 尽量避免全局变量,尽量将变量限制在函数作用域内
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">//创建一个变量var a = 10;function fun(){var a = "我是fun函数中的变量a";var b = 20;console.log("a = "+a);function fun2(){console.log("a = "+window.a);}fun2();	}fun();// 没有全局变量的b,然后就报错了,后边就不运行了// console.log("b = "+b);function fun3(){//声明提前console.log(a);var a = 35;fun4();function fun4(){alert("I'm fun4");}}fun3();//在函数中,不适用var声明的变量都会成为全局变量function fun5(){//d没有使用var关键字,则会设置为全局变量d = 100;}fun5();//在全局输出dconsole.log("d = "+d);//定义形参就相当于在函数作用域中声明了变量function fun6(e=10){alert(e);}fun6();	</script></head><body></body>
</html>

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


🟡 this

在 JavaScript 中,this 是一个动态绑定的关键字,它的指向取决于函数的调用方式,而不是定义位置。让我们来详细解析:


🎯 this 解析:不同调用方式的 this 指向

✅ 1. 以「普通函数」调用,this 指向 window
function sayHello() {console.log(this); // 指向 window(在严格模式下是 undefined)
}sayHello(); // 浏览器环境下:window

解析

  • sayHello() 直接调用,属于普通函数调用
  • 非严格模式下,this 指向 window
  • 严格模式"use strict")下,this 变为 undefined

✅ 2. 以「方法」调用,this 指向调用方法的对象
var person = {name: "Alice",greet: function () {console.log(this.name);}
};person.greet(); // 输出:Alice(this 指向 person)

解析

  • person.greet()方法调用,所以 this 指向 person 对象。

✅ 3. 赋值后调用(丢失 this 绑定)
var obj = {name: "Bob",say: function () {console.log(this.name);}
};var func = obj.say; // 赋值给变量
func(); // 输出:undefined(在非严格模式下是 window)

解析

  • func() 作为普通函数调用,this 指向 window(或 undefined)。
  • 解决方案可以用 bind(),如下👇:
var boundFunc = obj.say.bind(obj);
boundFunc(); // 输出:Bob

🔹 bind(obj) 可以永久绑定 this


✅ 4. this 在构造函数中指向新对象
function Person(name) {this.name = name;
}var p1 = new Person("Charlie");
console.log(p1.name); // 输出:Charlie

解析

  • 使用 new 关键字,this 指向新创建的对象

✅ 5. this 在箭头函数中继承外层作用域
var obj = {name: "David",say: function () {var inner = () => {console.log(this.name);};inner();}
};obj.say(); // 输出:David

解析

  • 箭头函数不会创建自己的 this,它继承外层作用域(即 say 方法中的 this)。
  • 因为 sayobj 调用的,所以 this 指向 obj

✅ 6. call()apply()bind() 改变 this
var obj1 = { name: "Eve" };
var obj2 = { name: "Frank" };function sayHello() {console.log(this.name);
}sayHello.call(obj1); // Eve
sayHello.apply(obj2); // Frank

解析

  • call(obj)apply(obj) 强制把 this 绑定到 obj
  • bind(obj) 返回一个新的函数,可以永久绑定 this
var boundFunc = sayHello.bind(obj1);
boundFunc(); // Eve

🎯 this 绑定规则总结

调用方式this 指向
普通函数调用window(严格模式下 undefined
方法调用该方法所属的对象
构造函数调用新创建的对象
箭头函数继承外层作用域的 this
call(obj) / apply(obj)指定的 obj
bind(obj)绑定后 this 永远指向 obj

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">	function fun(){console.log(this.name);}var obj = {name:"喜羊羊",sayName:fun};var obj2 = {name:"美羊羊",sayName:fun};var name = "全局的name属性";//以函数形式调用,this是windowfun();//以方法的形式调用,this是调用方法的对象obj.sayName();obj2.sayName();</script></head><body></body>
</html>

在这里插入图片描述

🟡 工厂动态创对象

  • 使用工厂方法创建对象,通过该方法可以大批量的创建对象。
  • 使用工厂方法创建的对象,使用的构造函数都是Object,所以创建的对象都是Object这个类型,就导致我们无法区分出多种不同类型的对象。
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">//创建一个对象var obj = {name:"喜羊羊",age:18,gender:"男",sayName:function(){alert(this.name);}};//使用工厂方法创建对象,通过该方法可以大批量的创建对象function createPerson(name , age ,gender){//创建一个新的对象 var obj = new Object();//向对象中添加属性obj.name = name;obj.age = age;obj.gender = gender;obj.sayName = function(){alert(this.name);};//将新的对象返回return obj;}//用来创建狗的对象function createDog(name , age){var obj = new Object();obj.name = name;obj.age = age;obj.sayHello = function(){alert("汪汪~~");};return obj;}var obj2 = createPerson("美羊羊",16,"女");var obj3 = createPerson("懒羊羊",6,"男");var obj4 = createPerson("沸羊羊",18,"男");//创建一个狗的对象var dog = createDog("旺财",3);console.log(dog);console.log(obj4);</script></head><body></body>
</html>

在这里插入图片描述

🟡 构造函数

  • 构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写
  • 构造函数和普通函数的区别就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用。
  • 构造函数的执行流程:
    • 1.立刻创建一个新的对象
    • 2.将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象。
    • 3.逐行执行函数中的代码。
    • 4.将新建的对象作为返回值返回。
  • 使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类
  • 我们将通过一个构造函数创建的对象,称为是该类的实例
  • this的情况:
    • 1.当以函数的形式调用时,this是window
    • 2.当以方法的形式调用时,谁调用方法this就是谁
    • 3.当以构造函数的形式调用时,this就是新创建的那个对象
  • 使用instanceof可以检查一个对象是否是一个类的实例:语法:对象 instanceof 构造函数。如果是,则返回true,否则返回false。
  • 所有的对象都是Object的后代所以任何对象和Object左instanceof检查时都会返回true
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">//创建一个构造函数,专门用来创建Person对象的function Person(name , age , gender){this.name = name;this.age = age;this.gender = gender;this.sayName = function(){alert(this.name);};}function Dog(name , age){var obj = new Object();obj.name = name;obj.age = age;obj.sayHello = function(){alert("汪汪~~");};return obj;}var per = new Person("喜羊羊",18,"男");var per2 = new Person("美羊羊",16,"女");var per3 = new Person("慢羊羊",58,"男");var dog = new Dog();console.log(per instanceof Person);//trueconsole.log(dog instanceof Person);//false//所有的对象都是Object的后代,所以任何对象和Object左instanceof检查时都会返回trueconsole.log(dog instanceof Object);//true</script></head><body></body>
</html>

在Person构造函数中,为每一个对象都添加了一个sayName方法

  • 目前我们的方法是在构造函数内部创建的,也就是构造函数每执行一次就会创建一个新的sayName方法,也是所有实例的sayName都是唯一的。
  • 这样就导致了构造函数执行一次就会创建一个新的方法,执行10000次就会创建10000个新的方法,而10000个方法都是一模一样的,这是完全没有必要,完全可以使所有的对象共享同一个方法

解决方法一:将sayName方法在全局作用域中定义

  • 将函数定义在全局作用域,污染了全局作用域的命名空间。
  • 而且定义在全局作用域中也很不安全。

解决方法二(good):向原型中添加sayName方法

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">//创建一个Person构造函数function Person(name , age , gender){this.name = name;this.age = age;this.gender = gender;//向对象中添加一个方法//this.sayName = fun;}//将sayName方法在全局作用域中定义//将函数定义在全局作用域,污染了全局作用域的命名空间//而且定义在全局作用域中也很不安全/*function fun(){alert("Hello大家好,我是:"+this.name);};*///向原型中添加sayName方法Person.prototype.sayName = function(){alert("Hello大家好,我是:"+this.name);};//创建一个Person的实例var per = new Person("喜羊羊",18,"男");var per2 = new Person("蕉太狼",28,"男");per.sayName();per2.sayName();console.log(per.sayName == per2.sayName);//true</script></head><body></body>
</html>

在这里插入图片描述

在这里插入图片描述

🟡 原型函数

  • 原型prototype:我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype,这个属性对应着一个对象,这个对象就是我们所谓的原型对象。
  • 如果函数作为普通函数调用prototype没有任何作用
  • 当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过__proto__来访问该属性。
  • 原型对象(prototype)相当于一个公共区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一到原型对象中。
  • 当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如找到则直接使用。
  • 以后我们创建构造函数时,可以将这些对象共有的属性和方法统一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了。
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">function MyClass(){}//向MyClass的原型中添加属性aMyClass.prototype.a = 123;//向MyClass的原型中添加一个方法MyClass.prototype.sayHello = function(){alert("hello");};var mc = new MyClass();var mc2 = new MyClass();console.log(MyClass.prototype);console.log(mc2.__proto__ == MyClass.prototype);//向mc中添加a属性mc.a = "我是mc中的a";console.log(mc.a);console.log(mc2.a);mc.sayHello();</script></head><body></body>
</html>

在这里插入图片描述
在这里插入图片描述

  • 使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
  • 可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性,使用该方法只有当对象自身中含有属性时,才会返回true
  • 原型对象也是对象,所以它也有原型,当我们使用一个对象的属性或方法时:
    • 先在自身中寻找,自身中如果有,则直接使用。
    • 如果没有则去原型对象中寻找,如果原型对象中有,则使用。
    • 如果没有则去原型的原型中寻找,直到找到Object对象的原型。
    • Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">//创建一个构造函数function MyClass(){}//向MyClass的原型中添加一个name属性MyClass.prototype.name = "我是原型中的名字";var mc = new MyClass();mc.age = 18;console.log(mc.name);//使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回trueconsole.log("name" in mc);//可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性//使用该方法只有当对象自身中含有属性时,才会返回trueconsole.log(mc.hasOwnProperty("age"));console.log(mc.hasOwnProperty("hasOwnProperty"));/** 原型对象也是对象,所以它也有原型,* 	当我们使用一个对象的属性或方法时,会现在自身中寻找,* 		自身中如果有,则直接使用,* 		如果没有则去原型对象中寻找,如果原型对象中有,则使用,* 		如果没有则去原型的原型中寻找,直到找到Object对象的原型,* 		Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined*/console.log(mc.__proto__.hasOwnProperty("hasOwnProperty"));console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty"));// Object对象的原型没有原型console.log(mc.__proto__.__proto__.__proto__);</script></head><body></body>
</html>

在这里插入图片描述

🟡 toString()方法

在 JavaScript 中,toString()所有对象都继承自 Object.prototype 的一个方法,它的作用是将对象转换为字符串


1️⃣ 默认情况下,toString() 方法

如果我们直接打印一个对象,默认会调用 toString() 方法:

var obj = {};
console.log(obj.toString()); // "[object Object]"

解析

  • Object.prototype.toString() 默认返回 [object Object],表示这是一个普通对象

2️⃣ 覆盖 toString() 方法

如果我们希望自定义 toString() 的返回值,可以在类的原型上重写:

var person = {name: "Alice",age: 25,toString: function () {return `姓名:${this.name}, 年龄:${this.age}`;}
};console.log(person.toString()); // "姓名:Alice, 年龄:25"

解析

  • 由于 toString() 被重写,现在 console.log(person) 输出的是更具可读性的字符串

3️⃣ 在类的 prototype 中添加 toString()

在 ES6 类中,可以这样重写 toString()

class Person {constructor(name, age) {this.name = name;this.age = age;}toString() {return `姓名:${this.name}, 年龄:${this.age}`;}
}let p1 = new Person("Bob", 30);
console.log(p1.toString()); // "姓名:Bob, 年龄:30"

解析

  • 这里 Person 类内部重写了 toString(),实例 p1 调用 toString() 后返回自定义的字符串

4️⃣ Object.prototype.toString.call() 识别数据类型

Object.prototype.toString.call(value)判断数据类型的标准方法:

console.log(Object.prototype.toString.call(123));         // "[object Number]"
console.log(Object.prototype.toString.call("hello"));     // "[object String]"
console.log(Object.prototype.toString.call([]));         // "[object Array]"
console.log(Object.prototype.toString.call({}));         // "[object Object]"
console.log(Object.prototype.toString.call(null));       // "[object Null]"
console.log(Object.prototype.toString.call(undefined));  // "[object Undefined]"
console.log(Object.prototype.toString.call(function(){})); // "[object Function]"

解析

  • 这个方法比 typeof 更精确,可以区分 ArrayObject,还能正确识别 nullundefined

总结

用法说明
obj.toString()默认返回 [object Object]
obj.toString = function() {...}自定义对象的 toString()
class 内定义 toString()console.log(obj) 输出更友好的内容
Object.prototype.toString.call(value)用于判断数据类型

这样你就掌握了 toString() 的用法!🚀

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">function Person(name , age , gender){this.name = name;this.age = age;this.gender = gender;}//当我们直接在页面中打印一个对象时,事件上是输出的对象的toString()方法的返回值//如果我们希望在输出对象时不输出[object Object],可以为对象添加一个toString()方法//修改Person原型的toStringPerson.prototype.toString = function(){return "Person[name="+this.name+",age="+this.age+",gender="+this.gender+"]";};//创建一个Person实例var per = new Person("孙悟空",18,"男");var per2 = new Person("猪八戒",28,"男");var result = per.toString();console.log("result = " + result);console.log(per.__proto__.__proto__.hasOwnProperty("toString"));</script></head><body></body>
</html>

在这里插入图片描述

🟡 垃圾回收(GC)

  • 就像人生活的时间长了会产生垃圾一样,程序运行过程中也会产生垃圾,这些垃圾积攒过多以后,会导致程序运行的速度过慢,所以我们需要一个垃圾回收的机制,来处理程序运行过程中产生垃圾。
  • 当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,此时这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序运行变慢,所以这种垃圾必须进行清理。
  • 在JS中拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁,我们不需要也不能进行垃圾回收的操作,我们需要做的只是要将不再使用的对象设置null即可
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title></title><script type="text/javascript">var obj = new Object();//对对象进行各种操作。。。。obj = null;</script></head><body></body>
</html>

🔄 JavaScript 垃圾回收(GC)机制

JavaScript 是一门自动管理内存的语言,这意味着程序员不需要手动分配和释放内存。JavaScript 引擎会自动回收不再使用的对象,释放它们占用的内存,这个过程叫做垃圾回收(Garbage Collection, GC)


1️⃣ JavaScript 内存管理

在 JavaScript 中,内存管理的过程可以分为三个阶段

  1. 内存分配(Allocation):创建变量、对象、函数时,JS 引擎会自动为其分配内存。
  2. 内存使用(使用变量、调用函数):程序运行时,访问和修改变量、操作 DOM、执行函数等都会占用内存。
  3. 内存回收(Garbage Collection):当某些对象不再被使用时,JS 引擎会回收这些对象的内存。

2️⃣ JavaScript 的垃圾回收算法

目前主流的 JavaScript 引擎(如 V8)主要使用**标记清除(Mark-and-Sweep)**算法进行垃圾回收。

🔹(1)标记清除(Mark-and-Sweep)

原理

  1. 从根对象(Global Object)开始标记所有可达对象(即仍然可以访问的对象)。
  2. 扫描内存中的所有对象,如果某个对象没有被标记,就认为它是不可达的,进行回收。
  3. 释放这些不可达对象占用的内存

示例:

function demo() {let obj = { name: "Alice" }; // obj 被创建console.log(obj.name);
} // 函数执行完毕,obj 没有被引用,GC 会回收它
demo();

分析:

  • objdemo() 函数执行时被创建。
  • demo() 结束后,obj 变量被销毁,没有其他地方引用它。
  • GC 发现 obj 无法再被访问,就会回收其内存。

🔹(2)引用计数(Reference Counting)

原理

  • 每个对象都有一个引用计数,记录被多少个变量或属性引用
  • 如果对象的引用计数变成 0,则表示它不再被使用,可以被回收。

示例:

let obj1 = { name: "Bob" };
let obj2 = obj1; // obj1 和 obj2 都引用这个对象
obj1 = null; // obj2 仍然引用这个对象,无法回收
obj2 = null; // 现在对象引用数为 0,GC 进行回收

⚠️ 问题

  • 循环引用可能导致对象无法被回收,从而引发内存泄漏

循环引用示例(可能导致内存泄漏)

function circularReference() {let objA = {};let objB = {};objA.ref = objB; // objA 引用 objBobjB.ref = objA; // objB 引用 objAreturn "done";
}
circularReference(); // 由于相互引用,GC 可能无法回收 objA 和 objB

解决方案

  • 使用 WeakMapWeakSet,弱引用不会阻止对象被回收:
let weakMap = new WeakMap();
let obj = { name: "Charlie" };
weakMap.set(obj, "some value"); // obj 仍可被垃圾回收
obj = null; // GC 会回收 obj

3️⃣ 如何优化垃圾回收?

✅ 1. 手动释放对象
  • 如果对象不再使用,将其设为 null,帮助 GC 回收:
let user = { name: "David" };
user = null; // 释放 user 变量的引用
✅ 2. 避免全局变量
  • 全局变量不会自动回收,应尽量减少:
function foo() {let temp = "Hello"; // temp 在函数结束后被回收
}
foo();
✅ 3. 使用 WeakMap / WeakSet
  • 适用于存储可能随时消失的对象
let weakMap = new WeakMap();
let obj = { name: "Eve" };
weakMap.set(obj, "value");
obj = null; // obj 失去引用后,会被 GC 回收
✅ 4. 避免 DOM 泄漏
  • 移除不再使用的 DOM 事件监听
let btn = document.getElementById("myButton");
function handleClick() {console.log("Clicked");
}
btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick); // 及时移除

总结

GC 算法说明
标记清除主流垃圾回收机制,标记不可达对象并回收
引用计数维护引用次数,引用为 0 时回收(可能有循环引用问题)
💡 JavaScript GC 优化技巧
  1. 减少全局变量(局部变量更容易被回收)
  2. 手动释放不再使用的对象(赋值 null
  3. 使用 WeakMap / WeakSet(避免循环引用)
  4. 及时清理 DOM 事件监听(避免 DOM 泄漏)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com