JavaScript-----原型链

摘要

这篇博客博客我们主要解决以下几个问题:

  • 什么是构造函数的原型对象?
  • 什么是对象的原型对象?
  • 什么是原型对象的原型对象?
  • 如何通过原型链来实现类的继承?

内容补充:

        要解决以上问题我们就先了解JavaScript中类和对象以及类的继承的相关知识,接下来我们先来回顾一下相关知识:(若你对ES5、ES6中类的知识已经了解了可直接看问题解决

一、ES6的面向对象的语法

「1.面向对象的三大特征

  • 封装性:对象是属性和行为的封装体
  • 多态性:同一消息被不同对象接收后,表现的结果不同
  •  继承性:子类可以继承父类的属性和方法

「2.ES6中定义类」

类:是具有相同属性和行为的对象的集合(类是对象的抽象)

class Student{
    constructor(id,name,sex){ //构造方法
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.age = 18;
        this.address = null;
    }
    show(){ //成员方法(成员函数)
        console.log('学号:',this.id)
        console.log('姓名:',this.name)
        console.log('性别:',this.sex)
        console.log('年龄:',this.age)
        console.log('地址:',this.address)
    }
}
  • '类名':首字母大写
  •  构造方法:对类的对象进行初始化的。不会显式调用。
  •  成员方法:表示某种行为。

「3.ES6中创建对象」 

 对象:是类的实例化,使用new运算符

var s1 = new Student(1001,'郭靖','男')//在内存中开辟了一个存储空间

(1)构造方法:
          A、作用是初始化对象
          B、调用:不能显式调用,而是通过类名调用  

(2)this指针:指当前的对象
(3)属性和方法的使用
     属性:对象名.属性           
     方法:对象名.方法名([参数])

使用new运算符创建实例化对象的过程。在使用new运算符调用类的构造方法是会执行的操作有:
1、在内存中创建新对象,即给对象分配一块存储区。
2、在该对象的内部[prototype]指针指向构造函数的constructor属性
3、构造函数内部的this指针指向该新对象
4、执行构造函数内部的代码(给新对象添加属性和方法)
5、如果构造函数返回非空对象则返回该对象;否则返回刚创建的新对象

s1.show()

s1.age = 22
s1.address = '蒙古'

s1.show()


           
二、ES6中类的继承

子类可以继承父类的部分属性和方法,在继承后子类还可以定义自己独有的属性和方法

「1.子类」

子类又称”派生类“。是继承的类
「2.父类」

父类又称“超类、基类”,是被继承的类
「3.语法格式」

class 子类名 extends 父类名{
          构造方法(){}
          成员方法
        }

   「4.子类访问父类的属性和方法」
(1)使用super关键字调用父类的构造方法: super([参数])
             
          注意:
          
           (a)从父类继承过来的属性必须通过super调用父类的构造方法进行初始化
           
           (b)super([参数])必须作为子类构造方法的第一条语句
           
           (3)无论是父类或子类属性的初始化都必须通过构造方法来完成
        
(2)使用super调用父类的普通成员方法:  super.方法名([参数])
 「5.方法覆盖和方法重载」

     当子类的成员方法和父类的成员方法重名时,子类的方法会覆盖父类的同名方法--方法覆盖(override)。
       方法重载(OverLoad):给同一个方法(函数)传递不同的参数,其执行结果不同。

class Father{  //父类或超类
    constructor(x,y){
        this.x = x  //this.x中的x表示是父类Father的属性
        this.y = y  //this.y中的y表示是父类Father的属性
    }
    money(){
        console.log(100)
    }
    getSum(){ //获取x和y的和
        return this.x + this.y
    }
}
class Son extends Father{ //子类Son继承Father
  //(1)调用父类的构造方法初始化x和y属性
  constructor(x,y,z){ //是子类Son自己的构造方法
    super(x,y) //调用父类的构造方法,x、y是Son从Father中继承的
    this.z = z //this.z中的z是Son自己的属性
  }
  getSum(){ //子类Son自己的成员方法
    let s = super.getSum() + this.z//通过super调用父类的getSum()方法
    console.log(s)
  }
}

//创建子类对象
var s1 = new Son(10,20,30)
s1.getSum()
// s1.money() //子类继承了父类的方法

s1.money运行结果: 

s1.getSum运行结果:  

三、ES5中创建类

    1、创建构造函数,在构造函数中定义属性和方法
       (1)构造方法名就是类名
       (2)和使用class创建类的区别
           a、构造方法创建类:属性和方法都在构造方法中定义  
           b、使用class创建类:属性在构造方法中定义,

//ES5中创建类
function Person(name,sex){ //构造函数,name、sex是构造函数的参数,构造函数名就是类名
    this.name = name  //this.name中的name是Person类的属性
    this.sex = sex //this.sex中的sex是Person类的属性

    this.sleep= function(){ //sleep是成员方法
        console.log(this.name+'在睡觉')
    }
    this.eat = function(){ //eat是成员方法
        console.log(this.name+'在吃饭') 
    }
}
//ES6中创建类
class Person{
    constructor(name,sex){
        this.name = name
        this.sex = sex
    }
    sleep(){ //sleep是成员方法
        console.log(this.name+'在睡觉')
    }
    eat(){ //eat是成员方法
        console.log(this.name+'在吃饭') 
    }
}

四、类的静态成员和实例成员

类的静态成员和实例成员:又称为类成员

「1.实例成员」

实例成员(成员变量和成员方法):属于类的实例(具体的对象) 
「2.静态成员」

包括静态的成员变量和静态的成员方法。不属于某个具体的对象,而是类的所有对象共享的成员。是通过类名(或构造方法名)访问的成员

 强调:在ES6中定义静态成员方法时必须用static关键字修饰

function Student(s,name,sex){
    // this.school = school  //this.school是实例变量
    Student.school = s //school 是静态的成员变量,Student的所有对象共享该成员

    this.name = name //this.name是实例变量
    this.sex =sex //this.sex是实例变量

    this.diplay = function(){
        let str = '学校:'+Student.school+'\n姓名:'+this.name+'\n性别:'+this.sex
        console.log(str)
    }
    Student.sayHello = function(){ //静态成员函数
        console.log('Hello '+Student.school)
    }
}

var s1 = new Student('西安交通大学','周瑜','男')
var s2 = new Student('西安交通大学','小乔','女')
var s3 = new Student('西安交通大学','黄盖','男')

//Student.school = '西安邮电大学'

s1.diplay()
s2.diplay()
s3.diplay()

Student.sayHello()

 若将Student.school = '西安邮电大学'注释则代码运行结果为

若取消Student.school = '西安邮电大学'代码的额注释则运行结果为

由上诉实验结果可看出静态成员对所有对象共享。 

五、原型对象

「1.原型对象」

每个构造函数都有一个原型对象,是通过构造函数的prototype属性来访问
「2.作用」
       (1)使用原型对象共享方法

「3.对象的原型对象」

  • 每个对象都有一个原型对象,是通过'__proto__'访问
  • 构造方法的原型对象和由该构造方法创建的对象的原型对象是相等的(一样的)
  •  ES6中通过class关键字创建的类其本质还是构造方法
function Person(name){ //构造方法:方法名就是类名
    this.name = name
    this.show = function(){
        console.log('姓名:',this.name)
    }
}

Person.prototype.sayHello = function(){
    console.log('Hello 西安邮电大学')
}

//输出Person的原型对象
// console.log(Person.prototype) //node中输出:{},在浏览器控制台输出:{constructor: ƒ}

var p1 = new Person('高峰')
var p2 = new Person('张宇')

/* let flag1 = (p1.sayHello === p2.sayHello) //判断p1和p2调用的是否是同一函数
console.log('flag1=',flag1) //输出:true

let flag2 = (p1.show === p2.show)
console.log('flag2=',flag2) //输出:false */

//输出p1对象的原型对象
console.log(p1.__proto__) //node中输出:{},在浏览器控制台输出:{constructor: ƒ}

console.log(p1.__proto__ === Person.prototype) //输出:true

六、类的创建

「1.ES5中创建的方法」
构造函数名就是类名

function 函数构造名([参数]){
    成员变量(数据成员)
    成员方法
}

「2.ES6中创建的方法」
(1)

class 类名{
    构造函数([参数]){
        初始化成员变量
    }
   成员方法
}

(2)类表达式

var 变量名 = class{
    构造函数([参数]){
        初始化成员变量
    }
   成员方法
}

通过上面的内容补充我们对ES5、ES6中类的相关知识有了一定的了解那么接下来我们就来解决摘要中要解决的问题。

问题解决

一、构造函数与原型对象


「1.构造函数」

构造函数主要用来创建对象,并为对象的成员赋初始值。

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sing = function() {
    console.log('我会唱歌');
  };
}
var p1 = new Person('张三', 18);
var p2 = new Person('李四', 19);
console.log(p1.name); // 输出结果:张三
console.log(p2.age); // 输出结果:19
p2.sing(); // 输出结果:我会唱歌

「2.原型对象」

每个构造函数都有一个原型对象存在,这个原型对象通过构造函数的prototype属性来访问。

function Person() {} // 定义函数
console.log(Person.prototype); // 输出结果:{constructor: ƒ}
console.log(typeof Person.prototype); // 输出结果:object

二、原型链

「1.访问对象的原型对象」

对象的原型对象:每个对象都有一个__proto__属性,这个属性指向了对象的原型对象。

function Person() {}
var p1 = new Person();
console.log(p1.__proto__ === Person.prototype); // 输出结果:true

「2.访问对象的构造函数」

对象的构造函数:在原型对象里面有一个constructor属性,该属性指向了构造函数。

function Person() {}
// 通过原型对象访问构造函数
console.log(Person.prototype.constructor === Person);	// 输出结果:true
// 通过实例对象访问构造函数
var p1 = new Person();
console.log(p1.constructor === Person); // 输出结果:true

 「3.原型对象和原型对象」

原型对象的原型对象:原型对象也是对象,那么这个对象应该也会有一个原型对象存在。

function Person() {}
// 查看原型对象的原型对象
console.log(Person.prototype.__proto__);
// 查看原型对象的原型对象的构造函数
console.log(Person.prototype.__proto__.constructor);

 「4.原型链」

原型链结构特点:

  • 每个构造函数都有一个prototype属性指向原型对象。
  • 原型对象通过constructor属性指向构造函数。
  • 通过实例对象的__proto__属性可以访问原型对象。
  • Object的原型对象的__proto__属性为null。

 「5.成员查找机制」

成员查找机制:

  • JavaScript首先会判断实例对象有没有这个成员
  • 如果没有找到,就继续查找原型对象的原型对象
  • 如果直到最后都没有找到,则返回undefined。
function Person() {
     this.name = '张三';
}
Person.prototype.name = '李四';

var p = new Person();
console.log(p.name); // 输出结果:张三

delete p.name; // 删除对象p的name属性
console.log(p.name); // 输出结果:李四

delete Person.prototype.name; // 删除原型对象的name属性
console.log(p.name); // 输出结果:undefined

三、this指向

 「1.分析指针this」

函数中this指向,情况如下:

  • 构造函数内部的this指向新创建的对象。
  • 直接通过函数名调用函数时,this指向的是全局对象window。
  • 如果将函数作为对象的方法调用,this将会指向该对象。
function foo() {
  return this;
}
var o = {name: 'Jim', func: foo};
console.log(foo() === window); // 对应第2种情况,输出结果:true
console.log(o.func() === o);	// 对应第3种情况,输出结果:true

 「2.更改this指向」

更改this指向方法有:apply()方法和call()方法。

function method() {
    console.log(this.name);
  }
  n1={ name: '张三' }
  n2={ name: '李四' }
  method.apply(n1); 	// 输出结果:张三  此时method方法的this指向n1
  method.call(n2);	// 输出结果:李四  此时method方法的this指向n2
  1. 两个方法的区别和联系
  • 在调用函数时第一个参数默认就是对象
  • apply()方法除第一个参数外第一个参数以数组方式传递  
  • c.call()方法除第一个参数外后面的参数   
//apply()和call()的区别
  function method(a, b) {
    console.log(a + b);
  }
  method.apply({}, ['1', '2']); 	// 数组方式传参,输出结果:12
  method.call({}, '3', '4');		// 参数方式传参,输出结果:34

四、继承

ES6的继承

ES6的继承(一个类的直接父类只能有一个)
class 子类名 extends 父类名{构造方法  成员方法}

class Father{
    constructor(name){
        this.name=name
    }
    show(){
        console.log('姓名:'+this.name);
    }
}

class Son extends Father{//子类
    constructor(name){
        super(name)//在子类的构造方法中调用父类的构造方法----经典继承(对象伪造或盗用构造方法)
    }

} 

ES5的继承

 「1.借构造函数继承父类属性」

call()方法 :将父类的this指向子类的this,这样就可以实现子类继承父类的属性。

function Father(name){
    this.colors=['red','green','pink']//colors属性的值是引用值(数组)
    this.name=name
    this.show = function(){
        console.log('姓名:'+this.name);
    }
}
function Son(Subname){
    Father.call(this,Subname)//继承父类  在子类的构造方法中通过call()函数调用父类构造函数
}

var s1=new Son('刘备')
s1.show()//姓名:刘备
console.log(s1.colors);//[ 'red', 'green', 'pink', 'black' ]

 「2.利用原型对象继承父类方法」

原型对象继承父类方法:将父类的实例对象作为子类的原型对象来使用

//通过原型链实现类的继承
//1.定义父类
function SuperType(){
    this.property='中国人民银行'
}
//2.给SuperType的原型对象添加方法
SuperType.prototype.getSuperValue = function(){
    return this.property
}
//3.定义子类构造方法
function SubType(){
    this.subProperty = '中国工商银行'
}
//4.实现SubType对SuperType的继承
SubType.prototype=new SuperType()//通过原型对象实现子类对父类的继承
//5.在子类的原型对象上增加一个方法
SubType.prototype.getSubValue=function(){
    return this.subProperty
}
//子类覆盖父类的
SubType.prototype.getSuperValue=function(){
    return '中国建设银行'
}
//6.创建一个子类实例对象
let sub=new SubType()
let s1=new SuperType()
/* console.log(sub.getSuperValue());//子类的实例对象调用父类的方法
console.log(sub.getSubValue());//子类的实例对象调用子类类的方法 */

/* console.log(sub instanceof Object);//true
console.log(sub instanceof SuperType);//true
console.log(sub instanceof SubType);//true */

/* console.log(s1 instanceof Object);//true 
console.log(s1 instanceof SuperType);//true 
console.log(s1 instanceof SubType);//false */

console.log(Object.prototype.isPrototypeOf(sub));//判断sub的原型对象是不是Object的原型对象   true
console.log(SuperType.prototype.isPrototypeOf(sub));  //true
console.log(SubType.prototype.isPrototypeOf(sub));  //true

3、在原型链继承中实现方法的覆盖(Override)--子类方法和父类方法同名时,子类方法覆盖了父类的方法。
实现方法:在子类的原型对象方法上添加和父类的完全同名的方法
4、在继承过程中确立原型对象和实例对象之间的关系方法 
(1)instanceof运算符:判断实例对象的类型
(2)isPrototypeOf()方法:判断原型对象的类型


五、原型链的缺点和解决办法

「1.原型链的问题」
(1)若原型中包含引用值,则该引用值会在所有的实例对象中共享。这既是属性放在构造方法中定义而不放在原型对象中定义的原因
(2)子类的实例对象不能给父类型的构造方法传参
「2.原型链问题的解决办法」
6、原型链问题的解决办法:盗用父类的构造方法(又称'对象伪造'或'经典继承')
基本思路:在子类的构造方法中调用父类的构造方法。
(1)ES6中 

class Father{
    constructor(name){
        this.name=name
    }
    show(){
        console.log('姓名:'+this.name);
    }
}

class Son extends Father{//子类
    constructor(name){
        super(name)//在子类的构造方法中调用父类的构造方法----经典继承(对象伪造或盗用构造方法)
    }

}

(2)ES5中:将父类的this指向子类的this,这样就可以实现子类继承父类的属性。

function Father(name){
    this.colors=['red','green','pink']//colors属性的值是引用值(数组)
    this.name=name
    this.show = function(){
        console.log('姓名:'+this.name);
    }
}
function Son(Subname){
    Father.call(this,Subname)//继承父类  在子类的构造方法中通过call()函数调用父类构造函数
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值