【Javascript的基本知识——原型、this和原型链以及继承】

文章详细介绍了JavaScript中的原型概念,包括每个函数的prototype属性、实例的__proto__属性,以及它们在查找成员时的作用。接着,解释了原型链的概念,如何通过原型链实现成员共享,减少代码冗余。此外,文章还讨论了继承,特别是如何处理构造函数内部和原型上的重复代码,以及如何利用JS实现继承。最后,提供了一个简单的继承封装函数示例。

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

原型

原型是如何解决的

在这里插入图片描述

  1. 原型

    每个函数都会自动附带一个属性prototype,这个属性的值是一个普通对象,称之为原型对象

  2. 实例

    instance,通过new产生的对象称之为实例。

    由于JS中所有对象都是通过new产生的,因此,严格来说,JS中所有对象都称之为实例

  3. 隐式原型

    每个实例都拥有一个特殊的属性__proto__,称之为隐式原型,它指向构造函数的原型

这一切有何意义?

当访问实例成员时,先找自身,如果不存在,会自动从隐式原型中寻找

这样一来,我们可以把那些公共成员,放到函数的原型中,即可被所有实例共享

这是啥?

不同的场景, 指代的含义不同,JS中的this关键字也是如此:

  • 在全局代码中使用this,指代全局对象

    在真实的开发中,很少在全局代码使用this

  • 在函数中使用this,它的指向完全取决于函数是如何被调用的

    调用方式示例函数中的this指向
    通过new调用new method()新对象
    直接调用method()全局对象
    通过对象调用obj.method()前面的对象
    callmethod.call(ctx)call的第一个参数
    applymethod.apply(ctx)apply的第一个参数
var person = {
    name:'Autha',
    age:17,
    sayHi:function(){
        //完成该方法打印姓名和名字
        console.log(person.name,person.age)
    },
};

person.sayHi();

/*
正常情况下调用这样的函数,就像这样写的函数,也不是不可以
就是如果函数发生改变,就会出现问题
*/

var person = {
    name:'Autha',
    age:17,
    sayHi:function(){
        console.log(this.name,this.age);
    },
};

var person1 = person;
person1.sayHi();


//使用this能够很好的解决函数发生变化时候的问题,所以只要理解好this的用法,是好用的
function User(firstName,lastName)
{
    this.firstName = firstName;
    this.lastName = lastName;
    this.fullName = firstName + this.lastName;
}

//能否不适用new,通过User函数创建对象(不能更改User函数)

var u = {};
User.call(u,'小','陈');
console.log(u);

原型链

什么是原型链

所有的对象都是通过new 函数的方式创建的

var u1 = new User('小', '陈'); // 对象 u1 通过 new User 创建
var u2 = { // 对象 u2 通过 new Object 创建
  firstName: '小',
  lastName: '陈'
}
// 等效于
var u2 = new Object(); 
u2.firstName = '小';
u2.lastName = '陈';

上面的代码形成的原型图如下
在这里插入图片描述

我们在创建完一个对象,但是在使用这个对象的时候,发现本身没有给这个对象设置相应的方法,但是有的方法他是确实存在的,这个就是原型,在创建完一个对象之后,他除了具备它本身的方法,他仍然具备原型上面的方法。这个给今后的开发中带来很大的便利性,举一个例子,在进行创建三个学生的对象的时候,需要设置学生的学号,姓名等信息,这些是不同的,但是对这个学生构造一种算成绩平均分的方法是相同的,所以可以把他创建在原型上面,这样代码的冗余量就得到极大的提升,三个学生只是一个例子,扩大到更多的数量就是一个多么快捷便利的事。

Function.prototype.isFun = true;

function sum(){}

console.log(sum.isFun);


原型对象本身也是一个对象,默认情况下,是通过new Object创建的,因此,上面的两幅原型图是可以发生关联的

Object.prototype.__proto__比较特殊,它固定指向null

可以看出,u1的隐式原型形成了一个链条,称之为原型链

当读取对象成员时,会先看对象自身是否有该成员,如果没有,就依次在其原型链上查找

完整的链条

在这里插入图片描述

对开发的影响

在原型上更改会产生多大影响

更改构造函数的原型会对所有原型链上有该构造函数的原型的对象产生影响

学会利用原型链判断类型

  1. instanceof关键字【常用】

    object instanceof constructor
    // 判断object的原型链中,是否存在constructor的原型
    
  2. Object.getPrototypeOf()【不常用】

    Object.getPrototypeOf(object);
    // 返回object的隐式原型
    

学会创建空原型的对象

  1. 利用Object.create()

    Object.create(target);
    // 返回一个新对象,新对象以target作为隐式原型
    
  2. 利用Object.setPrototypeOf()

    Object.setPrototypeOf(obj, prototype);
    // 设置obj的隐式原型为prototype
    

继承

会员系统

视频网站有两种会员:

  • 普通会员
    • 属性:用户名、密码
    • 方法:观看免费视频
  • VIP会员
    • 属性:普通会员的所有属性、会员到期时间
    • 方法:普通会员的所有方法、观看付费视频

如果我们需要使用构造函数来创建会员,如何书写构造函数才能实现上面的需求?

// 普通会员的构造函数
function User(username, password){
  this.username = username;
  this.password = password;
}
User.prototype.playFreeVideo = function(){
  console.log('观看免费视频')
}

// VIP会员的构造函数
function VIPUser(username, password, expires){
  this.username = username;
  this.password = password;
  this.expires = expires;
}
VIPUser.prototype.playFreeVideo = function(){
  console.log('观看免费视频')
}
VIPUser.prototype.playPayVideo = function(){
  console.log('观看付费视频')
}

上面的代码出现了两处重复代码:

  1. VIPUser的构造函数中包含重复代码

    this.username = username;
    this.password = password;
    

    这段代码和User构造函数并没有区别,可以想象得到,将来也不会有区别,即:普通用户该有的属性,VIP用户一定有

  2. VIPUser的原型上包含了重复代码

    VIPUser.prototype.playFreeVideo = function(){
      console.log('观看免费视频')
    }
    

    这个方法和User上的同名方法逻辑完全一致,可以想象得到,将来也不会有区别,即:普通用户该有的方法,VIP用户一定有

如何解决上述两处重复?

处理构造器内部的重复

可以将VIPUser构造器改写为

function VIPUser(username, password, expires){
  User.call(this, username, password);
  this.expires = expires;
}

处理原型上的重复

只需要将原型链设置为下面的结构即可

在这里插入图片描述

仅需一句代码即可

Object.setPrototypeOf(VIPUser.prototype, User.prototype)

至此,完美的解决了之前提到的两处重复代码的问题

这和继承有什么关系

继承是面向对象的概念,它描述了两个对象类型(类,构造函数)之间的关系

如果在逻辑上可以描述为:A不一定是B,但B一定是A,则:B继承A、A派生B、A是B的父类、B是A的子类

子类的实例应该自动拥有父类的所有成员

继承具有两个特性:

  • 单根性:子类最多只有一个父类
  • 传递性:间接父类的成员会传递到子类中

如何在JS中封装继承

function inherit(Child, Parent){
  // 在原型链上完成继承 
  Object.setPrototypeOf(Child.prototype, Parent.prototype);
}

过去,由于没有提供更改隐式原型的方法,因此这一过程会比较复杂。那时候,我们使用一种称之为「圣杯模式」的办法来达到相同的目的,这里不做介绍。

function inherit(Child, Parent) {
  Object.setPrototypeOf(Child.prototype, Parent.prototype);
}

// 普通会员的构造函数
function User(username, password) {
  // this = {}
  this.username = username;
  this.password = password;
}
User.prototype.playFreeVideo = function () {
  console.log('观看免费视频');
};

// VIP会员的构造函数
function VIPUser(username, password, expires) {
  // this = {}
  User.call(this, username, password);
  this.expires = expires;
}

VIPUser.prototype.playPayVideo = function () {
  console.log('观看付费视频');
};

// 完成原型上的变化
inherit(VIPUser, User);

var vip = new VIPUser('abc', '123', '2022-10-01');

vip.playFreeVideo();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值