wordpress 自己的html,张家口网站seo,东莞网站设计风格,wordpress登入后缀#x1f525; 个人主页#xff1a;空白诗 文章目录 #x1f525; 引言#x1f9f1; 原型基础⛓️ 原型链的形成#x1f504; 修改原型的影响#x1f3c1; 原型链的尽头为什么null标志着结束#xff1f;实际意义 #x1f310; #x1f504; 继承的实现方式1. 原型链继承… 个人主页空白诗 文章目录 引言 原型基础⛓️ 原型链的形成 修改原型的影响 原型链的尽头为什么null标志着结束实际意义 继承的实现方式1. 原型链继承 2. 构造函数继承 ️3. 组合继承经典继承4. ES6 Class继承 实战示例创建可扩展的动物王国1. 基础动物类 (Animal)2. 具体动物类 (Dog Cat)3. 实战应用 总结 相关链接 引言 在深入探索JavaScript编程的旅程中理解继承机制是攀登至高技能水平的关键一步。作为这门语言的基石之一继承不仅支撑着代码的复用性和模块化的实现还深刻影响着对象间关系的构建与数据结构的设计。其中原型链扮演着核心角色它定义了对象属性和方法的查找规则串联起JavaScript对象的血缘与能力传承。本篇讨论将详尽剖析继承的概念从基本原理到多种实现方式旨在为您铺设一条通向JavaScript面向对象编程高手之路的坚实桥梁。 原型基础
首先每个JavaScript对象都有一个内置的属性叫做[[Prototype]]通常通过__proto__访问非标准但广泛支持它指向创建该对象的构造函数的prototype属性。构造函数的prototype本身也是一个对象拥有自己的属性和方法。
示例代码
function Animal(name) {this.name name;
}Animal.prototype.speak function() {console.log(I am an animal);
};let cat new Animal(Kitty); // 创建Animal的实例
console.log(cat)在这里cat的__proto__指向Animal.prototype这意味着cat可以访问Animal.prototype上的方法如speak。 ⛓️ 原型链的形成
当试图访问一个对象的属性或方法时如果该对象本身没有定义JavaScript引擎会向上查找其原型__proto__指向的对象这一过程会一直追溯到原型链的顶部通常是Object.prototype。如果在那里还找不到就会返回undefined。
示例代码
console.log(cat.speak Animal.prototype.speak); // true这行代码确认了cat实例的speak方法确实是指向Animal.prototype上的speak方法证实了继承关系的存在。
cat.speak(); // 输出: I am an animal调用cat.speak()成功执行并打印出I am an animal这证明了cat实例能够正确地沿原型链访问到Animal.prototype上定义的speak方法。
console.log(cat.toString());尽管在Animal构造函数或其原型上没有直接定义toString方法cat.toString()仍然能够执行并按预期工作。这是因为所有JavaScript对象除非被特殊修改都默认从Object.prototype继承了toString方法。toString方法通常用于返回对象的字符串表示对于普通的对象实例默认情况下返回的是[object Object]。 原型链是JavaScript实现继承的核心机制它允许对象间接访问其原型链上定义的属性和方法直至达到Object.prototype。这一机制不仅简化了代码复用也是理解JavaScript面向对象编程的关键。通过上述示例我们可以看到即便没有在每个对象或构造函数中显式定义所有方法也可以通过原型链继承自上层原型或最终的Object.prototype从而获得这些功能。 修改原型的影响
修改原型对象会影响所有通过该构造函数创建的实例。这是因为所有实例共享同一个原型对象。
Animal.prototype.speak function() {console.log(Now I can talk too!);
};cat.speak(); // 输出变为 Now I can talk too!这里我们修改了Animal.prototype上的speak方法所有Animal的实例调用speak时都会反映出这一变化。
由于修改原型会影响到所有通过该构造函数创建的实例开发中应当谨慎操作以防止原型污染。一种常见做法是使用不可变Immutable的设计模式或者在必要时为每个实例单独添加方法而不是修改原型。
function giveUniqueVoice(animal, voice) {animal.speak function() {console.log(voice);};
}let specialCat new Animal(Whiskers);
giveUniqueVoice(specialCat, Meow!);specialCat.speak(); // 输出 Meow!
cat.speak(); // 输出 My behavior has been changed!在这个例子中我们通过giveUniqueVoice函数为特定实例specialCat添加了一个独特的speak方法这样做不会影响到其他Animal实例的行为。 原型链的尽头
原型链的尽头指的是JavaScript中对象原型链层级结构的最终点这个终点是null。在JavaScript中每个对象除null外都有一个内部属性称为[[Prototype]]它指向创建该对象的原型对象。这个原型对象本身也可能是一个对象同样拥有自己的[[Prototype]]如此形成了所谓的原型链。 当我们尝试访问一个对象的属性或方法时如果在该对象自身找不到JavaScript引擎会继续在其原型对象中查找即沿着原型链向上遍历。这一过程会一直持续到遇到一个原型对象的[[Prototype]]为null的点这标志着原型链的终点。换句话说null作为原型链的终点表示没有更进一步的原型可以继承或查找。 Object.prototype是大多数对象原型链中倒数第二层的对象几乎所有JavaScript对象直接或间接的原型链最终都会追溯到Object.prototype而Object.prototype的[[Prototype]]则为null形成了原型链的闭环。
如下代码所示
class Animal {name Animal;speak() {console.log(I am an animal);}constructor(name) {this.name name;}
}
class Dog extends Animal {constructor(name) {super(name); }
}
const myDog new Dog(Rex);
console.log(myDog)myDog (Dog实例): 直接属性name Rex这是因为在Dog类的构造函数中通过super(name)调用了父类Animal的构造函数并将Rex作为参数传递从而设置了实例的name属性。内部属性[[Prototype]]指向Dog.prototype。 Dog.prototype: 这是Dog类的原型对象默认包含一个constructor属性指向Dog构造函数自身。内部属性[[Prototype]]指向Animal.prototype因为Dog类通过extends Animal继承了Animal类所以其原型链会链接到Animal类的原型对象。 Animal.prototype: 包含了Animal类定义的方法如speak()。内部属性[[Prototype]]指向Object.prototype这是所有JavaScript对象原型链的标准终点前一站表明Animal类的原型也是基于基础的JavaScript对象构建的。 Object.prototype: 这是所有JavaScript对象的原型链最终到达的地方包含了像toString(), valueOf()等基本方法。内部属性[[Prototype]]为null标志着原型链的终点。
综上所述myDog的原型链路径如下
myDog - Dog.prototype - Animal.prototype - Object.prototype - null
这条链展示了从myDog实例出发逐级向上通过原型链查找属性和方法的过程直到抵达null即原型链的顶层。
为什么null标志着结束
null作为Object.prototype的[[Prototype]]是一个特意的设计选择它表示这条原型链到此为止没有更进一步的原型可供查找。null既不是对象也不是函数它是一种特殊的值用来表示空值或者尚未赋值的状态。在原型链上下文中它起到了终止链式查找的作用防止无限循环查找。
实际意义
理解原型链的这一终端特性对开发者来说有几个重要含义
性能考量它确保了属性查找有一个明确的终点避免了无止境的循环搜索从而优化了访问速度。对象基础揭示了所有对象共享的基本行为强调了JavaScript中一切皆对象的原则即使是null这样的特殊值也是对象行为逻辑的一部分。继承体系的清晰度有助于开发者构建清晰的继承结构知道何时应该直接在对象上定义方法何时通过原型链继承以及如何避免无意中修改基础对象的行为。
原型链的尽头指向Object.prototype其[[Prototype]]为null这一设计精巧地构建了JavaScript对象继承的基础框架。掌握这一概念对于深入理解对象间的继承关系、避免常见的原型链错误以及高效地设计和维护代码结构都是至关重要的。它是通往JavaScript高级编程之路上的一块基石。 继承的实现方式
1. 原型链继承
最直接的继承方式就是通过原型链。上面的例子已经展示了这一点但我们可以更明确地设置原型链
function Dog(name) {this.name name;
}// 使用Animal的prototype作为Dog.prototype的原型
Dog.prototype Object.create(Animal.prototype);
Dog.prototype.constructor Dog; // 修复constructor指向Dog.prototype.bark function() {console.log(Woof!);
};let myDog new Dog(Rex);
myDog.speak(); // I am an animal
myDog.bark(); // Woof!这段代码展示了如何使用原型链继承在JavaScript中实现继承。这里是逐步解析 定义子类构造函数 Dog: function Dog(name) {this.name name;
}Dog 构造函数接收一个参数 name并将其作为实例的 name 属性。 设置 Dog.prototype 的原型为 Animal.prototype 的副本: Dog.prototype Object.create(Animal.prototype);这行代码是关键它使用 Object.create 方法创建了 Animal.prototype 的一个新对象然后将其赋值给 Dog.prototype。这样一来所有通过 Dog 构造函数创建的实例都会在其原型链上找到 Animal.prototype从而继承了 Animal 的属性和方法。 . 修复 constructor 指向: Dog.prototype.constructor Dog;由于我们直接改变了 Dog.prototype 的指向原本指向 Dog 的 constructor 现在会指向 Animal。为了修正这一点我们需要手动将其设置回 Dog。 在 Dog.prototype 上定义 bark 方法: Dog.prototype.bark function() {console.log(Woof!);
};这为 Dog 的实例添加了一个独有的方法 bark。 创建 Dog 的实例并测试: let myDog new Dog(Rex);
myDog.speak(); // 输出 I am an animal
myDog.bark(); // 输出 Woof!通过 new Dog(Rex) 创建了一个名为 “Rex” 的狗实例。由于 Dog.prototype 指向了 Animal.prototype 的副本myDog 可以访问到 Animal 上的 speak 方法。同时它也有自己特有的 bark 方法。
综上所述这段代码演示了如何利用原型链实现JavaScript中的继承让子类能够复用父类的属性和方法同时也能够扩展自己的特性。
特点简单直接通过将子类型的原型指向父类型的实例实现方法的继承。优点易于实现节省内存共享方法。缺点父类的引用类型属性会被所有子类实例共享无法在构造函数中向父类传递参数。
2. 构造函数继承 ️
另一种方式是通过在子类构造函数内部调用超类构造函数这种方式不涉及原型链而是直接复制属性。
function Animal(name) {this.name name;
}function Dog(name) {Animal.call(this, name);this.species Canine;
}let myDog new Dog(Rex);
console.log(myDog.name); // Rex
console.log(myDog.species); // Canine这段代码展示了构造函数继承的方式实现JavaScript中的继承。下面是详细的解析 定义子类构造函数 Dog: function Dog(name) {Animal.call(this, name);this.species Canine;
}在 Dog 构造函数内部通过 Animal.call(this, name) 调用了 Animal 构造函数。这里的 call 方法改变了 Animal 内部 this 的指向使其指向当前 Dog 实例从而使得 Dog 实例能够继承 Animal 的属性和方法。这就是构造函数继承的核心所在。接着Dog 构造函数还定义了自己的属性 species设置为 Canine。 创建 Dog 实例并检查属性: let myDog new Dog(Rex);
console.log(myDog.name); // 输出 Rex
console.log(myDog.species); // 输出 Canine通过 new Dog(Rex) 创建了一个 Dog 的实例并传入名字 Rex。由于在 Dog 构造函数中调用了 Animal.call(this, name)myDog 实例继承了 Animal 的 name 属性值为 Rex。同时myDog 实例还有自己特有的属性 species值为 Canine。
总结来说这段代码演示了如何通过在子类构造函数内部手动调用父类构造函数并使用 call 或 apply 方法绑定正确的 this 上下文来实现继承这种方式允许子类继承父类的属性同时可以扩展自己的属性和方法。
特点通过在子类构造函数内部调用父类构造函数实现属性的继承。优点每个实例都有自己的属性副本解决了原型链继承中的属性共享问题。缺点只能继承属性无法继承方法每次实例化都会创建方法的新副本浪费内存。
3. 组合继承经典继承
结合原型链继承和构造函数继承是最常用的继承模式。
function Animal(name) {this.name name;
}Animal.prototype.speak function() {console.log(I am an animal);
};function Dog(name) {Animal.call(this, name);this.species Canine;
}// 使用Animal的prototype作为Dog.prototype的原型
Dog.prototype Object.create(Animal.prototype);
Dog.prototype.constructor Dog;Dog.prototype.bark function() {console.log(Woof!);
};let myDog new Dog(Rex);
myDog.speak(); // I am an animal
myDog.bark(); // Woof!
console.log(myDog.species); // Canine这段代码展示了JavaScript中的一种继承模式结合了构造函数继承和原型链继承也称作组合继承是实现继承的常用方式之一。下面是详细的解析 定义子类构造函数 Dog: function Dog(name) {Animal.call(this, name);this.species Canine;
}在 Dog 构造函数内部使用 Animal.call(this, name) 调用了 Animal 构造函数实现了属性的继承构造函数继承。同时它还定义了特有的属性 species。 设置 Dog.prototype 并修复构造函数指针: Dog.prototype Object.create(Animal.prototype);
Dog.prototype.constructor Dog;这两行代码通过 Object.create(Animal.prototype) 设置了 Dog.prototype使得 Dog 的实例可以通过原型链访问到 Animal.prototype 上的方法实现了方法的继承原型链继承。然后修正了构造函数指针因为默认情况下Object.create 会将原型链上原有的构造函数指针设为 Animal。 在 Dog.prototype 上定义 bark 方法: Dog.prototype.bark function() {console.log(Woof!);
};为 Dog 类添加了特有的方法 bark。 创建 Dog 实例并测试: let myDog new Dog(Rex);
myDog.speak(); // 输出 I am an animal
myDog.bark(); // 输出 Woof!
console.log(myDog.species); // 输出 CaninemyDog 既是 Dog 的实例也能够访问到 Animal 的 speak 方法同时具有 Dog 特有的 bark 方法和 species 属性展示了组合继承的特性。
这种组合继承方式综合了构造函数继承和原型链继承的优点既能够继承实例属性又能有效复用方法是JavaScript中较为完善的继承实现方式之一。
特点结合了原型链继承和构造函数继承的优点是最常用的继承模式。优点既能继承属性也能继承方法且每个实例都有自己的属性副本同时方法又是共享的。缺点构造函数中调用了两次父类构造函数一次在子类构造函数内部一次在原型链设定时稍微有些冗余。
4. ES6 Class继承
ES6引入了基于class的语法糖使得继承更加清晰易懂。
class Animal {constructor(name) {this.name name;}speak() {console.log(I am an animal);}
}class Dog extends Animal {constructor(name) {super(name); // 调用父类构造函数this.species Canine;}bark() {console.log(Woof!);}
}let myDog new Dog(Rex);
myDog.speak(); // I am an animal
myDog.bark(); // Woof!
console.log(myDog.species); // Canine这段代码展示了使用ES6的class语法来实现面向对象编程中的继承。下面是代码的详细解析 定义基类 Animal: class Animal {constructor(name) {this.name name;}speak() {console.log(I am an animal);}
}Animal 类通过 constructor 方法定义了一个构造器用于初始化 name 属性并定义了一个 speak 方法。 定义子类 Dog 继承 Animal: class Dog extends Animal {constructor(name) {super(name); // 调用父类构造函数this.species Canine;}bark() {console.log(Woof!);}
}extends 关键字表明 Dog 类继承自 Animal 类。在 Dog 的构造函数中super(name) 调用了父类的构造函数传递了参数 name这是继承父类属性的关键步骤。定义了 Dog 特有的属性 species 和方法 bark。 创建 Dog 实例并测试: let myDog new Dog(Rex);
myDog.speak(); // 输出 I am an animal
myDog.bark(); // 输出 Woof!
console.log(myDog.species); // 输出 Canine通过 new Dog(Rex) 创建了一个 Dog 的实例它继承了 Animal 类的所有属性和方法同时拥有自己的特有属性 species 和方法 bark。
使用 class 语法实现继承简化了传统构造函数和原型链的复杂性提供了更接近于其他面向对象语言的继承模型使得代码更加清晰和易于理解。
特点引入了面向对象编程中的class语法使得继承的语义更加清晰更接近其他面向对象语言。优点语法简洁易于理解支持静态方法和类属性提高了代码的可读性和可维护性。缺点本质上仍然是基于原型只是语法糖新手可能会误解为传统的类继承模型。 每种继承方式的选择应根据实际项目需求和团队习惯来决定。在ES6及以后的版本中推荐使用class语法进行继承它不仅代码更加优雅而且更易于理解和维护。然而理解背后的基本原理——原型链和构造函数——对于深入掌握JavaScript的面向对象编程是至关重要的。 实战示例创建可扩展的动物王国
假设我们要构建一个简单的动物王国模拟器其中包含各种动物它们能发出不同的叫声。我们想要设计一个灵活的架构使得新增动物种类时无需修改现有代码同时让每种动物都能继承通用行为如发出叫声和拥有特有行为。
1. 基础动物类 (Animal)
// 定义基础动物构造函数接收一个name参数初始化动物名字
function Animal(name) {this.name name; // 使用this关键字将传入的name赋值给新创建对象的name属性
}// 在Animal的原型对象上定义一个speak方法模拟动物发出声音
Animal.prototype.speak function() {console.log(Some generic sound); // 打印通用的声音文本
};2. 具体动物类 (Dog Cat)
// Dog构造函数继承Animal
function Dog(name) {Animal.call(this, name); // 使用Animal.call调用超类构造函数确保name属性被正确初始化
}// 设置Dog的原型为Animal的原型的一个新对象实例实现继承
Dog.prototype Object.create(Animal.prototype);
// 修复构造函数指针确保构造函数引用正确
Dog.prototype.constructor Dog;// 覆盖speak方法使Dog有特定的叫声
Dog.prototype.speak function() {console.log(Woof!); // 打印Dog的叫声
};// 类似的操作创建Cat类
function Cat(name) {Animal.call(this, name);
}Cat.prototype Object.create(Animal.prototype);
Cat.prototype.constructor Cat;Cat.prototype.speak function() {console.log(Meow!); // 打印Cat的叫声
};3. 实战应用
// 创建Dog和Cat的实例
const myDog new Dog(Rex);
const myCat new Cat(Whiskers);// 调用各自的speak方法
myDog.speak(); // 输出: Woof!
myCat.speak(); // 输出: Meow!// 动态添加行为到Animal原型所有子类实例都能访问
Animal.prototype.sleep function() {console.log(${this.name} is sleeping.); // 打印睡觉信息使用模板字符串插入实例的名字
};// 调用新添加的sleep方法
myDog.sleep(); // 输出: Rex is sleeping.
myCat.sleep(); // 输出: Whiskers is sleeping.这个示例演示了如何利用原型链实现继承保持代码的灵活性和扩展性。通过Object.create方法建立原型链关系确保了子类能够访问父类的属性和方法同时也能够覆盖或添加新方法以实现特有行为。动物王国的模拟展示了多态性即不同对象对同一消息如speak做出不同响应的能力以及代码的可维护性和扩展性。 总结
本文全面解析了JavaScript中的继承机制核心围绕原型链这一核心概念展开阐述了其在对象继承中的作用与重要性并介绍了几种主要的继承实现方式。以下是文章内容的概括 原型基础
每个JavaScript对象都隐含一个[[Prototype]]属性通常通过__proto__访问指向创建它的构造函数的prototype对象。构造函数的prototype本身是个对象包含可被实例共享的方法和属性。示例展示了如何通过原型链实例能访问到构造函数原型上的方法。 原型链的形成与查找规则
当访问对象的属性或方法时若对象自身未定义则会沿其原型链向上查找直至Object.prototype最后到null终止。解释了所有对象共享Object.prototype上的基本方法如toString()等。 修改原型的影响
修改原型对象会影响所有通过该构造函数创建的实例因它们共享同一原型。强调需谨慎修改原型以防“原型污染”建议采用不可变模式或针对实例单独添加方法。 原型链的尽头
深入探讨了Object.prototype的[[Prototype]]为null的意义作为原型链的终点保证了查找过程的终止。 继承的实现方式
原型链继承直接设置子类型的原型为父类型的实例简单直接共享方法但需注意构造函数的修正。构造函数继承子类构造函数内部调用父类构造函数实现属性继承但不继承方法且方法不共享。组合继承结合上述两者最常用既继承属性也继承方法但构造函数被调用两次。ES6 Class继承引入class语法简化继承表达提供更清晰的面向对象编程风格本质仍是基于原型。
每种继承方式都有其适用场景与优缺点理解这些机制有助于开发者根据具体需求选择合适的继承策略提升代码的效率与可维护性。文章强调了深入理解原型链与构造函数原理对于掌握JavaScript面向对象编程的重要性。 相关链接
JavaScript 中的 Class 类JavaScript中call、apply与bind的区别JavaScript 垃圾回收机制深度解析内存管理的艺术