一文带你了解Js的原型、原型链与继承

本文详细介绍了JavaScript中的面向对象编程概念,包括构造函数、内置对象、工厂函数和自定义构造函数的创建方式。重点讲解了原型和原型链的工作原理,以及如何通过原型实现对象间的继承。同时,对比了ES6中的类语法和继承机制,阐述了它们与传统构造函数的区别和优势。
摘要由CSDN通过智能技术生成

在了解"原型"之前,你需要知道"面向对象"、"构造函数"等相关知识。

面向对象

  • 面向对象实际是一种编程方式、思想。
  • 提到面向对象,不得不提的是面向过程。
  • 区别:
    • 面向过程,关注的是每一步如何实现。
    • 面向对象,只需要关注对象能做什么事。
    • 举个例子:
      • 比如你想吃冰淇淋
      • 面向对象:你直接去超市或者商店,找到冰淇淋,付钱,开吃。
      • 面向过程:你要准备冰淇淋的材料,还要知道制作的过程,制作好后,才能吃。

你可能还并没有完全了解面向对象的概念,但是没关系。再举个例子:

  • 比如你想吃拉面,你可以选择自己制作,也可选择去面馆。
  • 自己做就是“面向过程”,因为你需要买面,和面,拉面,煮面···最后吃面。
  • 去面馆就是“面向对象”,因为你只需要和老板说,我要吃“牛肉拉面”,等一会就能吃面了。

你也可以去阿里云开发者社区查看大佬的解释,或自行百度。

对象的创建方式

内置构造函数

var obj = new Object();  // 创建一个对象
obj.year = 2022;  // 为对象添加一个属性
// 为对象添加一个方法
obj.hiFn = function() {
	console.log("你好,2022!")
}

字面量

// 定义一个对象,并赋初始值
var obj1 = {
    name: 'tim',
    age: 22
}

工厂函数

// 先定义一个函数
function factory() {
    // 手动创建一个对象
    var obj = new Object();
    // 手动添加属性
    obj.name = 'tim';
    obj.age = 22;
    // 手动返回这个对象
    return obj;
}
// 外部再使用
var o1 = factory();
var o2 = factory();

自定义构造函数

// 先定义一个函数
function Person(name, age) {
    // 自动创建一个对象,并赋值给this
    // this = new Object();
    // 手动添加属性
    this.name = name;
    this.age = age;
    // 自动返回这个对象
    // return this;
}
// 外部使用时,需要与new连用
var p1 = new Person('tim', 22);
console.log(p1); // { name: tim, age: 22}

构造函数知识点

  • 构造函数与普通函数类似,之不过调用时需要与new连用
  • 首字母用大驼峰命名,如PersonAnimal···
  • new连用时,内部会自动创建一个对象,并返回对象,不需要写return
  • 构造函数内部的this,指向当前实例。函数里this的指向,由调用方式决定:
    • 作为声明式函数调用:this ⇒ window
    • 作为事件函数调用:this ⇒ 事件源
    • 作为对象的方法调用:this ⇒ 调用方法的实例对象
    • ···
  • 书写构造函数时,除了添加属性也可以添加方法
  • 每new一次实例,方法都会单独开辟一块空间,即使是同一个方法
  • 因此这会造成同一个方法占用多个内存空间,所以引入了原型 prototype

原型

prototype

  • 原型的出现,就是为了解决构造函数的缺点:同一个方法占用多个内存空间
  • 每一个函数都自带一个属性,叫prototype,是一个对象空间,构造函数也有
  • 可以通过函数名访问,也可以向该空间添加属性、方法,如:Person.prototypePerson.prototype.name = "tim"
  • prototype虽然是通过函数添加的内容,但内容却是给实例使用的
// 定义一个函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}
// 在函数的原型上添加一个方法
Person.prototype.sleep = function() {
    console.log("人都需要睡觉,7-8小时为宜。");
}
// 使用构造函数
var p1 = new Person("tim", 22);
console.log(p1.name);   // tim
console.log(p1.age);    // 22
console.log(p1.sleep);  // 人都需要睡觉,7-8小时为宜。

proto

  • 每一个对象都自带一个属性,叫__proto__,也是一个对象空间
  • 实例对象也有__proto__,这个空间是指向该构造函数的prototype
  • 所以,__proto__的地址与构造函数的prototype的地址是同一个地址
  • 当实例访问某个属性/方法时,会从自身查找。没找到会自动去__proto__里找
  • 通常,在书写构造函数时:
    • 属性写在构造函数的内部
    • 方法写在构造函数的原型上

原型链

  • __proto__里面有一个成员叫constructor,是指向当前对象所属的构造函数
  • 如果,当一个对象不知道是谁构造的,就认为是由Object构造的
  • 因为,ObjectJs中的顶级构造函数,有句话叫:“万物皆对象”
  • 所以,当找到Object.prototype时就到顶了,Object.prototype.__proto__结果是null
  • 由此形成了一个链状结构,就是"原型链"
  • 原型链的规则:
    • 访问规则
      • 当访问一个对象的属性或方法时,会先从自身查找
      • 如果没有找到,就会自动去__proto__里查找
      • 如果还没找到,就会去__proto____proto__里面查找
      • 一直找到Object.prototype里面都没有找到,那么就会返回undefined
    • 赋值规则
      • 当需要给一个对象的属性或方法赋值时,会先从自身查找
      • 如果找到了,就修改属性的值
      • 如果没找到,就直接添加该属性并赋值
      • 不会去__proto__

继承

  • 继承指的是可以让子类使用父类的属性与方法

原型继承

  • 通过改变原型链的方式达到继承
  • 就是将子类的原型指向父类的实例,Son.prototype = new Father();
  • 缺点:
    • 子类实例若修改了方法,会影响其他子类的方法,因为共用一个地址空间
// 父类
function Father(name){
    this.name = name;
}
// 原型上有一个方法
Person.prototype.sayHi = function(){
    console.log('hello, my son!')
} 
// 2 子类
function Son(age){
    this.age = age;
}
// 子类原型指向父类实例,就会拥有父类的属性与方法
Son.prototype = new Father('Jack');
// 使用子类,就可以使用的父类的属性与方法
var s1 = new Son(18);
console.log(s1); 

构造函数继承

  • 在子类的构造函数中,使用父类的构造函数,并修改this指向,指到子类实例
  • 使用call来改变函数内部this指向,call(子类实例,传递给函数的参数, ...)
  • 优点:可以将继承自父类的属性直接写在自身
  • 缺点:
    • 只能继承父类函数体里的内容,不能继承父类原型上的内容
    • 因为,函数体里通常写的是属性,而方法通常是写在原型上的
// 父类
function Father(name, gender){
    this.name = name;
    this.gender = gender;            
}
Father.prototype.sayHi = function(){
    console.log('hello, my son!');
}
// 子类继承父类
function Son(age, name, gender){
    this.age = age;  // 子类自身的属性
    // 使用call将this指向当前实例,并传入父类的属性
    Person.call(this, name, gender); // 继承父类的属性
}
// 子类的实例对象,就会拥有父类的属性
var s1 = new Son(18, "小红", '女');

组合继承

  • 由于上述两种方法,都有各自的缺点,所以将两者结合,就是组合继承
  • 先使用构造函数继承,将属性继承到自身
  • 再使用原型继承,将方法继承到自身
  • 缺点:
    • 子类自身原型上的方法,会被覆盖
    • 所以,必须要继承以后,再为子类添加原型方法
// 父类
function Father(name, gender){
    this.name = name;
    this.gender = gender;            
}
Father.prototype.sayHi = function(){
    console.log('hello, my son!');
}
// 子类
function Son(age, name, gender){
    this.age = age;
    // 先使用构造函数 继承属性
    Father.call(this, name, gender);
}
// 再使用原型 继承方法
Son.prototype = new Father();
// 最后再添加 子类自身的方法
Son.prototype.sonFn = function(){
    console.log("我是子类自身的方法")
}
// 最终可以使用父类与子类的属性与方法
var s2 = new Student(20, "小明", '男');

ES6的类语法

  • 学习ES6class语法之前,先复习下之前使用构造函数如何定义属性与方法

    // 构造函数
    function Human(name, age){
        this.name = name;
        this.age = age;
    }
    // 在原型上添加方法
    Human.prototype.eat = function(){
        console.log("一顿不吃饿得慌")
    }
    
  • 然后再来对比,ES6的类语法

    // 使用关键字 class 定义一个函数
    class Human{
       // 使用关键字 constructor 定义函数体内部的属性
       constructor(name, age) {
           this.name = name;
           this.age = age;
       }
       // 直接书写函数 就是在原型上定义的方法 等同于 Human.prototype.eat
       eat(){
           console.log("一顿不吃饿得慌")
       }
       // 使用关键字 static定义静态属性  该属性是给Human函数使用的
       static year = 2022;
       static sayHi = function() {
           console.log("Hello world!");
       };
    }
    // 实例化
    var h1 = new Human("小明", 20); // 使用非static定义的属性与方法
    console.log(Human.year);  // 2022 使用static定义的静态属性
    console.log(Human.sayHi());  // 使用static定义的静态方法
    

ES6继承

  • 可以使用extends关键字:class Son extends Father{ }
  • 继承父类使用super关键字:super(name, age);,并且必须先写
// 父类
class Father{
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    sayHi() {
        console.log("Hello, my son!")
    }
}
// 子类  使用extends关键字表示要继承父类
class Son extends Father{
    constructor(gender, name, age) {
        // 必须先写super,表示要继承父类的属性,并传入参数
        super(name, age);
        // 然后再书写自身的属性
        this.gender = gender; 
    }
    // 子类自身的方法
    sayHello() {
        console.log("Hello, my father!")
    }
}
// 实例就会有父类的属性与方法
var s1 = new Son("男", "李磊", 22)
console.log(s1);
  • 子类通过extends表示要继承父类
  • 然后通过super来继承父类的属性
  • 父类的方法不需要写其他的,会通过原型链自动查找继承

Js的继承

【自己总结的,难免有错,仅供参考】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值