js面向对象(对象、原型与继承三者的关系)

js学习笔记之面向对象(对象、原型与继承三者的关系)

一、对象

1. 面向对象的特性:

  • 封装:包括封装数据、封装实现、封装类型和封装变化,目的是将信息隐藏。
  • 继承:类与类之间的关系(js中没有类的概念,但可以通过构造函数模拟类,是可以继承的)
  • 多态:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。换句话说,给不同的对象发送同一个消息时候,这些对象会根据这个消息分别给出不同的反馈。

2. 创建类的方式

  • 工厂模式

    function createObject(name, age) {
      var obj = new Object();
      obj.name = name;
      obj.age = age;
      obj.sayHi = function () {
        console.log("sayHi");
      };
      return obj;
    }
    
    var per1 = createObject("Lily", 22);
    
  • 自定义构造函数

    function Person(name, age) {
          this.name = name;
          this.age = age;
          this.sayHi = function () {
            console.log("Hi");
          };
        }
    var per1 = new Person("Lily", 22);
    

3. 实例对象和构造函数的关系

  • 实例对象的.constructor 指向构造函数

    console.log(per1.constructor==Person);   //true 
    
  • 对象会记住它的原型

    “对象的原型”就JavaScript来讲,其实不能说对象有原型,实际上指的是对象的构造器(构造函数)的原型。而“对象将请求委托给它自己的原型”是指,对象把请求委托给它的构造器(构造函数)的原型。

    console.log(per.constructor==Person);  //true 
    console.log(per.__proto__.constructor==Person);  //true 
    console.log(per.__proto__.constructor==Person.prototype.constructor);  //true 
    
  • 实例对象可以访问它原型中的每一个属性和方法。

    实例对象使用的属性或者方法,先在当前实例中查找,找到了则直接使用;找不到则,去实例对象的__proto__指向的原型(prototype)中找,找到了则使用,找不到则报错。

    可以使用hasOwnProperty()来判断对象是否包含特定的实例成员。可以使用in来确定一个对象是否包含特定的属性。

    var student = {
      name:"Tom",
      age: 18
    }
    console.log(student.hasOwnProperty("name") //true
    console.log(student.hasOwnProperty("toString") //false
    
    console.log("name" in student ) //true
    console.log("toString" in student ) //true
    

    对象在原型链中的位置越深,找到它也就越慢,也就意味着越耗费性能。

  • 如何判断对象是不是这个数据类型?

    • 通过构造器的方式 实例对象.构造器==构造函数名字 (有可能会指向Object,所以尽量用第二种)

    • 对象 instanceof 构造函数名字

       console.log(per instanceof Person);  //true 
       console.log(per instanceof Object);  //true 
      

二、原型

1. 原型的概念

原型(prototype)可以理解为模板?原型的作用:一、数据共享,节省空间;二、实现继承。

2. 简单的原型实现

 function Student(name, age, sex) {
      this.name = name;
      this.age = age;
      this.sex = sex;
    }
    Student.prototype = {
      // 相当于赋值一个{}空对象,constructor属性被覆盖了,需要手动修正constructor的指向
      constructor:Student,
      height: "188",
      weight: "55kg",
      study: function () {
        console.log("I'm reading");
      },
      eat: function () {
        console.log("I'm eating");
      }
    };

3. 原型链

  • 用来描述实例对象和原型对象之间的关系,通过__proto__记录,并且指向实例对象.prototype

  • 实例对象使用的属性或者方法,先在当前实例中查找,找到了则直接使用;找不到则,去实例对象的__proto__指向的原型(prototype)中找,找到了则使用,找不到则报错。

  • 实例对象不能直接改变原型对象中的属性和方法,当实例对象和原型对象的属性和方法重名时,会优先读取实例中的属性和方法。

  • 原型的指向时可以改变的。

    var A = funtion() {};
    A.prototype = {name:"Lily"};
    
    var B = funtion() {};
    B.prototype = new A();
    
    var b = new B();
    console.log(b.name); //Lily
    
  • 原型链不是无限长的,读取一个不存在属性b.age时,会遍历原型链会一层一层地网上搜索,一直到达Object.prototype,但Object.prototype的值为null,因此这个属性会返回undefined

三、继承

1. 概念

  • 继承是类与类的关系
  • 继承主要通过原型链实现,继承有多种方式:原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承。其中最常用的是组合继承,最有效的是寄生组合继承。因此这里着重讨论这两种。

2. 继承的实现

2.1 原型继承
function Person(name,age,sex,weight) {
  this.name=name;
  this.age=age;
  this.sex=sex;
  this.weight=weight;
}
Person.prototype.sayHi=function () {
  console.log("Hi");
};
function Student(score) {
  this.score=score;
}
Student.prototype=new Person("Xiaoming",18,"male","50kg");
    
var stu1=new Student("100");
console.log(stu1.name,stu1.age,stu1.sex,stu1.weight,stu1.score);
stu1.sayHi();
// Xiaoming 18 male 50kg 100
    
var stu2=new Student("120");
console.log(stu2.name,stu2.age,stu2.sex,stu2.weight,stu2.score);
stu2.sayHi();
// Xiaoming 18 male 50kg 120


缺陷: 实现了数据共享,但new的时候直接初始化了属性,继承过来的属性的值都是相同的,只能重新调用对象的属性进行重新赋值。

2.2 组合继承(原型+构造函数)
function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}
Person.prototype.sayHi = function () {
  console.log("Hello");
};
function Student(name,age,sex,score) {
  //借用父级的构造函数传参数,this的指向是实例对象
  Person.call(this,name,age,sex);  //【第一次调用构造函数】
  this.score = score;
}

//原型继承
Student.prototype = new Person(); //已经传值了,这里不传值  //【第二次调用构造函数】
Student.prototype.eat = function () {
  console.log("eating");
};

var stu1 = new Student("Xiaoming",18,"male","100");
console.log(stu1.name, stu1.age, stu1.sex, stu1.score);
//Xiaoming 18 male 100

var stu2 = new Student("Xiaohong",16,"female","120");
console.log(stu2.name, stu2.age, stu2.sex, stu2.score);
//Xiaohong 16 female 120

缺陷:子类需要调用两次超类构造函数。会创建多余的属性。

上述代码中,实例stu2Student的原型中都分别有一组name age sex 的属性。这就是调用两次Student 构造函数的结果。
在这里插入图片描述

2.3 寄生继承
  • 概念:寄生式继承是于原型式继承紧密相关的一种思路。寄生式基础的思路与寄生构造函数和工厂模式类似,既创建一个仅用于封装继承过程的函数,该函数内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

    function object(o){   
      function F(){}  
      F.prototype=o;  
      return new F();
    }; // 相当于Object.create(o)
    
    function createAnother(original){
        var clone = object(original); // 通过调用函数创建一个新对象
        clone.sayHi = function(){ //以某种方式增强真个对象
            alert("hi");
        }
        return clone; //返回这个对象
    }
    
    var person = {
      name:"Nicholas",
      friends:["Shelby","Court","Van"]
    }
    var now = createAnother(person);
    now.sayHi(); // hi
    

    缺陷:使用寄生式继承方式来为对象添加函数,由于不能达到函数复用,导致效率变低,这与构造函数模式类似。

2.4 寄生组合继承

通过借用构造函数来继承属性,通过原型链来继承方法,解决了组合继承的问题。是实现基于类型继承的最有效的方式。

function inheritPrototype(subType, superType){
    var protoType = Object.create(superType.prototype);    //以superType的原型为模板创建对象
    protoType.constructor = subType; //增强对象 constructor原本指向superType
    subType.prototype = protoType;   //指定对象 绑定到subType原型
}

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
}

function SubType(name, age){
    SuperType.call(this, name);  
    this.age = age;
}

inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
    console.log(this.age);
}

var instance = new SubType("Bob", 18);
instance.sayName();
instance.sayAge();

3. ES6中的继承

3.1 Class 的使用

基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

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

  sayHi() {
    console.log("Hi");
  }
}
var per1 = new Person("Lily", 22);

//等同于ES5写法
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.sayHi = function () {
  console.log("Hi");
};
var per1 = new Person("Lily", 22);

将这两种写法分别创建两个类Person和``Person1,再分别new一个实例出来,会发现它们的proto(也就是prototype`)的结构是一致的。
在这里插入图片描述

ES6 的类,完全可以看作构造函数的另一种写法。构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

class Person {
  constructor() {
    // ...
  }

  sayHi() {
    // ...
  }

  tostring() {
    // ...
  }
}

// 等同于

Person.prototype = {
  constructor() {},
  sayHi() {},
  tostring() {},
};
3.2 Class继承

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  sayHi() {
    console.log("Hi");
  }
  tostring() {
    return `my name is ${this.name}, my age is ${this.age} `;
  }
}
class Student extends Person {
  constructor(name, age, score) {
    super(name, age); // 调用父类的constructor(name, age)
    this.score = score;
  }
  tostring() {
    return `${super.tostring()}, my score is ${this.score}`;// 调用父类的tostring()
  }
}
let stu1 = new Student("Lily", 22, 100);
stu1.sayHi(); // Hi
console.log(stu1.tostring());   //my name is Lily, my age is 22 , my score is 100

需要注意的是,子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

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

class Student extends Person {
  constructor(name, age, score) {
    this.score = score; // ReferenceError
    super(name,agey);
    this.score = score; // 正确
  }
}

上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值