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 的继承机制带来了更简洁、易读的语法,同时内部也进行了一些优化,使得继承的实现更加高效和直观。
使用class和extends实现继承:通过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]]࿰…...
5 长度和距离计算模块(length.rs)
这段代码定义了一个泛型结构体 Length<T, Unit>,用于表示一维长度,其中 T 表示长度的数值类型,而 Unit 是一个编译时检查单位一致性的占位符类型,不会用于运行时表示长度的值。这个设计允许开发者在编译阶段确保不同单位之间…...
ollama改模型的存盘目录解决下载大模型报c:盘空间不足的问题
使用Ollama和Open WebUI快速玩转大模型:简单快捷的尝试各种llm大模型,比如DeepSeek r1,非常简单方便,参见:使用Ollama和Open WebUI快速玩转大模型:简单快捷的尝试各种llm大模型,比如DeepSeek r1…...
OSCP:常见文件传输方法
在渗透测试过程中,文件传输是一个关键环节,涉及不同的协议和工具,本文整理了 Linux 和 Windows 系统下常见的文件传输方法,并提供相应的命令示例。 通用文件传输方式 Base64 编码传输 Base64 可用于跨平台传输文件,…...
B站吴恩达机器学习笔记
机器学习视频地址: 4.5 线性回归中的梯度下降_哔哩哔哩_bilibili 机器学习分类: 1. 有监督学习(Supervised Learning) 在有监督学习中,训练数据包含了输入特征和正确的输出标签,模型通过这些带有标签的…...
Java 性能优化与新特性
Java学习资料 Java学习资料 Java学习资料 一、引言 Java 作为一门广泛应用于企业级开发、移动应用、大数据等多个领域的编程语言,其性能和特性一直是开发者关注的重点。随着软件系统的规模和复杂度不断增加,对 Java 程序性能的要求也越来越高。同时&a…...
【计算机网络】host文件
host文件的主要功能: 域名解析 本地映射:host文件的主要功能是将**域名映射到相应的 IP 地址**。当计算机需要访问一个网站或服务时,它会首先在 host文件中查找该域名对应的 IP 地址。如果在 host文件中找到了匹配的域名和 IP 地址映射&…...
【C语言】在Windows上为可执行文件.exe添加自定义图标
本文详细介绍了在 Windows 环境下,如何为使用 GCC 编译器编译的 C程序 添加自定义图标,从而生成带有图标的 .exe 可执行文件。通过本文的指导,读者可以了解到所需的条件以及具体的操作步骤,使生成的程序更具专业性和个性化。 目录 1. 准备条件2. 具体步骤步骤 1: 准备资源文…...
爬虫基础(五)爬虫基本原理
目录 一、爬虫是什么 二、爬虫过程 (1)获取网页 (2)提取信息 (3)保存数据 三、爬虫可爬的数据 四、爬虫问题 一、爬虫是什么 互联网,后面有个网字,我们可以把它看成一张蜘蛛网…...
力扣【1049. 最后一块石头的重量 II】Java题解(背包问题)
让石头分成重量相同的两堆(尽可能相同),相撞之后剩下的石头就是最小的。进一步转化成容量为重量总喝一半的背包最多可以装多少质量的石头。这样就转化成了背包问题。 最后求结果时,我们所最多能装的时dp[target],那另一…...
FFmpeg rtmp推流直播
文章目录 rtmp协议RTMP协议组成RTMP的握手过程RTMP流的创建RTMP消息格式Chunking(Message 分块) rtmp服务器搭建Nginx服务器配置Nginx服务器 librtmp库编译推流 rtmp协议 RTMP(Real Time Messaging Protocol)是由Adobe公司基于Flash Player播放器对应的…...
WordPress Icegram Express插件Sql注入漏洞复现(CVE-2024-2876)(附脚本)
免责申明: 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 0x0…...
重构字符串(767)
767. 重构字符串 - 力扣(LeetCode) 解法: class Solution { public:string reorganizeString(string s){string res;//因为1 < s.length < 500 , uint64_t 类型足够uint16_t n s.size();if (n 0) {return res;}unordere…...
IO进程线程复习
IO进程线程复习...
深入理解Linux内核的虚拟地址到物理地址转换机制及缓存优化
在现代计算机系统中,虚拟地址到物理地址的转换是操作系统内存管理的重要组成部分。特别是在基于x86_64架构的Linux系统上,这一转换过程及其相关的缓存机制对系统性能和稳定性至关重要。本文将深入探讨Debian 10上运行Linux 4.19内核时,这些机制的实现细节,特别是页表管理、…...
2025年01月29日Github流行趋势
项目名称:Janus 项目地址url:https://github.com/deepseek-ai/Janus项目语言:Python历史star数:9350今日star数:5969项目维护者:learningpro, hills-code, TheOneTrueGuy, mowentian, soloice项目简介&…...
yolov11、yolov8部署的7种方法(yolov11、yolov8部署rknn的7种方法),一天一种部署方法,7天入门部署
由于涉及量化、部署两个领域,本博文难免有不对之处,欢迎指正。 本博客对 yolov11(yolov8)尝试了7种不同的部署方法,在最基础的模型上一步一步的去掉解码相关的操作(移到后处理种进行)࿰…...
【ArcGIS遇上Python】批量提取多波段影像至单个波段
本案例基于ArcGIS python,将landsat影像的7个波段影像数据,批量提取至单个波段。 相关阅读:【ArcGIS微课1000例】0141:提取多波段影像中的单个波段 文章目录 一、数据准备二、效果比对二、python批处理1. 编写python代码2. 运行代码一、数据准备 实验数据及完整的python位…...
Node.js MySQL:深度解析与最佳实践
Node.js MySQL:深度解析与最佳实践 引言 Node.js作为一种流行的JavaScript运行时环境,以其轻量级、高性能和事件驱动模型受到开发者的青睐。MySQL则是一款功能强大的关系型数据库管理系统,广泛应用于各种规模的应用程序中。本文将深入探讨Node.js与MySQL的集成,分析其优势…...
wordpress外贸独立站常用询盘软件
LiveChat LiveChat是一家提供实时聊天软件的公司,帮助企业通过其平台与客户进行即时通讯,提高客户满意度和忠诚度。他们的产品允许企业在网站、应用程序或电子邮件等多个渠道与客户互动,从而提升客户体验并促进销售增长。 LiveChat的软件特…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
