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

JavaScript原型链与继承:优化与扩展的深度探索

在 JavaScript 的世界里,万物皆对象,而每个对象都有一个与之关联的原型对象,这就构成了原型链的基础。原型链,简单来说,是一个由对象的原型相互连接形成的链式结构 。每个对象都有一个内部属性[[Prototype]](在大多数浏览器中可以通过__proto__属性访问,不过__proto__是非标准属性,更推荐使用Object.getPrototypeOf()方法来获取原型),它指向该对象的原型对象。

以一个简单的例子来说明,我们创建一个构造函数Person:

function Person(name) {this.name = name;}Person.prototype.sayName = function() {console.log('My name is'+ this.name);};let person1 = new Person('Alice');

在这个例子中,person1是Person构造函数创建的实例对象。person1的__proto__属性指向Person.prototype,而Person.prototype也是一个对象,它同样有自己的__proto__属性,指向Object.prototype,Object.prototype的__proto__则为null,这就形成了一条完整的原型链:person1 -> Person.prototype -> Object.prototype -> null。

当我们访问person1的属性或方法时,比如调用person1.sayName(),JavaScript 引擎会首先在person1自身上查找是否有sayName方法。由于person1自身并没有定义sayName方法,引擎就会沿着原型链向上查找,在Person.prototype中找到了sayName方法,于是就执行该方法。如果在Person.prototype中也没有找到,就会继续向上在Object.prototype中查找,直到找到该属性或方法,或者到达原型链的顶端(null)。如果一直到原型链顶端都没有找到,就会返回undefined。

继承机制基础

JavaScript 基于原型链的继承机制是其实现代码复用和对象层次化结构的核心方式。简单来说,通过将一个对象的原型设置为另一个对象,新对象就可以继承原型对象的属性和方法。

继续以上面的Person构造函数为例,我们创建一个新的构造函数Student,让Student继承Person:

function Student(name, grade) {Person.call(this, name);this.grade = grade;}Student.prototype = Object.create(Person.prototype);Student.prototype.constructor = Student;Student.prototype.sayGrade = function() {console.log('My grade is'+ this.grade);};let student1 = new Student('Bob', 10);

在这段代码中,首先在Student构造函数内部通过Person.call(this, name)调用了Person构造函数,这一步的作用是让Student实例能够继承Person构造函数中定义的属性,比如name。然后,通过Student.prototype = Object.create(Person.prototype)将Student.prototype的原型设置为Person.prototype,这样Student的实例就可以继承Person.prototype上的属性和方法,比如sayName方法。最后,重新设置Student.prototype.constructor为Student,以确保构造函数的指向正确。

通过这样的方式,student1既拥有自己特有的属性grade和方法sayGrade,又继承了Person的属性name和方法sayName,实现了对象间的属性和方法继承,充分体现了 JavaScript 基于原型链继承机制的灵活性和强大之处。

现有继承方式剖析

原型链继承

原型链继承是 JavaScript 中最基本的继承方式,它通过将子类的原型指向父类的实例,从而实现子类对父类属性和方法的继承。下面是一个简单的示例:

function Animal(name) {this.name = name;}Animal.prototype.speak = function() {console.log(this.name +'makes a sound.');};function Dog(name, breed) {this.breed = breed;this.name = name;}Dog.prototype = new Animal();Dog.prototype.constructor = Dog;Dog.prototype.bark = function() {console.log(this.name +'barks.');};let myDog = new Dog('Buddy', 'Golden Retriever');

在这个例子中,Dog.prototype = new Animal();这行代码将Dog的原型设置为Animal的一个实例,这样Dog的实例就可以通过原型链访问到Animal原型上的属性和方法,比如speak方法。

优点

  • 简单直观:实现方式简单,易于理解,通过原型链的机制,自然地实现了属性和方法的继承。
  • 共享方法:父类原型上的方法可以被所有子类实例共享,节省内存空间,提高代码复用性。例如,多个Dog实例都可以调用speak方法,而不需要在每个实例中都创建该方法的副本。

缺点

  • 引用类型属性共享问题:由于子类实例共享父类原型上的属性,对于引用类型的属性,一个子类实例对其进行修改,会影响到其他子类实例。比如,如果Animal原型上有一个friends属性,是一个数组,当一个Dog实例向friends数组中添加元素时,其他Dog实例的friends数组也会发生变化。
  • 无法向父类构造函数传参:在创建子类实例时,无法直接向父类构造函数传递参数,这在很多情况下会限制代码的灵活性。例如,我们无法在创建Dog实例时,直接为Animal构造函数中的name属性赋值。

借用构造函数继承

借用构造函数继承,也称为经典继承,是通过在子类构造函数中使用call或apply方法调用父类构造函数,从而实现子类对父类实例属性的继承。示例如下:

function Animal(name) {this.name = name;this.species = 'Animal';}function Dog(name, breed) {Animal.call(this, name);this.breed = breed;}let myDog = new Dog('Max', 'Poodle');

在上述代码中,Animal.call(this, name);这行代码在Dog构造函数的作用域内调用了Animal构造函数,使得Dog实例拥有了Animal构造函数中定义的属性,如name和species。

优点

  • 解决引用类型属性共享问题:每个子类实例都有自己独立的属性副本,不会出现引用类型属性共享导致的相互影响问题。例如,每个Dog实例都有自己独立的name和breed属性,一个Dog实例修改自己的属性,不会影响其他Dog实例。
  • 可以向父类构造函数传参:在创建子类实例时,可以方便地向父类构造函数传递参数,灵活地初始化父类属性。比如在创建Dog实例时,可以直接为Animal构造函数中的name属性传值。

缺点

  • 无法继承父类原型方法:只能继承父类构造函数中的属性和方法,无法继承父类原型对象上的方法。例如,Animal原型上定义的方法,Dog实例无法直接访问和调用。
  • 方法无法复用:由于方法是在构造函数中定义的,每次创建子类实例时,都会重新创建一遍方法,造成内存浪费,降低了代码的复用性。

组合式继承

组合式继承结合了原型链继承和借用构造函数继承的优点,通过原型链继承父类的原型属性和方法,通过借用构造函数继承父类的实例属性。示例如下:

function Animal(name) {this.name = name;this.colors = ['black', 'white'];}Animal.prototype.speak = function() {console.log(this.name +'makes a sound.');};function Dog(name, breed) {Animal.call(this, name);this.breed = breed;}Dog.prototype = new Animal();Dog.prototype.constructor = Dog;Dog.prototype.bark = function() {console.log(this.name +'barks.');};let myDog = new Dog('Cooper', 'Labrador');

在这个例子中,Dog.prototype = new Animal();实现了原型链继承,使得Dog实例可以访问Animal原型上的方法,如speak;Animal.call(this, name);实现了借用构造函数继承,让Dog实例拥有自己独立的name和breed属性。

优点

  • 融合两者优势:既实现了原型方法的复用,又保证了每个实例都有自己独立的属性,避免了原型链继承中引用类型属性共享的问题,也克服了借用构造函数继承中无法继承原型方法的缺陷。例如,Dog实例既可以共享Animal原型上的speak方法,又有自己独立的name、breed和colors属性。

缺点

  • 父类构造函数调用两次:在创建子类实例时,父类构造函数会被调用两次。一次是在设置子类原型时(Dog.prototype = new Animal();),另一次是在子类构造函数内部(Animal.call(this, name);)。这会导致子类实例中存在两份相同的父类实例属性,浪费内存,降低性能。

原型式继承与寄生式继承

原型式继承

原型式继承是基于已有对象创建新对象,通过一个临时构造函数将已有对象作为其原型,然后返回这个临时构造函数的实例,从而实现新对象对已有对象属性和方法的继承。ES5 中通过Object.create()方法规范化了原型式继承。示例如下:

let person = {name: 'John',friends: ['Alice', 'Bob']};let anotherPerson = Object.create(person, {name: {value: 'Jane'}});

在这个例子中,anotherPerson通过Object.create(person)创建,它继承了person的属性和方法,并且可以通过第二个参数为新对象定义额外的属性。

优点

  • 简单灵活:不需要定义构造函数,就能快速基于已有对象创建新对象,适用于简单的对象复制和继承场景。

缺点

  • 引用类型属性共享问题:和原型链继承一样,对于引用类型的属性,新对象和原对象会共享该属性,一个对象对其修改会影响另一个对象。例如,anotherPerson和person共享friends数组,anotherPerson.friends.push('Eve')会使person.friends也发生变化。

寄生式继承

寄生式继承是在原型式继承的基础上,通过一个函数对新创建的对象进行增强,添加新的属性或方法,最后返回这个增强后的对象。示例如下:

function createAnother(original) {let clone = Object.create(original);clone.sayHi = function() {console.log('Hi!');};return clone;}let person = {name: 'Nicholas',friends: ['Shelby', 'Court', 'Van']};let anotherPerson = createAnother(person);

在这个例子中,createAnother函数通过Object.create(original)创建了一个新对象clone,然后为其添加了sayHi方法,最后返回这个增强后的对象anotherPerson。

优点

  • 增强对象功能:可以在不修改原对象的基础上,为新对象添加特定的属性和方法,增强了对象的功能。

缺点

  • 方法无法复用:和借用构造函数继承类似,每次创建新对象时,添加的方法都是新创建的,无法实现方法的复用,降低了效率。同时,它也存在原型式继承中引用类型属性共享的问题。

寄生组合式继承

寄生组合式继承是对组合式继承的优化,它通过借用构造函数来继承属性,通过原型链来继承方法,但避免了组合式继承中父类构造函数被多次调用的问题。其核心是创建一个仅包含父类原型的副本的对象,然后将子类的原型指向这个副本。示例如下:

function inheritPrototype(subType, superType) {let prototype = Object.create(superType.prototype);prototype.constructor = subType;subType.prototype = prototype;}function Animal(name) {this.name = name;this.colors = ['black', 'white'];}Animal.prototype.speak = function() {console.log(this.name +'makes a sound.');};function Dog(name, breed) {Animal.call(this, name);this.breed = breed;}inheritPrototype(Dog, Animal);Dog.prototype.bark = function() {console.log(this.name +'barks.');};let myDog = new Dog('Charlie', 'Bulldog');

在这个例子中,inheritPrototype函数创建了一个Animal.prototype的副本prototype,并将其constructor指向Dog,然后将Dog.prototype指向这个副本。这样,既保证了Dog实例可以继承Animal的属性和方法,又避免了多次调用Animal构造函数。

优点

  • 高效性能:只调用了一次父类构造函数,避免了在子类原型上创建多余的属性,大大提高了性能,减少了内存浪费。
  • 原型链保持完整:原型链保持不变,能够正常使用instanceof和isPrototypeOf等操作符来判断对象的类型和继承关系。

优化策略

减少原型链查找次数

在 JavaScript 中,原型链查找是一个相对耗时的操作,因为每次查找属性或方法时,引擎都需要沿着原型链逐级向上搜索,直到找到目标或到达原型链的顶端。为了提高代码性能,我们可以采取以下几种方式来减少原型链查找次数。

合理设计对象结构:在创建对象时,尽量将常用的属性和方法直接定义在对象自身上,而不是依赖原型链查找。例如,在一个频繁使用的工具函数对象中,如果某个方法被频繁调用,就可以直接将该方法定义在对象实例上:

let utils = {// 直接定义常用方法calculateSum: function(a, b) {return a + b;}};// 直接调用,避免原型链查找console.log(utils.calculateSum(3, 5));

这样,每次调用calculateSum方法时,JavaScript 引擎可以直接在utils对象自身上找到该方法,而不需要在原型链上进行查找,大大提高了访问效率。

使用闭包缓存属性和方法:利用闭包的特性,将需要频繁访问的属性或方法缓存起来,减少原型链查找的次数。例如,假设有一个对象dataObject,其内部的某个属性dataValue被频繁访问:

function createDataObject() {let dataValue = 10;return {getData: function() {// 闭包缓存dataValue,避免每次访问都查找原型链return dataValue;},setData: function(newValue) {dataValue = newValue;}};}let myData = createDataObject();console.log(myData.getData());

在这个例子中,getData方法通过闭包访问并缓存了dataValue,每次调用getData时,不需要在原型链上查找dataValue,提高了访问速度。

避免不必要的继承层次

在设计继承结构时,保持继承层次的简洁性至关重要。过深的继承层次会带来诸多问题,如性能开销增大、代码维护困难等。

性能开销方面:随着继承层次的加深,原型链会变长。当访问对象的属性或方法时,JavaScript 引擎需要在更长的原型链上进行查找,这会显著增加查找时间,降低代码的执行效率。例如,在一个复杂的图形绘制库中,如果存在一个从Shape类开始,经过多层继承得到的ComplexShape类,当调用ComplexShape实例的某个方法时,引擎可能需要在包含多个中间原型对象的原型链上进行查找,这无疑会增加性能损耗。

维护困难方面:过多的继承层次会使代码结构变得复杂,难以理解和维护。当需要修改某个基类的属性或方法时,可能会对多个子类产生意想不到的影响,因为这些子类通过继承链与基类紧密相连。例如,在一个企业级应用中,存在一个多层继承的用户权限管理系统,当修改最顶层的User类的权限验证方法时,可能需要仔细检查每个子类的行为,以确保不会破坏整个权限管理逻辑。

为了避免这些问题,在设计继承结构时,应遵循 “简单即美” 的原则,只在必要时使用继承,并且尽量减少继承的层数。如果某些功能可以通过组合(将不同的对象组合在一起,而不是通过继承)来实现,那么组合可能是更好的选择,因为它可以提供更大的灵活性,同时避免了继承带来的复杂性。

利用 ES6 类语法优化继承

ES6 引入的class和extends关键字为 JavaScript 的继承机制带来了更简洁、易读的语法,同时内部也进行了一些优化,使得继承的实现更加高效和直观。

使用classextends实现继承:通过class关键字定义类,使用extends关键字实现继承,代码结构更加清晰。例如,定义一个Animal类作为父类,再定义一个Dog类继承自Animal:

class Animal {constructor(name) {this.name = name;}speak() {console.log(this.name +'makes a sound.');}}class Dog extends Animal {constructor(name, breed) {super(name);this.breed = breed;}bark() {console.log(this.name +'barks.');}}let myDog = new Dog('Rex', 'German Shepherd');myDog.speak();myDog.bark();

在这段代码中,Dog类通过extends关键字明确地表明它继承自Animal类,super(name)用于调用父类的构造函数,初始化从父类继承的属性。这种语法比传统的基于原型链的继承方式更加直观,易于理解和维护。

ES6 类语法的内部优化:ES6 的类语法在内部实现上进行了一些优化,提高了继承的性能。例如,类的方法在创建时会被预先解析和优化,使得方法调用更加高效。同时,class和extends的实现方式也对原型链的管理进行了优化,减少了不必要的原型链查找和属性复制,从而提升了整体的性能表现。此外,ES6 类语法在错误处理和代码的可读性方面也有很大的提升,使得开发者在编写和调试继承相关的代码时更加轻松。

扩展方法

为原型添加新方法

在 JavaScript 中,为对象原型添加新方法是扩展对象功能的一种常见方式。通过这种方式,我们可以为所有该类型的对象实例添加通用的方法,从而提高代码的复用性和可维护性。然而,在添加新方法时,需要特别注意避免命名冲突,以免覆盖原生方法或其他已有的重要方法。

以String类型为例,假设我们想要为所有字符串对象添加一个reverse方法,用于将字符串反转。可以通过以下方式实现:

if (!String.prototype.reverse) {String.prototype.reverse = function() {return this.split('').reverse().join('');};}let str = 'hello';console.log(str.reverse());

在这段代码中,首先通过if (!String.prototype.reverse)检查String.prototype上是否已经存在reverse方法。如果不存在,才定义新的reverse方法。这样可以确保不会意外地覆盖已有的reverse方法。新定义的reverse方法先使用split('')将字符串拆分成字符数组,然后调用数组的reverse方法反转数组,最后使用join('')将数组重新拼接成字符串。

再比如,为Array原型添加一个sum方法,用于计算数组元素的总和:

if (!Array.prototype.sum) {Array.prototype.sum = function() {return this.reduce((acc, num) => acc + num, 0);};}let numbers = [1, 2, 3, 4, 5];console.log(numbers.sum());

这里同样先检查Array.prototype上是否有sum方法,避免命名冲突。sum方法利用数组的reduce方法,对数组中的每个元素进行累加,初始值为 0,最终返回数组元素的总和。

实现多重继承

JavaScript 本身并不直接支持多重继承,但我们可以通过一些技术手段来模拟实现多重继承的效果,其中比较常用的方法是混入(mixin)模式。混入模式通过将多个对象的属性和方法合并到一个目标对象中,使目标对象能够拥有多个来源的功能。

下面是一个简单的混入函数示例:

function mixin(target,...sources) {sources.forEach(source => {for (let key in source) {if (source.hasOwnProperty(key)) {target[key] = source[key];}}});return target;}let obj1 = {method1: function() {console.log('Method 1');}};let obj2 = {method2: function() {console.log('Method 2');}};let targetObj = {};mixin(targetObj, obj1, obj2);targetObj.method1();targetObj.method2();

在这个例子中,mixin函数接受一个目标对象target和多个源对象sources。通过forEach遍历每个源对象,再使用for...in循环遍历源对象的属性。hasOwnProperty方法用于确保只复制源对象自身的属性,而不包括从原型链继承的属性。最后将源对象的属性和方法复制到目标对象中,使目标对象拥有了多个源对象的功能。

在实际应用中,混入模式常用于插件开发、组件开发等场景。例如,在一个前端组件库中,可能有多个不同功能的模块,通过混入模式可以将这些模块的功能组合到一个组件中,实现组件的功能扩展。比如,一个基础的表单组件可能只包含基本的表单元素和验证功能,通过混入其他模块的属性和方法,可以为表单组件添加数据提交、实时校验提示等更多功能。

基于继承实现设计模式

JavaScript 的继承机制为实现各种设计模式提供了有力的支持。通过合理运用继承,我们可以创建出结构清晰、可维护性高且具有良好扩展性的代码。以下介绍如何利用继承机制实现工厂模式和单例模式。

工厂模式

工厂模式是一种创建对象的设计模式,它将对象的创建和使用分离,通过一个工厂函数或类来负责创建对象,使得代码的依赖关系更加清晰,也便于代码的维护和扩展。

使用函数实现简单的工厂模式:

function ShapeFactory(type) {switch (type) {case 'circle':return {draw: function() {console.log('Drawing a circle');}};case'rectangle':return {draw: function() {console.log('Drawing a rectangle');}};default:return null;}}let circle = ShapeFactory('circle');circle.draw();

在这个例子中,ShapeFactory函数根据传入的参数type创建不同类型的形状对象。每个形状对象都有一个draw方法,用于绘制相应的形状。通过这种方式,我们可以将形状对象的创建逻辑封装在工厂函数中,使用者只需要调用工厂函数并传入所需的参数,就可以获取到相应的形状对象,而无需关心对象的具体创建过程。

单例模式

单例模式确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。在 JavaScript 中,可以通过闭包和原型链来实现单例模式。

let Singleton = (function() {let instance;function MySingleton() {// 私有属性和方法let privateProperty = 'This is a private property';function privateMethod() {console.log('This is a private method');}// 公共属性和方法this.publicProperty = 'This is a public property';this.publicMethod = function() {console.log('This is a public method');privateMethod();console.log(privateProperty);};}return {getInstance: function() {if (!instance) {instance = new MySingleton();}return instance;}};})();let singleton1 = Singleton.getInstance();let singleton2 = Singleton.getInstance();console.log(singleton1 === singleton2);singleton1.publicMethod();

在这段代码中,通过立即执行函数表达式(IIFE)创建了一个闭包。在闭包内部,定义了一个MySingleton构造函数,它包含私有属性和方法以及公共属性和方法。getInstance方法用于获取单例实例,如果实例不存在,则创建一个新的MySingleton实例并返回;如果实例已存在,直接返回已有的实例。这样就确保了整个应用中只有一个MySingleton实例。通过这种方式实现的单例模式,不仅保证了实例的唯一性,还可以隐藏内部实现细节,只对外暴露必要的公共接口,提高了代码的安全性和可维护性。

实践案例分析

大型项目中的原型链与继承优化

在一个大型的电商前端项目中,商品展示和购物车功能是核心部分。为了实现高效的代码管理和性能优化,充分运用了原型链和继承机制。

在商品展示模块,定义了一个基础的Product类,包含商品的基本属性和方法,如商品名称、价格、图片路径以及获取商品信息的方法。代码如下:

class Product {constructor(name, price, imageUrl) {this.name = name;this.price = price;this.imageUrl = imageUrl;}getProductInfo() {return `Name: ${this.name}, Price: ${this.price}`;}}

然后,针对不同类型的商品,如电子产品、服装等,创建了各自的子类,继承自Product类,并添加了特定的属性和方法。以ElectronicProduct类为例:

class ElectronicProduct extends Product {constructor(name, price, imageUrl, brand, model) {super(name, price, imageUrl);this.brand = brand;this.model = model;}getProductInfo() {return `${super.getProductInfo()}, Brand: ${this.brand}, Model: ${this.model}`;}}

通过这种继承方式,代码结构更加清晰,不同类型商品的共性部分在Product类中实现,减少了重复代码。同时,利用 ES6 类语法的优化,提高了代码的执行效率。

在购物车功能中,为了提高性能,减少原型链查找次数,将一些常用的方法直接定义在购物车对象实例上。例如,计算购物车总价的方法:

class Cart {constructor() {this.items = [];// 直接在实例上定义计算总价的方法this.calculateTotal = function() {return this.items.reduce((total, item) => total + item.price, 0);};}addItem(product) {this.items.push(product);}}

这样,每次调用calculateTotal方法时,无需在原型链上查找,直接在实例上就能找到该方法,大大提高了计算效率,尤其是在购物车中商品数量较多的情况下,性能提升更为明显。

常见错误及解决方案

在使用原型链和继承机制时,常常会遇到一些错误,下面列举几个常见的错误及相应的解决方案。

原型对象修改不当:在修改原型对象时,如果不注意,可能会导致意外的结果。例如,在创建实例后修改原型对象的属性,可能会影响到已经创建的实例。

function Person() {}Person.prototype.name = 'Default Name';let person1 = new Person();Person.prototype.name = 'New Name';let person2 = new Person();console.log(person1.name);console.log(person2.name);

在这个例子中,person1创建时,Person.prototype.name的值为Default Name,虽然之后修改了Person.prototype.name的值为New Name,但person1仍然保留着原来的值。这是因为实例在创建时,会获取原型对象的一份 “快照”,之后原型对象的修改不会影响到已经创建的实例。

解决方案:如果需要修改原型对象的属性,并且希望所有实例都能反映出这个修改,最好在创建任何实例之前进行修改。如果在创建实例后必须修改原型对象,可以通过重新定义属性的方式,使其具有可配置性,从而影响到所有实例。例如:

function Person() {}Person.prototype.name = 'Default Name';let person1 = new Person();Object.defineProperty(Person.prototype, 'name', {value: 'New Name',writable: true,enumerable: true,configurable: true});let person2 = new Person();console.log(person1.name);console.log(person2.name);

通过Object.defineProperty重新定义name属性,并设置configurable: true,这样修改后的属性会影响到所有实例。

继承方式选择错误:在不同的场景下,选择不合适的继承方式会导致代码出现问题。例如,在需要共享原型方法的情况下,使用了借用构造函数继承,就会导致无法继承原型方法。

function Animal(name) {this.name = name;}Animal.prototype.speak = function() {console.log(this.name +'makes a sound.');};function Dog(name, breed) {Animal.call(this, name);this.breed = breed;}let myDog = new Dog('Max', 'Poodle');myDog.speak();

在这个例子中,Dog通过借用构造函数继承了Animal的实例属性,但无法继承Animal原型上的speak方法,所以调用myDog.speak()会报错。

解决方案:根据具体需求选择合适的继承方式。如果需要继承原型方法,应使用原型链继承、组合继承或 ES6 类继承。例如,使用 ES6 类继承可以解决上述问题:

class Animal {constructor(name) {this.name = name;}speak() {console.log(this.name +'makes a sound.');}}class Dog extends Animal {constructor(name, breed) {super(name);this.breed = breed;}}let myDog = new Dog('Max', 'Poodle');myDog.speak();

通过class和extends关键字实现继承,Dog实例可以正确继承Animal原型上的speak方法,避免了继承方式选择错误带来的问题。

最后小总结

在 JavaScript 的世界里,原型链和继承机制是其面向对象编程的核心支柱。通过对原型链的深入理解,我们明晰了对象属性和方法的查找路径,它就像一条无形的纽带,将对象与原型紧密相连,构建起了对象之间的层次关系。而多种继承方式的存在,为我们在不同的开发场景中提供了灵活的选择,每种继承方式都有其独特的优缺点,从原型链继承的简单直观,到寄生组合式继承的高效优化,我们需要根据项目的具体需求来精心挑选,以实现代码的最佳性能和可维护性。

在优化策略方面,减少原型链查找次数和避免不必要的继承层次,能够显著提升代码的执行效率,让我们的程序运行得更加流畅。而 ES6 类语法的出现,不仅为继承带来了更简洁、优雅的表达方式,还在内部实现上进行了优化,使得开发过程更加高效和便捷。

在扩展方法上,为原型添加新方法,为我们定制对象的功能提供了便利,让我们能够根据实际需求,为对象赋予更多的能力。实现多重继承的混入模式,突破了 JavaScript 原生不支持多重继承的限制,为我们构建复杂的对象结构提供了新的思路。基于继承实现的工厂模式和单例模式,更是将继承机制与设计模式相结合,展现了 JavaScript 强大的编程能力,使得我们能够创建出结构清晰、可维护性高的代码。

相关文章:

JavaScript原型链与继承:优化与扩展的深度探索

在 JavaScript 的世界里,万物皆对象,而每个对象都有一个与之关联的原型对象,这就构成了原型链的基础。原型链,简单来说,是一个由对象的原型相互连接形成的链式结构 。每个对象都有一个内部属性[[Prototype]]&#xff0…...

【建站】专栏目录

建站专栏的想法有很多,想写穷鬼如何快速低成本部署前后端项目让用户能访问到,如何将网站收录到百度,bing,google并优化seo让搜索引擎搜索到网站,想写如何把网站加入google广告或者接入stripe信用卡首款平台收款&#x…...

题目 1160: 出圈

题目描述 设有n个人围坐一圈并按顺时针方向从1到n编号,从第1个人开始进行1到m的报数,报数到第个m人,此人出圈,再从他的下一个人重新开始1到m的报数,如此进行下去直到所剩下一人为止。 输入格式 输入多行,每…...

Python小游戏29乒乓球

import pygame import sys # 初始化pygame pygame.init() # 屏幕大小 screen_width 800 screen_height 600 screen pygame.display.set_mode((screen_width, screen_height)) pygame.display.set_caption("打乒乓球") # 颜色定义 WHITE (255, 255, 255) BLACK (…...

力扣 【99. 恢复二叉搜索树】Java题解(二叉树的 Morris 遍历)

题目链接 Morris遍历 递归和迭代遍历,不管是前序中序还是后续,空间复杂度都是O(n)(递归是因为隐式调用栈的开销)。 而Morris遍历可以做到空间复杂度是O(1)。 思路就是节点的前序节点的右指针指向该节点,来保证可以通…...

CNN的各种知识点(一):卷积神经网络CNN通道数的理解!

卷积神经网络CNN通道数的理解! 通道数的核心概念解析1. 通道数的本质 2. 单张灰度图的处理示例: 3. 批量输入的处理通道与批次的关系: 4. RGB三通道输入的处理计算过程:示例: 5. 通道数的实际意义6. 可视化理解(1) 单通…...

python-UnitTest框架笔记

UnitTest框架的基本使用方法 UnitTest框架介绍 框架:framework,为了解决一类事情的功能集合 UnitTest框架:是python自带的单元测试框架 自带的,可以直接使用,不需要格外安装 测试人员用来做自动化测试,作…...

书生大模型实战营3

文章目录 L0——入门岛git基础Git 是什么?Git 中的一些基本概念工作区、暂存区和 Git 仓库区文件状态分支主要功能 Git 平台介绍GitHubGitLabGitee Git 下载配置验证下载 Git配置 Git验证 Git配置 Git常用操作Git简易入门四部曲Git其他指令 闯关任务任务1: 破冰活动…...

在CentOS服务器上部署DeepSeek R1

在CentOS服务器上部署DeepSeek R1,并通过公网IP与其进行对话,可以按照以下步骤操作: 一、环境准备 系统要求: CentOS 8+(需支持AVX512指令集)。 硬件配置: GPU版本:NVIDIA驱动520+,CUDA 11.8+。 CPU版本:至少16核处理器,64GB内存。 存储空间:原始模型需要30GB,量…...

C++中常用的十大排序方法之4——希尔排序

成长路上不孤单😊😊😊😊😊😊 【😊///计算机爱好者😊///持续分享所学😊///如有需要欢迎收藏转发///😊】 今日分享关于C中常用的排序方法之4——希尔排序的相…...

机器学习day7

自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测,对预测结果计算精确度和召回率及F1分数 代码 import numpy as np import torch import torch.nn as nn import torch.optim as optimizer import matplotlib.pyp…...

【流媒体】搭建流媒体服务器

搭建Windows Nginx服务器 搭建 下载nginx工具包解压至本地,并在cmd窗口中切换至nginx所在的本地目录修改 conf/nginx.conf 文件,更改其端口号 server中的 listen的端口号从 80改为 8080,因为80经常被其他服务占用,导致无法打开 …...

(电脑版)植物大战僵尸幼儿园版本,开启你的冒险之旅!

欢迎来到植物大战僵尸中文版,园长Jen已准备好迎接你的挑战!在这个充满乐趣和策略的游戏中,你将体验到多种游戏模式,每种模式都带来不同的挑战和乐趣。 游戏模式: 冒险模式:踏上刺激的冒险旅程,…...

民法学学习笔记(个人向) Part.2

民法学学习笔记(个人向) Part.2 民法始终在解决两个生活中的核心问题: 私法自治;交易安全; 3. 自然人 3.4 个体工商户、农村承包经营户 都是特殊的个体经济单位; 3.4.1 个体工商户 是指在法律的允许范围内,依法经…...

解决SetWindowCompositionAttribute使控件文本透明的问题

用以下参数调用该API,能实现类似Aero的模糊透明效果。 参数具体含义见 https://zhuanlan.zhihu.com/p/569258181 http://www.memotech.de/WindowComposition/Text.txt http://www.memotech.de/WindowComposition/WindowComposition.zip DWORD accent[4] { 3,0,0,0 …...

响应式编程与协程

响应式编程与协程的比较 响应式编程的弊端虚拟线程Java线程内核线程的局限性传统线程池的demo虚拟线程的demo 响应式编程的弊端 前面用了几篇文章介绍了响应式编程,它更多的使用少量线程实现线程间解耦和异步的作用,如线程的Reactor模型,主要…...

Altium Designer绘制原理图时画斜线的方法

第一步:检查设置是否正确 打开preferences->PCB Editor ->Interactive Routing->Interactive Routing Options->Restrict TO 90/45去掉勾选项,点击OK即可。如下图所示: 然后在划线时,按下shift空格就能够切换划线…...

Android --- CameraX讲解

预备知识 surface surfaceView SurfaceHolder surface 是什么? 一句话来说: surface是一块用于填充图像数据的内存。 surfaceView 是什么? 它是一个显示surface 的View。 在app中仍在 ViewHierachy 中,但在wms 中可以理解为…...

动态分库分表

1. 动态分库分表的核心目标 解决单库性能瓶颈:通过水平拆分数据,提升并发处理能力。 支持弹性扩展:在不中断服务的前提下,实现数据分片的动态扩容/缩容。 避免跨分片操作:减少跨分片查询(如JOIN、事务&am…...

shell -c

个人博客地址:shell -c | 一张假钞的真实世界 shell -c {string}:表示命令从-c后的字符串读取。在需要使用管道或者重定向需要sudo时很有用,如下: $ sudo find ../*/exportFiles -mtime 15 -name "*" | xargs -I {} r…...

Spring Boot 2 快速教程:WebFlux处理流程(五)

WebFlux请求处理流程 下面是spring mvc的请求处理流程 具体步骤: 第一步:发起请求到前端控制器(DispatcherServlet) 第二步:前端控制器请求HandlerMapping查找 Handler (可以根据xml配置、注解进行查找) 匹配条件包括…...

10.8 LangChain Output Parsers终极指南:从JSON解析到流式处理的规范化输出实践

LangChain Output Parsers终极指南:从JSON解析到流式处理的规范化输出实践 关键词: LangChain Output Parsers、结构化输出、JSON解析、数据校验、流式处理 一、为什么需要规范化输出?大模型输出的“荒野西部”问题 原始输出的三大痛点: 格式不可控:模型可能返回纯文本、…...

G1. Yunli‘s Subarray Queries (easy version)

题目链接:Problem - 2009G1 - Codeforces 题目大意: 给你一个长度为n的整数数组a序列, 然后你可以操作任何次, 将序列里的一个数换成其他任意数字。 后有q次询问, 每一次询问[L, R] 在此区间里, 可最少进行…...

[漏洞篇]SQL注入漏洞详解

[漏洞篇]SQL注入漏洞详解 介绍 把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。通过构造恶意的输入,使数据库执行恶意命令,造成数据泄露或者修改内容等,以达到攻击的目的。…...

【apt源】RK3588 平台ubuntu20.04更换apt源

RK3588芯片使用的是aarch64架构,因此在Ubuntu 20.04上更换apt源时需要使用针对aarch64架构的源地址。以下是针对RK3588芯片在Ubuntu 20.04上更换apt源到清华源的正确步骤: 步骤一:打开终端 在Ubuntu 20.04中,按下Ctrl Alt T打…...

Maven

什么是Maven? Maven是一个项目管理工具,基于POM(Project Object Model,项目对象模型)的概念呢,Maven可以通过一小段描述信息来管理项目的构建,报告和文档的项目管理工具软件。 Maven包含了一个…...

软件工程概论试题五

一、多选 1.好的软件的基本属性包括()。 A. 效率 B. 可依赖性和信息安全性 C. 可维护性 D.可接受性 正答:ABCD 2.软件工程的三要素是什么()? A. 结构化 B. 工具 C.面向对象 D.数据流! E.方法 F.过程 正答:BEF 3.下面中英文术语对照哪些是正确的、且是属…...

Python量化交易助手:xtquant的安装与应用

Python量化交易助手:xtquant的安装与应用 技术背景和应用场景 在量化交易领域,Python因其强大的库支持和灵活性成为了许多开发者的首选语言。其中,xtquant 是迅投官方开发的一个Python包,专门用于与miniqmt通信,实现…...

opencv图像处理框架

一.课程简介与环境配置 二.图像基本操作 (1)计算机眼中的视觉 1)计算机眼中图像是由一块块组成,每一块又由很多很多个像素点组成,一个像素点的值是在0到255之间,值越大就越亮。 2)RGB表示彩色图像的三个颜色通道(红绿蓝),一张…...

MotionLCM 部署笔记

目录 依赖项 humanml3d: sentence-t5-large 下载数据: 报错:No module named sentence_transformers 继续报错:from transformers.integrations import CodeCarbonCallback 解决方法: 推理相关 GitHub - Dai-W…...

BUUCTF_[安洵杯 2019]easy_web(preg_match绕过/MD5强碰撞绕过/代码审计)

打开靶场,出现下面的静态html页面,也没有找到什么有价值的信息。 查看页面源代码 在url里发现了img传参还有cmd 求img参数 这里先从img传参入手,这里我发现img传参好像是base64的样子 进行解码,解码之后还像是base64的样子再次进…...

LLM - 基于LM Studio本地部署DeepSeek-R1的蒸馏量化模型

文章目录 前言开发环境快速开始LM Studio简单设置模型下载开始对话 模型选择常见错误最后 前言 目前,受限于设备性能,在本地部署的基本都是DeepSeek-R1的蒸馏量化模型,这些蒸馏量化模型的表现可能并没有你想象的那么好。绝大部分人并不需要本…...

Intel 与 Yocto 项目的深度融合:全面解析与平台对比

在嵌入式 Linux 领域,Yocto 项目已成为构建定制化 Linux 发行版的事实标准,广泛应用于不同架构的 SoC 平台。Intel 作为 x86 架构的领导者,在 Yocto 生态中投入了大量资源,为其嵌入式处理器、FPGA 和 AI 加速硬件提供了完整的支持…...

2025-工具集合整理

科技趋势 github-rank 🕷️Github China/Global User Ranking, Global Warehouse Star Ranking (Github Action is automatically updated daily). 科技爱好者周刊 制图工具 D2 D2 A modern diagram scripting language that turns text to diagrams 文档帮助 …...

快速提升网站收录:利用网站新闻发布功能

本文转自:百万收录网 原文链接:https://www.baiwanshoulu.com/63.html 利用网站新闻发布功能快速提升网站收录是一个有效的策略。以下是一些具体的建议,帮助你更好地利用这一功能: 一、保持新闻更新频率 搜索引擎尤其重视网站的…...

wxss样式模板,全局配置window

1 wxss样式模板 1.1什么是wxss 1.2 rpx 1.3 样式导入 1.4 全局样式 1.5局部样式 2 全局配置 2.1 全局配置window 2.2 window 导航栏区域...

git多人协作

目录 一、项目克隆 二、 1、进入克隆仓库设置 2、协作处理 3、冲突处理 4、多人协作分支的推送拉取删除 1、分支推送(2种) 2、远程分支拉取(2种) 3、远程分支删除 一、项目克隆 git clone 画船听雨眠/test1 (自定义的名…...

Maven全解析:从基础到精通的实战指南

概念: Maven 是跨平台的项目管理工具。主要服务基于 Java 平台的构建,依赖管理和项目信息管理项目构建:高度自动化,跨平台,可重用的组件,标准化的流程 依赖管理: 对第三方依赖包的管理&#xf…...

使用Pytorch训练一个图像分类器

一、准备数据集 一般来说,当你不得不与图像、文本或者视频资料打交道时,会选择使用python的标准库将原始数据加载转化成numpy数组,甚至可以继续转换成torch.*Tensor。 对图片而言,可以使用Pillow库和OpenCV库对视频而言&#xf…...

除了成本核算,还有哪些财务分析工具可以提高工作效率?

除了成本核算,财务工作中还有多种分析工具可以提高工作效率,以下是详细介绍: 一、数据可视化工具 Power BI:这是一款强大的数据可视化工具,通过创建交互式报表、仪表板和图表来展示财务数据。它易于使用,提…...

【SSM】Spring + SpringMVC + Mybatis

SSM课程,以下为该课程的笔记 bean:IOC容器创建的对象 P12 bean的生命周期 在bean中定义init()和destroy()方法,然后在xml中配置方法名,让bean对象能找到对应的生命周期方法。 或通过实现接口的方式定义声明周期方法。 P13 sett…...

Windows图形界面(GUI)-QT-C/C++ - QT Tab Widget

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 一、概述 1.1 什么是 QTabWidget? 1.2 使用场景 二、常见样式 2.1 选项卡式界面 2.2 动态添加和删除选项卡 2.3 自定义选项卡标题和图标 三、属性设置 3.1 添加页面&…...

pstricks PGFTikz 在CTeX套装中绘图Transparency或Opacity失效的问题

我在CTeX中画图的时候,习惯用Geogebra先画好,然后生成pstricks或PGFTikz代码: 这样不用插入eps或pdf之类的图片,也是一种偷懒的方法。以前往arXiv.org上面传论文也是这样:代码出图,就不用另外上传一幅eps或…...

操作系统和中间件的信息收集

在浏览器中收集操作系统与中间件信息时,主要通过客户端JavaScript(用于操作系统/浏览器信息)和服务器端脚本(用于中间件信息)实现。以下是分步指南: 一、客户端操作系统信息收集(JavaScript&am…...

Android --- handler详解

handler 理解 handler 是一套Android 消息传递机制,主要用于线程间通信。 tips: binder/socket 用于进程间通信。 参考: Android 进程间通信-CSDN博客 handler 就是主线程在起了一个子线程,子线程运行并生成message ,l…...

C++:结构体和类

在之前的博客中已经讲过了C语言中的结构体概念了,重复的内容在这儿就不赘述了。C中的结构体在C语言的基础上还有些补充,在这里说明一下,顺便简单地讲一下类的概念。 一、成员函数 结构体类型声明的关键字是 struct ,在C中结构体…...

初级数据结构:栈和队列

目录 一、栈 (一)、栈的定义 (二)、栈的功能 (三)、栈的实现 1.栈的初始化 2.动态扩容 3.压栈操作 4.出栈操作 5.获取栈顶元素 6.获取栈顶元素的有效个数 7.检查栈是否为空 8.栈的销毁 9.完整代码 二、队列 (一)、队列的定义 (二)、队列的功能 (三&#xff09…...

携程Java开发面试题及参考答案 (200道-下)

insert 一行数据的时候加的是什么锁?为什么? 在 MySQL 中,当执行 INSERT 操作插入一行数据时,加锁的情况会因存储引擎和具体的事务隔离级别而有所不同。一般来说,在 InnoDB 存储引擎下,INSERT 操作加的是行级排他锁(Row Exclusive Lock),以下详细说明原因。 行级排他…...

Python从0到100(八十六):神经网络-ShuffleNet通道混合轻量级网络的深入介绍

前言: 零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…...

98,【6】 buuctf web [ISITDTU 2019]EasyPHP

进入靶场 代码 <?php // 高亮显示当前 PHP 文件的源代码&#xff0c;通常用于调试或展示代码&#xff0c;方便用户查看代码逻辑 highlight_file(__FILE__);// 从 GET 请求中获取名为 _ 的参数值&#xff0c;并赋值给变量 $_ // 符号用于抑制可能出现的错误信息&#xff…...