JavaScript中的原型和原型链以及原型继承

本文深入讲解JavaScript中的原型和原型链概念,以及如何利用构造函数和原型实现继承,附带示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 原型与原型链 JavaScript 是一门基于原型的语言,在软件设计模式中,有一种模式叫做原型模式, JavaScript 正是利用这种模式而被创建出来原型模式是用于创建重复的对象,同时又能保证性能,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。

构造函数

 提原型和原型链,就不得不提到构造函数了,因为JS中没有类(Class)这个概念,所以JS的设计者使用了 构造函数 来实现继承机制。JavaScript语言使用构造函数(constructor)作为对象的模板。 所谓“构造函数”,就是专门用来生成“对象”的函数。 它提供模板,描述对象的基本结构。

ES6中的class可以看作只是一个语法糖,它的绝大部分的功能,ES5都可以做到,新的class写法只是让原型的写法更加的清晰、更像面向对象编程的语法而已。(摘自阮一峰的ES6入门)

//构造函数
    function Person(uname, age , sex){
        this.uname = uname;
        this.age = age;
        this.gender = gender ;
    }


//实例化对象
    let p = new Person("11",1,"1");

如上述代码所示,JS通过构造函数来生成实例。但又出现一个新的问题,在构造函数中通过this赋值的属性或者方法,是每个实例的实例属性以及实例方法,同时公共属性和方法也被创建,这样就会重复的创造无用数据,产生数据冗余。所以又设计出了一个原型对象,来存储这个构造函数的公共属性以及方法。

 构造函数创建实例化的过程

  1. 创建一个对象
  2. 将构造函数的作用域赋值给对象(这样this就指向了对象)
  3. 执行构造函数中的代码(为对象添加实例属性和实例方法)
  4. 返回新对象

原型

JavaScript是基于原型的我们创建的每个函数都有一个 prototype(原型) 属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

就是当我们创建一个构造函数的时候,系统就会自动分配一个 prototype属性,而这个prototype属性对应的就是原型对象,可以用来存储可以让所有实例共享的属性和方法。

原型对象有一个constructor属性,对应其构造函数。

并且每一个对象都有一个__proto__属性去对应他的原型对象。

用一张图来解释

原型特点

    function Father(name) {
        // this  指向创建它的实例对象
        this.name = name;
        this.age = age;
        this.sex = sex;
    }


    Father.prototype.money = function() {
        console.log(this.name+"----赚钱");
    }

    let p1 = new Father("zhangsan");
    let p2 = new Father("lisi");


    p1.money();//zhangsan----赚钱
    p2.money();//lisi----赚钱

从这段代码我们不难看出:

  • 实例可以共享原型上面的属性和方法
  • 实例自身的属性会屏蔽原型上面的同名属性,实例上面没有的属性会去原型上面找

原型链

既然原型也是对象,那么他有自己的原型对象么,答案是肯定有的。原型的原型也有自己的原型不过这时这个原型为null

JavaScript 中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链

同样的我们用一张图解释

 

  • 所有原型链的终点都是 Object 函数的 prototype 属性
  • Objec.prototype 指向的原型对象同样拥有原型,不过它的原型是 null ,而 null 则没有原型

清楚了原型链的概念,我们就能更清楚地知道属性的查找规则,比如前面的 person 实例属性.如果自身和原型链上都不存在这个属性,那么属性最终的值就是 undefined ,如果是方法就会抛出错误

鉴于原型的特点和存在的问题,所以我们在实际开发中一般不会单独使用原型链。一般会使用构造函数和原型相配合的模式,当然这也就牵扯出了 JavaScript 中另一个有意思的领域:继承

继承

我们创造两个构造函数,Father()作为父类,Son()作为子类。

我们可以用call()方法直接调用Father方法,然后this指向调用Son的对象,这样就完成了属性的继承

// 利用原型及原型链  实现继承【面向对象】


    //父   构造函数 
    function Father(name,age,sex) {
        // this  指向创建它的实例对象
        this.name = name;
        this.age = age;
        this.sex = sex;
    }


    Father.prototype.money = function() {
        console.log(this.name+"----赚钱");
    }


    // 子   构造函数
    function Son(name,age,sex,email) {
        // this  指向创建它的实例对象
        // this.name = name;
        // this.age = age;
        // this.sex = sex;
        // 过于繁琐,Father中有其属性的创建
        // 所以直接继承Father

        // 继承  属性
        // 利用call() 立即调用父类方法,并传递参数
        Father.call(this,name,age,sex);
        
        //  子类自身的属性 
        this.email = email;
    }

但是继承中难点是父类的公共方法继承

    // 继承  方法

    // 直接将Father的原型   赋值    给Son
    Son.prototype = Father.prototype

首先我们将子类的原型直接指向父类的原型,这样我们就可以实现父类的公共方法继承

 就是将两个构造函数的原型合并为一个,此时出现了新的问题:此时给Son的原型对象添加方法,Father也可以调用

所以说这种方式不可行

那么我们用原型链的处理方式将Father的实例化对象给Son作为原型

    // 将Father的实例对象   赋值给Son的原型对象
    Son.prototype = new Father();
    // 此时给Son添加的方法 ,Father不能调用

此时给Son添加的方法,Father就不能调用了,同时我们也可以调用Father中的公共方法

console.log(Son.prototype.constructor);      //结果指向Father

但是出现了一个新的问题,就是Son的原型对象的构造函数指向的是Father

所以应该修改原型的构造函数指向

// 所以将Son的原型对象的构造函数重新指向Son
    Son.prototype.constructor = Son;
    console.log(Son.prototype.constructor);      //结果指向Son
    Son.prototype.game = function () {
        console.log(this.name+"----游戏");
    }


    let f = new Father("xx",50,"1")
    let s = new Son("oo",0,"0","0202020@163.com")

    f.money();
    s.money();
    s.game();
    // f.game();    //不可用

对此,我们看到了给Son添加的方法,Father就不能调用了,同时我们也可以调用Father中的公共方法,此时的情况如下所示

 利用原型的特点和存在的问题,使用构造函数和原型相配合的模式,实现了继承,现在的Son和Father就是继承的关系。emm,也是最简单的继承关系。

如果有更精辟的见解欢迎评论留言探讨,一起探讨,一起进步!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bitw-QwQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值