JavaScript原型链机制与多种继承模式详解

作者:半夏时间:2026-10-23 07:48:10浏览:563

本文深入解析JavaScript原型链机制,阐明实例对象、构造函数与原型对象之间的[[wp:a7]]联系。详细对比了利用call借用构造函数、原型链指向、组合继承、拷贝继承、直接继承prototype以及空对象中介等6种继承模式的优缺点与实现细节,帮助开发者掌握JS面向对象编程核心。

本文详细介绍JavaScript中原型链的工作原理,并深入探讨如何利用原型特性实现对象继承。内容包含原理分析与多种实现代码示例,供开发者参考学习。

原型链基础与语法

原型链基础

原型链本质上是实例对象与原型对象之间建立的一种关联关系,这种关联通过原型属性进行连接。

在JavaScript中,实例对象内部包含属性,该属性指向原型对象。需要注意的是,并非标准属性,而是浏览器内部使用的机制,部分旧版浏览器可能不支持。同时,构造函数中定义的prototype属性也指向原型对象。

需要特别注意的是,定义在原型对象中的方法可以相互调用访问。

代码示例


 function Animal(name,age){
    this.name=name;
    thia.age=age;
   }

   //在原型中添加方法
   Animal.prototype.eat=function(){
    console.log("动物吃草")
    this.play()
   }
   Animal.prototype.play=function(){
    console.log("玩啥呢")
   }

原型的基本语法结构

通过原型对象实现数据共享

第一种实现方式


 function Student(name,age,sex){
      this.name=name;
      this.age=age;
      this.sex=sex;
   }
  
   Student.prototype.height="188"
   Student.prototype.weight="55kg"
   Student.prototype.study=function(){
      console.log("好好学习i")
    }
  var stu=new Student("小红",20,"男")
  console.dir(stu)

运行结果

原型链基础与语法

第二种实现方式


 function Student(name,age,sex){
      this.name=name;
      this.age=age;
      this.sex=sex;
   }
  Student.prototype={
    height:"188",
    weight:"55kg",
    study:function(){
      console.log("好好学习i")
    }
   }
  var stu=new Student("小红",20,"男")
  console.dir(stu)


运行结果

原型链基础与语法

对比上述两种写法,可以发现存在显著差异。第二种写法会覆盖原型对象,导致constructor构造器属性丢失,因此必须手动修正构造器的指向。

修正后的完整代码


function Student(name,age,sex){
      this.name=name;
      this.age=age;
      this.sex=sex;
   }
  Student.prototype={
    constructor:Student,
    height:"188",
    weight:"55kg",
    study:function(){
      console.log("好好学习i")
    }
   }
  var stu=new Student("小红",20,"男")
  console.dir(stu)


经过修正,构造器指向恢复正常。

原型链基础与语法

实例对象属性与方法查找规则

实例对象属性与方法查找规则

当访问实例对象的属性或方法时,JavaScript引擎遵循以下查找顺序:首先在实例自身中查找,若存在则直接使用;若不存在,则通过__proto__指向的原型对象进行查找;如果原型中仍未找到,则继续沿着__proto__向上追溯,直至原型链末端未找到时抛出错误。

对象间关系与原型指向可变性

构造函数、实例对象与原型对象的关系

构造函数用于实例化对象。构造函数内部包含一个prototype属性,指向该构造函数的原型对象。该原型对象中包含一个constructor属性,指向构造函数本身。此外,实例对象的属性指向构造函数的prototype属性。因此,定义在构造函数原型对象中的方法,可以被其实例对象直接访问。

原型指向是否可变?

在探讨原型指向变化之前,需明确构造函数和实例对象中this关键字的指向含义。

此处定义一个自定义构造函数Person,并在其内部输出this的值。

同时在Person的原型对象上添加一个eat方法,并在该方法内部输出this。

接着实例化一个对象,并调用该eat方法。

执行代码以查看具体结果。

原型链基础与语法

控制台输出结果

原型链基础与语法

基于执行结果分析

原型对象中方法内部的this指向当前实例对象。

构造函数内部的this同样指向当前实例对象。

接下来尝试修改原型的指向。

原型链基础与语法

代码中首先定义了一个Person自定义构造函数,并在其原型上添加了eat方法。

随后定义了一个Student函数,并在其原型上定义了sayHi方法。

将Student的原型指向Person的一个实例对象。

接着实例化一个Student对象,并在该实例上尝试调用eat方法和sayHi方法。

运行结果

原型链基础与语法

由此可以确认,通过上述代码成功改变了stu实例对象的原型指向。


Student.prototype=new Person(10);

总结

原型的指向是可以被动态改变的。

实例对象的属性始终指向其所属构造函数的原型对象。

如果构造函数的prototype属性指向发生更改,实例对象的指向也会随之改变。

实例对象与原型对象之间的联系是通过属性建立的,这种层级关系即被称为原型链。

在修改原型指向后,应当立即添加新的原型方法。

此时,sayHi方法将被创建在new Person(10)这个实例对象上。

原型链最终指向分析

原型链的最终指向位置

实例对象中的__proto__属性指向构造函数的prototype属性。

以以下代码为例进行分析。

原型链基础与语法

进行测试验证

原型链基础与语法

原型链基础与语法

结论如下

per实例对象的指向Person.prototype,Person.prototype的指向Object.prototype,而Object.prototype的最终指向Null。

查看HTML DOM对象的原型链结构,可以发现其具有有趣的层级关系。

原型链基础与语法

此处展示相关图示。

原型链基础与语法

JavaScript继承模式详解

实现对象继承

补充知识:instanceof运算符的判断逻辑

判断逻辑为:从左侧操作数的__proto__路径出发,与右侧操作数的prototype属性进行比较,如果两条路径最终指向同一个引用对象,则返回true。

1. 利用call借用构造函数实现继承

优点:成功实现了属性的继承,且各实例的属性值互不相同。

缺点:无法继承父级原型上定义的方法。


function Person(name,age,sex,weight){
  this.name=name;
  this.age=age;
  this.sex=sex;
  this.weight=weight;
}
Person.prototype.sayHi=function(){
  console.log("您好")
}

function Student(name,age,sex,weight,score){
  //将当前实例对象传入Person 借过来使用一次来达到继承效果
  Person.call(this,name,age,sex,weight);
  this.score=score;
}

var stu1=new Student("小明",10,"男","10kg","100")

2. 原型链方式实现继承

通过将Student的prototype指向Person实例来实现继承效果。

优点:成功继承了父级原型上的方法。

缺点:所有Student实例必须共享相同的name和age属性,无法拥有独立的状态。


Student.prototype.constructor=Student

注意:使用原型继承时,必须将构造器的指向修正回正确的构造函数。


function Person(name,age){
    this.name=name;
    this.age=age;
   }

   Person.prototype.eat=function(){
    console.log("Person 吃饭")
   }

   function Student(num,score){
    this.num=num
    this.score=score
   }
   //继承
  Student.prototype=new Person("小红",10)
  Student.prototype.constructor=Student

  var stu =new Student(2016002288,80)

  stu.eat()//Person 吃饭

3. 组合继承模式

组合继承结合了上述两种方法的优点,既实现了属性继承,又保留了原型方法的复用。


function Person(name,age,sex){
    this.name=name;
    this.age=age;
    this.sex=sex;
   }
   Person.prototype.sayHi=function(){
    console.log("你好")
   }

   function Student(name,age,sex,score){
    //借用构造函数
    Person.call(this,name,age,sex)
    this.score=score
   }

   // 改变了原型指向
   Student.prototype=new Person();//不传值
   Student.prototype.eat=function(){
    console.log("吃东西");
   }

   var stu=new Student("小黑",20,"男","100分")
   console.log(stu.name,stu.age,stu.sex,stu.score);
   stu.sayHi()//你好
   stu.eat()//吃东西

4. 拷贝继承模式

该方式类似于复制操作,将一个对象中的属性和方法直接复制到另一个对象中。


function Person(){
  }

  Person.prototype.name="小红"
  Person.prototype.age=18

  function Student(){
  }
  
  var p=Person.prototype;
  var s=Student.prototype;

  for(key in p){
    s[key]=p[key]
  }

  console.dir(Student)

控制台输出示例

原型链基础与语法

每次使用for in循环较为繁琐,可以对其进行优化封装。


function extend(Child,Parent) {

    var p = Parent.prototype;
    var c = Child.prototype;

    for (var i in p) {
      c[i] = p[i];
      }
    
    //这个属性直接指向父对象的prototype属性,可以直接调用父对象的方法,为了实现继承的完备性,纯属备用性质
    c.par = p;

  }

5. 直接继承prototype

优点:执行效率较高。

缺点:由于涉及传址引用,修改Student的属性可能会意外影响Person的属性。


  function Person(){};

  Person.prototype.name="小红";
  Person.prototype.age=18;

  function Student(){};

  Student.prototype=Person.prototype;

  console.dir(Student);
  console.dir(Person);
  Student.prototype.age=25;

控制台输出示例

原型链基础与语法

6. 利用空对象作为中介实现继承

通过这种方式修改Student的prototype,可以避免对Person的prototype产生副作用。


function Person(){};
  Person.prototype.name="小红";
  Person.prototype.age=11;

  function Student(){};
  var F=function(){};
  F.prototype=Person.prototype;

  Student.prototype=new F();
  Student.prototype.constructor=Student;

  Student.prototype.age=25;

  console.dir(Person)
  console.dir(Student)

控制台输出示例

原型链基础与语法

对继承逻辑进行封装


function extend(Child,Parent) {

    var F = function(){};

    F.prototype = Parent.prototype;

    Child.prototype = new F();

    Child.prototype.constructor = Child;

    Child.par = Parent.prototype;

  }

更多关于JavaScript的内容,可参考本站相关教程:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》。

希望本文所述内容能对您的JavaScript程序设计有所帮助。

作者最新文章