学习 JavaScript,你怎么能不会原型继承?

原型继承


在 ES6 之前 JavaScript 是没有 class 这个概念的,那么它要实现继承必然需要通过其他的办法 —— 那就是原型继承。(友情提醒,如果你不清楚什么是原型对象、prototype 等,建议先去弄明白(可以看我的另一篇博客 彻底理解 JavaScript 原型对象与原型链,否则下面的内容非常不友好)

我们都知道 JavaScript 每一个对象都有 [[Prototype]] 属性(浏览器称为 __proto__),指向它的原型对象,那实现继承最简单的方式是不是可以直接将一个对象的 [[Prototype]] 指向另一个对象

//方法1:直接指定对象的 __proto__ 属性(强烈不推荐)

//创建 Person 对象,具有 eat 和 run 的方法
var Person = {
    
    
    eat: function () {
    
    
        console.log(this.name + " 在吃饭");
    },

    run: function () {
    
    
        console.log(this.name + " 在跑步");
    }
}

//创建 Mike 对象,设置基本属性,具有 doHomework 的方法
var Mike = {
    
    
    name: "Mike",
    age: 15,
    grade: "8 年级",

    doHomework: function () {
    
    
        console.log(this.name + " 正在写 " + this.grade + "的作业");
    }
}

//我们让 Person 变为 Mike 的原型对象,注意只有支持 ES6 的浏览器才能这么写
Mike.__proto__ = Person;
Mike.eat();
Mike.run();
Mike.doHomework();

控制台运行结果:

41

确实是可以实现 “继承”,但是我非常不推荐这么干,不单单是因为浏览器限制的问题,而且直接使用 __proto__ 修改一个对象的原型对象,可能会带来意想不到的麻烦,所以上面的方法也就看个乐


如果你看过我写的那篇关于原型对象和原型链的博客,一定知道在 JavaScript 中还可以使用函数创建对象,下面我们看第二种写法:

//Person 函数(作为构造函数),注意首字母大写
function Person(obj) {
    
    
}

//对 Person 函数的原型对象添加 eat 和 run 方法(我就不说明为什么这么写了)
Person.prototype.eat = function () {
    
    
    console.log(this.name + " 在吃饭");
}

Person.prototype.run = function () {
    
    
    console.log(this.name + " 在跑步");
}

//Teenager 函数(作为构造函数),设置 name、age、grade 属性
function Teenager(obj) {
    
    
    this.name = obj.name;
    this.age = obj.age;
    this.grade = obj.grade;
}

/* 重点来了,我们将 Teenager 函数的原型对象指向 Person 函数产生的实例对象,
   这个实例对象并没有任何的属性和方法,但是它的原型对象,也就是 Object,拥有 eat 和 run 方法
   毕竟我们一开始是使用 Person.prototype.方法名 的方式!*/
Teenager.prototype = new Person();

//然后再在 Teenager 的新原型对象上添加 doHomeWork 方法(所以实际上 Teenager 的原型对象只有这一个方法,其他的都在 Object 上)
Teenager.prototype.doHomeWork = function () {
    
    
    console.log(this.name + " 正在写 " + this.grade + "的作业");
}

//创建实例对象
var Mike = new Teenager({
    
    name: "Mike", age: 15, grade: "8 年级"});
Mike.eat();
Mike.run();
Mike.doHomeWork();

控制台运行结果:

42


同样实现了 “继承”,我们通过 Teenager.prototype = new Person(),让 Person 函数产生的实例对象,变成 Teenager 的原型对象,但是注意,这个原型对象是不存在构造函数的,毕竟这是一个实例。所以这样其实是不符合原型链的定义


那有没有方法能解决这个问题呢?有,我们继续升级代码(这里我也是参考了廖雪峰大佬的 JavaScript 教程):

//这回我们把 name 和 age 抽象到 Person 函数中
function Person(obj) {
    
    
    this.name = obj.name;
    this.age = obj.age;
}

//一样的步骤,不重复说明了
Person.prototype.eat = function () {
    
    
    console.log(this.name + " 在吃饭");
}

Person.prototype.run = function () {
    
    
    console.log(this.name + " 在跑步");
}

//请注意!新的点来了
function Teenager(obj) {
    
    
    //这句话的意思是调用 Person 函数,并且改变后续 this 的绑定,让 this 和实例对象(就是 = 左边的对象)绑定在一起
    Person.call(this, obj);
    this.grade = obj.grade;
}

//下面四句话才是重中之重!我们的思路是想要找到一个中介,作为 Teenager 和 Person 的桥梁,即达到继承的目的,又不破坏它们的原型对象和构造函数
//1、首先定义一个空函数
function F(){
    
    }

/* 2、然后将这个函数的原型对象指向 Person 函数的原型对象。这句话我们好好理解一下,Person 的原型对象除了有 eat 和 run 方法,
别忘了还有构造函数,那么当我们下次使用 new F() 的时候,本质上就是使用 Person.prototype.constructor(就是 Person()) */
F.prototype = Person.prototype;

//3、将 Teenager 的原型指向 F,而 F 又指向 Person,桥梁已经搭好了
Teenager.prototype = new F();

/* 4、最后将 Teenager 的原型对象的构造函数重新修改为 Teenager,
否则 Teenager.prototype.constructor === Person.prototype.constructor */
Teenager.prototype.constructor = Teenager;

//5、最后在 Teenager 的原型对象上定义方法
Teenager.prototype.doHomework = function () {
    
    
    console.log(this.name + " 正在写 " + this.grade + "的作业");
}

let Mike = new Teenager({
    
    name: "Mike", age: 15, grade: "8 年级"});
Mike.eat();
Mike.run();
Mike.doHomework();

控制台输出结果(所有的要求都符合,这才是真正的原型继承):

扫描二维码关注公众号,回复: 15471272 查看本文章

43



关于 JavaScript 原型继承就讲的差不多了,确实是蛮绕的,看的时候一定要搞清楚函数、对象、原型对象和构造函数之间的关系

猜你喜欢

转载自blog.csdn.net/qq_52174675/article/details/122663494