当前位置: 首页 > news >正文

原型链与继承机制:继承背后的秘密

引言

JavaScript 的继承机制与其他传统面向对象语言截然不同,我们或许也曾经被 JavaScript 独特的基于原型的继承模型所困惑。

虽然 ES6 引入了 class 语法,但这只是语法糖,JavaScript 的本质仍是原型继承。理解这套机制不仅有助于解决开发中的疑难杂症,还能帮助我们编写更高效、更优雅的代码。

想象一下,如果把传统的类继承比作家族世系图,那么 JavaScript 的原型继承则更像是一条探索链—当你向一个对象询问它不知道的信息时,它会顺着这条链向上寻找答案。这种独特的机制赋予了 JavaScript 极大的灵活性,但也带来了理解上的挑战。

JavaScript 对象基础

在深入原型链之前,我们需要认清 JavaScript 中对象的本质:

const person = {name: "张三",sayHello() {console.log(`你好,我是${this.name}`);}
};

JavaScript 中的对象本质上是键值对的集合,我们通过属性和方法操作它们。这些属性可以随时添加、修改或删除,这与静态类型语言中对象结构固定的特性大相径庭。可以将 JavaScript 对象想象成一个动态的容器,可以在运行时调整其内容。

但与此同时,JavaScript 对象还有一个隐藏的连接,指向另一个对象,这就是原型。这种连接使得一个对象可以"继承"另一个对象的属性和方法,形成一种动态的继承关系。这就是 JavaScript 原型继承的核心。

原型链工作原理

原型的概念

每个 JavaScript 对象都有一个指向它原型的内部链接,这个原型对象同样有自己的原型,以此类推,形成一个"原型链",直到达到一个以 null 为原型的对象。

想象一个图书馆的借书系统:当你在自己的书架上找不到某本书时,你会去询问朋友是否有这本书;如果朋友也没有,你们会一起去图书馆查找。在 JavaScript 中,对象就像是你的书架,原型链就是这种层层询问的过程。

// 创建一个对象
const animal = {eat: function() {console.log("吃东西");}
};// 基于animal创建一个新对象
const dog = Object.create(animal);
dog.bark = function() {console.log("汪汪!");
};dog.eat(); // 输出 "吃东西"
dog.bark(); // 输出 "汪汪!"

在这个例子中,dog 对象本身并没有 eat 方法,但它通过原型链连接到 animal 对象,因此可以访问 animaleat 方法。这就是原型继承的核心思想—一个对象可以访问其原型链上任何对象的属性和方法。

当我们尝试访问 dog.eat() 时,JavaScript 引擎首先在 dog 对象本身查找 eat 方法。没找到后,会沿着原型链向上查找,在 animal 对象中找到并执行该方法。如果在整个原型链上都找不到请求的属性或方法,才会返回 undefined

Object.create() 方法是创建继承关系的一种直接方式,它创建一个新对象,并将参数指定的对象设置为新对象的原型。这是最简单直观的展示原型继承的方式。

proto 与 prototype

这里需要区分两个容易混淆的概念:

  1. __proto__:每个对象都有的内部属性,指向该对象的原型。在规范中,它被称为 [[Prototype]]__proto__ 只是大多数浏览器提供的访问这个内部属性的方式。现代 JavaScript 推荐使用 Object.getPrototypeOf()Object.setPrototypeOf() 来操作原型。

  2. prototype:函数对象特有的属性,当函数作为构造函数使用时,它的实例对象的 __proto__ 会指向这个 prototype 属性。这个属性是一个对象,包含所有实例共享的属性和方法。

这种区别可以用一个类比来理解:如果将构造函数比作一个工厂,那么 prototype 就是这个工厂的产品蓝图,而每个产品(实例)通过 __proto__ 指向这个蓝图,以便知道自己应该具备哪些特性。

function Person(name) {this.name = name;
}Person.prototype.sayHello = function() {console.log(`你好,我是${this.name}`);
};const person1 = new Person("李四");
person1.sayHello(); // 输出 "你好,我是李四"console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

这个例子展示了 JavaScript 中对象创建的全过程:

  1. 当我们使用 new Person("李四") 创建 person1 时,JavaScript 引擎创建了一个新对象,并将其 __proto__ 设置为 Person.prototype
  2. 然后执行 Person 函数,将新创建的对象作为 this 上下文,为其添加 name 属性。
  3. 由于 sayHello 方法定义在 Person.prototype 上,所有 Person 的实例都可以通过原型链访问这个方法。

上面的代码展示了一个完整的原型链:person1Person.prototypeObject.prototypenull。这就是 JavaScript 中对象原型的层级结构,也是所有对象继承的基础。

每次创建函数时,JavaScript 会自动为其创建一个 prototype 属性,指向一个只有 constructor 属性的对象,而这个 constructor 属性又指回函数本身,形成一个循环引用。这个设计使得实例可以通过 constructor 属性找到创建它的构造函数。

构造函数与继承模式

在 ES6 之前,JavaScript 实现继承的方式有多种,每种都有其优缺点。理解这些模式对于把握 JavaScript 面向对象的本质至关重要。

原型链继承

原型链继承是最基本的继承方式,直接将父类的实例赋值给子类的原型:

function Animal() {this.species = "动物";
}
Animal.prototype.eat = function() {console.log("吃东西");
};function Dog() {this.sound = "汪汪";
}
// 原型链继承
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog; // 修复constructor
Dog.prototype.bark = function() {console.log(this.sound);
};const myDog = new Dog();
myDog.eat(); // 吃东西
myDog.bark(); // 汪汪

在这个例子中,我们通过将 Animal 的实例赋值给 Dog.prototype,建立了原型链。这样,Dog 的实例就可以访问 Animal 原型上的方法。

需要注意的是,我们重新设置了 Dog.prototype.constructorDog,这是因为当我们将 new Animal() 赋值给 Dog.prototype 时,原有的 constructor 属性被覆盖了。修复 constructor 是为了保持正确的原型链关系,让实例能够通过 constructor 找到正确的构造函数。

缺点

  1. 原型中包含的引用类型属性会被所有实例共享。如果一个实例修改了共享的引用类型属性,所有实例都会受影响。
  2. 子类型在实例化时不能向父类型的构造函数传参,这限制了继承的灵活性。
  3. 由于原型链建立时调用了 new Animal(),导致 Animal 构造函数被执行,可能产生意外的副作用。

借用构造函数继承

为了解决原型链继承的问题,开发者提出了借用构造函数的方法:

function Animal(species) {this.species = species;this.foods = [];
}function Dog(species) {// 借用构造函数Animal.call(this, species);this.sound = "汪汪";
}const myDog = new Dog("犬科");
console.log(myDog.species); // 犬科

这种方式通过在子类构造函数中调用父类构造函数(使用 callapply 改变 this 指向),实现了实例属性的继承。这解决了原型链继承的两个主要问题:引用类型共享和无法传参。

Animal.call(this, species) 这行代码的作用是在 Dog 构造函数的上下文中执行 Animal 构造函数,相当于将 Animal 构造函数中的代码复制到 Dog 构造函数中执行,从而让 Dog 实例获得 Animal 的属性。

缺点

  1. 方法都在构造函数中定义,每次创建实例都会创建一次方法,无法实现函数复用。
  2. 父类原型上的方法无法被子类继承,因为这种方式只继承了构造函数中定义的属性和方法。

组合继承

组合继承结合了原型链继承和借用构造函数继承的优点:

function Animal(species) {this.species = species;this.foods = [];
}
Animal.prototype.eat = function(food) {this.foods.push(food);console.log(`${this.species}正在吃${food}`);
};function Dog(species, name) {Animal.call(this, species); // 借用构造函数this.name = name;
}
Dog.prototype = new Animal(); // 原型链继承
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {console.log(`${this.name}:汪汪!`);
};const myDog = new Dog("犬科", "小黑");
myDog.eat("骨头"); // 犬科正在吃骨头
myDog.bark(); // 小黑:汪汪!

组合继承的核心是:“使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承”。

这种方式的工作流程是:

  1. 在子类构造函数中,通过 Animal.call(this, species) 继承父类的实例属性。
  2. 通过 Dog.prototype = new Animal() 继承父类的原型方法。
  3. 重置子类原型的 constructor 属性,保持原型链的完整性。

组合继承是 JavaScript 中使用最广泛的继承模式之一,它避免了原型链继承和借用构造函数继承的缺点。

缺点

  1. 父类构造函数被调用两次:一次是在创建子类原型时 Dog.prototype = new Animal(),另一次是在子类构造函数内部 Animal.call(this, species)
  2. 这导致实例和原型上有重复的属性,虽然实例属性会覆盖原型属性,但仍然造成了内存浪费。

寄生组合继承

寄生组合继承是组合继承的优化版本,解决了父类构造函数被调用两次的问题:

function inheritPrototype(Child, Parent) {// 创建父类原型的副本const prototype = Object.create(Parent.prototype);// 将构造函数指向子类prototype.constructor = Child;// 将副本赋值给子类原型Child.prototype = prototype;
}function Animal(species) {this.species = species;this.foods = [];
}
Animal.prototype.eat = function(food) {this.foods.push(food);console.log(`${this.species}正在吃${food}`);
};function Dog(species, name) {Animal.call(this, species);this.name = name;
}inheritPrototype(Dog, Animal);Dog.prototype.bark = function() {console.log(`${this.name}:汪汪!`);
};const myDog = new Dog("犬科", "小黑");
myDog.eat("骨头"); // 犬科正在吃骨头
myDog.bark(); // 小黑:汪汪!

寄生组合继承的核心是:不必为了指定子类型的原型而调用父类的构造函数,我们所需要的无非就是父类原型的一个副本。

inheritPrototype 函数实现了这一点:

  1. 使用 Object.create(Parent.prototype) 创建一个对象,这个对象的原型是 Parent.prototype
  2. 设置这个对象的 constructor 属性为子类构造函数,确保正确的原型链。
  3. 将这个对象赋值给子类的 prototype,建立继承关系。

这种方式避免了组合继承中的重复调用父类构造函数,既能保证子类实例的属性独立,又能共享父类的方法,是 ES6 之前最理想的继承实现方式。

ES6 类语法与原型继承

ES6 引入了 class 语法,让 JavaScript 的面向对象编程更接近传统语言,但本质上仍是基于原型的:

class Animal {constructor(species) {this.species = species;this.foods = [];}eat(food) {this.foods.push(food);console.log(`${this.species}正在吃${food}`);}
}class Dog extends Animal {constructor(species, name) {super(species); // 调用父类构造函数this.name = name;}bark() {console.log(`${this.name}:汪汪!`);}
}const myDog = new Dog("犬科", "小黑");
myDog.eat("骨头"); // 犬科正在吃骨头
myDog.bark(); // 小黑:汪汪!

ES6 的类语法大大简化了继承的实现。extends 关键字用于创建子类,super 关键字用于调用父类的构造函数或方法。这种语法比传统的原型继承更加清晰易懂,尤其对于那些来自传统面向对象语言的开发者。

class 语法的主要特点包括:

  1. constructor 方法:类的构造函数,创建实例时自动调用。
  2. extends 关键字:建立子类与父类的继承关系。
  3. super 关键字:访问父类的构造函数或方法。
  4. 实例方法直接定义在类的内部,实际上是定义在原型上。
  5. 静态方法通过 static 关键字定义,直接挂在类本身上,而非原型。
  6. 所有代码都在严格模式下运行。

class 的本质

ES6 的 class 本质上是构造函数的语法糖。下面是 Babel 转译后的大致代码:

"use strict";function _inherits(subClass, superClass) {// 实现继承逻辑subClass.prototype = Object.create(superClass.prototype);subClass.prototype.constructor = subClass;// 设置 __proto__ 实现静态方法继承Object.setPrototypeOf(subClass, superClass);
}var Animal = function() {function Animal(species) {this.species = species;this.foods = [];}Animal.prototype.eat = function(food) {this.foods.push(food);console.log(this.species + "正在吃" + food);};return Animal;
}();var Dog = function(_Animal) {_inherits(Dog, Animal);function Dog(species, name) {// 调用父类构造函数var _this = _Animal.call(this, species) || this;_this.name = name;return _this;}Dog.prototype.bark = function() {console.log(this.name + ":汪汪!");};return Dog;
}(Animal);

通过 Babel 转译后的代码,我们可以清楚地看到 ES6 类语法背后的原理:

  1. class 被转换为构造函数。
  2. 类的实例方法被添加到构造函数的 prototype 上。
  3. 继承是通过 _inherits 函数实现的,这个函数使用了寄生组合继承的模式。
  4. Object.setPrototypeOf(subClass, superClass) 实现了静态方法和属性的继承。

可以看到,ES6 的类继承实际上是在做与寄生组合继承类似的事情,只是语法更简洁,并且添加了一些额外的特性和限制。

class 与传统原型继承的区别

尽管 class 的本质是原型继承,但它与传统的原型继承还是有一些重要区别:

  1. 提升行为不同:函数声明会提升,但类声明不会。这意味着必须先声明类,然后才能使用它。

  2. 严格模式:类内部的代码自动运行在严格模式下,无法选择退出。

  3. 不可枚举的方法:类定义的方法是不可枚举的,而手动添加到原型上的方法默认是可枚举的。

  4. 必须使用 new:类必须使用 new 调用,普通构造函数虽然也应该使用 new,但不强制。

  5. 静态属性和方法:类提供了 static 关键字来定义静态成员,传统原型继承则需要直接添加到构造函数上。

  6. 内置方法不可重写:类内部的特殊方法如 constructor 不能被属性赋值覆盖。

  7. 继承原生构造函数:ES6 类继承可以继承原生构造函数(如 Array、Error 等),这在 ES5 中很难实现。

继承中的常见陷阱

原型污染

原型污染是 JavaScript 中一个重要的安全隐患,它发生在恶意代码修改了对象原型的情况下:

const obj = {};
obj.__proto__.badMethod = function() {console.log("我被污染了!");
};const innocentObject = {};
innocentObject.badMethod(); // 我被污染了!

为什么这是危险的?因为 JavaScript 中几乎所有对象都继承自 Object.prototype,当这个原型被修改时,所有对象都会受到影响。这可能导致严重的安全问题,比如原型污染攻击可以注入恶意代码,或者修改应用的行为。

在实际开发中,这种情况可能通过不安全地合并用户输入数据和应用配置而发生:

const userInput = { "__proto__": { "isAdmin": true } };
const config = {};// 不安全的合并
for (let key in userInput) {config[key] = userInput[key];
}const user = {};
console.log(user.isAdmin); // true,所有对象都变成"管理员"了!

解决方法包括:

  1. 使用 Object.create(null) 创建无原型对象,这种对象不继承任何属性,因此不受原型污染的影响。
  2. 使用 Object.freeze(Object.prototype) 冻结 Object.prototype,防止修改。
  3. 使用安全的对象合并方法,如 Object.assign({}, source) 而非直接赋值。
  4. 在处理 JSON 数据时,使用 JSON Schema 验证输入,过滤掉 __proto__ 等危险属性。

this 指向问题

JavaScript 中 this 的动态绑定特性是许多困惑的来源,尤其在继承和异步操作中:

class Button {constructor(text) {this.text = text;}click() {console.log(`点击了按钮: ${this.text}`);}
}const button = new Button("提交");
const clickFunc = button.click;
clickFunc(); // 报错或 undefined,因为 this 丢失

在这个例子中,当我们将 button.click 方法赋值给 clickFunc 变量并调用时,this 不再指向 button 实例,而是指向全局对象(非严格模式下)或 undefined(严格模式下)。这是因为在 JavaScript 中,this 的值取决于函数如何被调用,而不是如何被定义。

在实际开发中,这种问题经常出现在事件处理、回调函数、定时器等场景:

class Countdown {constructor(seconds) {this.seconds = seconds;}start() {// this 将丢失!setInterval(function() {this.seconds--;console.log(this.seconds);}, 1000);}
}const countdown = new Countdown(10);
countdown.start(); // NaN,因为 this.seconds 是 undefined

解决方法是使用箭头函数或 bind

class Button {constructor(text) {this.text = text;// 方法一: 绑定thisthis.click = this.click.bind(this);// 方法二: 使用箭头函数this.clickArrow = () => {console.log(`点击了按钮: ${this.text}`);};}click() {console.log(`点击了按钮: ${this.text}`);}
}// 方法三: 使用类字段定义箭头函数(需要 Babel 支持)
class ModernButton {constructor(text) {this.text = text;}// 类字段语法定义的箭头函数click = () => {console.log(`点击了按钮: ${this.text}`);}
}

每种方法都有其适用场景:

  • bind 适合需要在多个地方重用同一个已绑定 this 的方法。
  • 箭头函数适合只在一个地方使用的回调函数。
  • 类字段箭头函数适合需要在整个类中保持 this 一致的方法。

属性查找与性能

原型链越长,属性查找越慢。在性能关键场景,应避免过深的继承层次。

// 性能测试
class A {}
class B extends A {}
class C extends B {}
class D extends C {}const obj = new D();
console.time("property lookup");
for(let i = 0; i < 1000000; i++) {obj.nonExistentProperty;
}
console.timeEnd("property lookup");

这个简单的测试展示了属性查找的性能开销。当 JavaScript 引擎在对象上查找不存在的属性时,必须遍历整个原型链,直到最终的 Object.prototype。链越长,查找越慢。

在实际开发中,特别是性能关键的应用(如游戏、数据处理、动画等),需要注意以下几点:

  1. 避免过深的继承层次:一般建议不超过 3-4 层继承。
  2. 使用组合优于继承:通过组合不同功能的对象,而非通过继承所有功能,可以减少原型链长度。
  3. 缓存频繁访问的属性:将原型链上频繁访问的属性缓存到局部变量。
  4. 直接定义常用属性:将最常用的属性直接定义在实例上,而非原型上,避免原型链查找。
  5. 使用 hasOwnProperty:在遍历对象属性时使用 hasOwnProperty 方法,避免访问原型属性。
// 优化前
function slowFunction(obj) {let result = 0;for (let i = 0; i < 1000000; i++) {if (obj.expensive) {result += obj.value;}}return result;
}// 优化后
function fastFunction(obj) {// 缓存属性const expensive = obj.expensive;const value = obj.value;let result = 0;for (let i = 0; i < 1000000; i++) {if (expensive) {result += value;}}return result;
}

这种优化在处理大量数据或高频操作时尤为重要。

实际应用:组件继承系统

下面我们实现一个简单的 UI 组件继承系统,展示原型链在实际项目中的应用:

// 基础组件
class Component {constructor(props = {}) {this.props = props;this.state = {};this.element = null;}setState(newState) {this.state = { ...this.state, ...newState };this.render();}render() {throw new Error("Component should implement render method");}mount(container) {if (!this.element) {this.element = this.render();}container.appendChild(this.element);this.componentDidMount();return this;}componentDidMount() {}
}// 按钮组件
class Button extends Component {constructor(props) {super(props);this.state = { clicks: 0 };}render() {const btn = document.createElement("button");btn.textContent = this.props.text || "Button";btn.onclick = () => {this.setState({ clicks: this.state.clicks + 1 });if (this.props.onClick) {this.props.onClick();}};return btn;}componentDidMount() {console.log("Button mounted");}
}// 使用组件
const myButton = new Button({ text: "点击我", onClick: () => console.log("按钮被点击了") 
});// 在页面上挂载
document.addEventListener("DOMContentLoaded", () => {myButton.mount(document.body);
});

这个简单的组件系统展示了如何使用类继承来构建可重用的 UI 组件。它模仿了现代前端框架(如 React)的基本架构:

  1. 基类 Component 提供所有组件共享的功能:

    • props 存储组件的配置选项
    • state 管理组件的内部状态
    • setState 方法更新状态并触发重新渲染
    • render 方法(抽象方法,子类必须实现)
    • mount 方法将组件挂载到 DOM
    • 生命周期方法如 componentDidMount
  2. 子类 Button 继承基类并添加特定功能:

    • 初始化自己的状态
    • 实现 render 方法创建按钮元素
    • 添加点击事件处理
    • 自定义生命周期行为

这个例子显示了 JavaScript 继承的实际应用价值:

  • 代码复用:所有组件共享基础逻辑,无需重复实现。
  • 一致的接口:所有组件都遵循相同的使用模式。
  • 扩展性:可以轻松创建新的组件类型,如 InputForm 等。
  • 封装:每个组件管理自己的状态和行为。

这种组件模型是现代前端框架的基础,虽然实际框架比这复杂得多,但核心概念是一致的。理解这种继承模式有助于更好地掌握框架内部工作原理。

原型继承总结

基于前面的讨论,我们可以总结一些使用原型继承的注意事项:

1. 选择合适的继承方式

  • 对于简单对象,使用 Object.create 创建继承关系。
  • 对于构造函数,使用寄生组合继承或 ES6 类语法。
  • 避免直接修改内置对象的原型,除非你确切知道自己在做什么。

2. 合理设计继承层次

  • 保持继承层次浅而宽,避免深层次的继承链。
  • 遵循"组合优于继承"的原则,使用组合模式实现功能重用。
  • 抽象出真正需要共享的功能放到父类中,特定功能保留在子类中。

3. 安全使用原型

  • 使用 Object.freeze(Object.prototype) 防止原型污染。
  • 处理用户输入时注意防范原型污染攻击。
  • 使用 Object.create(null) 创建无原型对象存储纯数据。

4. 性能优化

  • 缓存原型链上频繁访问的属性。
  • 使用类字段定义实例属性,避免在原型上查找。
  • 通过缓存方法绑定(如 this.method = this.method.bind(this))避免重复绑定。

5. 使用现代语法

  • 优先使用 ES6 类语法,它更清晰且易于理解。
  • 使用 super 关键字访问父类方法,而非手动操作原型链。
  • 利用类字段语法定义实例属性和方法,简化构造函数。

结论

JavaScript 的继承机制基于原型链,这是一种强大而灵活的实现方式,但也容易导致混淆。理解 __proto__prototype 的区别、掌握不同继承模式的优缺点,以及认识到 ES6 类语法背后的原理,对编写高质量的 JavaScript 代码至关重要。

原型继承与传统的基于类的继承有着根本的不同,它更加动态、灵活,但也更容易出错。随着 ES6 类语法的引入,JavaScript 的面向对象编程变得更加直观,但了解底层机制仍然是必不可少的,尤其是在调试复杂问题或优化性能时。

当你下次看到 extends 关键字或 Object.create() 方法时,希望你能想起原型链的工作原理,以及这套看似简单却又深邃的继承机制如何支撑起整个 JavaScript 生态系统。

参考资源

官方文档

  • MDN Web Docs: 继承与原型链
  • MDN Web Docs: Object.create()
  • ECMAScript 6 规范:类定义

技术文章

  • JavaScript原型链深度剖析和应用 - SegmentFault
  • Understanding JavaScript Constructors - CSS-Tricks
  • Master the JavaScript Interview: What’s the Difference Between Class & Prototypal Inheritance? - Eric Elliott

视频教程

  • JavaScript原型链与继承 - Bilibili
  • Object Oriented JavaScript - Mosh Hamedani

交互式学习

  • JavaScript OOP Crash Course - Traversy Media
  • JavaScript: Understanding the Weird Parts - Anthony Alicea(Udemy课程)

工具与代码示例

  • Babel REPL - 可以看到ES6类语法转译成ES5的结果
  • JavaScript Visualizer - 可视化JavaScript执行过程

进阶阅读

  • V8引擎中的对象表示 - V8 Blog
  • 深入理解JavaScript原型链污染攻击
  • JavaScript引擎如何优化对象属性访问

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

http://www.xdnf.cn/news/393211.html

相关文章:

  • Baklib内容中台的核心架构是什么?
  • 蓝桥杯14届国赛 班级活动
  • 反向代理对于 网络安全中服务器的一些思考
  • MiniMind:3块钱成本 + 2小时!训练自己的0.02B的大模型。minimind源码解读、MOE架构
  • JS | 正则 · 常用正则表达式速查表
  • Go语言——kratos微服务框架使用
  • Google语法整理
  • 《软件项目管理》笔记二
  • 从 TTS 到 TTRL:无标签数据强化学习探索与展望
  • CMOS内存的地址空间在主内存空间中吗?
  • Java Solon-MCP 实现 MCP 实践全解析:SSE 与 STDIO 通信模式详解
  • 深入剖析卷积神经网络之卷积层:原理、类型与优化策略
  • Baklib内容管理平台的核心组成是什么?
  • SpringBoot 自动装配原理 自定义一个 starter
  • Android架构模式推荐及分析和MVC架构模式制作一个简单的底部tab切换
  • 嵌入式学习笔记 - STM32 ADC,多重转换,内部参考电压,
  • linux基础操作4------(权限管理)
  • 产业带数据采集方案:1688 API 接口开发与实时数据解析实践
  • 【人工智能】 大模型训练的艺术:从数据到智能的飞跃
  • 【RP2350】香瓜树莓派RP2350之Delay延时
  • 基于SpringBoot的在线教育管理系统
  • spring
  • Python工具链UV整合环境管理
  • 国内外主流AI编程工具全方位对比分析(截至2025年5月)
  • SpringCloud Gateway知识点整理和全局过滤器实现
  • Python中,async和with结合使用,有什么好处?
  • redis数据结构-07(SADD、SREM、SMEMBERS)
  • c++STL-string的模拟实现
  • 谷歌与微软的AI战争:搜索、云服务与生态布局
  • 【Part 2安卓原生360°VR播放器开发实战】第四节|安卓VR播放器性能优化与设备适配