likes
comments
collection
share

JavaScript原型链继承

作者站长头像
站长
· 阅读数 17

理解原型链

每一个函数对象(Foo)都有一个prototype属性,也就是我们常说的原型,其实质就是一个对象,在这个对象上,有一个contructor属性,指向这个构造函数(Foo),同时,我们可以在这个对象上去自定义一些属性,供我们使用。 当我们通过这个构造函数(Foo)创建一个实例对象,在该对象上会有一个属性:proto,该属性指向的就是构造函数上的prototype,注意prototype也是一个对象,那么它也有__proto__属性,指向的上一级构造函数的prototype。当我们在访问实例对象上的属性时,JavaScript底层会先去找在构造函数中绑定到this上的属性,如果没有找到的话,就去prototype属性上去找有没有对应的属性,如果还没有的话,继续往上面找,直到发现prototype为null为止,最终返回undefined(没有找到)或者具体的值(找到了)。

其实大家在理解原型链的时候,就会发现,这种逐级向上查找的过程,就有些类似我们学过的继承,在面向对象编程中,我们接触最多的就是封装,继承,多态。JavaScript其实也是有面向对象编程的,只不过是在ES6+之后出现的。 还是之前那句话,一个新技术的出现,必定是为了解决某些问题。

那么在es5中,要实现继承,一般我们就会采用原型链的方式来实现,下面就简单地介绍一下关于JavaScript的原型链继承:

原型链继承

通过构造函数创建出来的实例对象,可以共享一份原型属性;

// 定义一个父类 
function Person() {} 
Person.prototype.name = 'Person'; 
function Student() {} 
Student.prototype = new Person(); // 让子类原型指向父类的实例 
Student.prototype.constructor = Student;
var student = new Student();

这种继承方式就是在原型链上去访问一些属性;那么我们就可以实现多个实例对象共享同一个原型对象; 但是这其中还是存在不少的问题:

  • 首先,不能在实例创建时父级构造函数传参,导致最开始的时候,所有实例访问到的父级属性都是一样的;
  • 其次,就是存在一个问题,就是如果原型上的属性为引用类型,当在某个实例对象上去修改该属性中的值时(注意,不是直接把引用类型换掉,而是去修改引用类型里面的属性的值),会发现,在其他的实例对象上访问这些原型属性时,就变成了被修改之后的值,这叫什么,自己什么都没有,然后数据就被改了,奇了个怪;从这个也可以看出,这其实在代码维护时将是一件非常不好的情况;

构造函数继承

// 定义一个父类
function Person() {
}
Person.prototype.name = 'Person';

function Student() {
    Person.call(this[, ...value1]); // 在子类构造函数中,调用父类构造函数,创建实例,完成了传递参数的功能;
    
}

该继承方法实现了创建实例对象时,通过显示绑定this的方式向构造函数(父级)传参; 但是存在一个非常关键的问题,就是无法访问原型上的属性了,失去非常重要的功能;

组合继承

就是将构造函数继承和原型链继承组合在一起使用

// 定义一个父类
function Person() {
}
Person.prototype.name = 'Person';

function Student() {
    // 第一处调用父类构造函数位置
    Person.call(this[, ...value1]); // 在子类构造函数中,调用父类构造函数;
}

Student.prototype = new Person(); // 第二次 调用父类构造函数位置
Student.prototype.constructor = Student;

var student = new Student();

是不是感觉,比较上面的两种要完美一起了,毕竟是前两个方法的优点的结合; 但是结合必定也存在弊端,且看:

  • 在将Student构造函数的原型执行Person后,在Student的实例对象上和其元素上都有相同的属性,造成字段冗余;
  • 调用了两次Person构造函数; 上述的问题,在实际开发中导致的结果,下面举例说明:
// 定义一个父类
function Person(age) {
  this.age = age || 100;
}
Person.prototype.name = 'Person';

function Student() {
  Person.call(this, 12); // 在子类构造函数中,调用父类构造函数,创建实例,完成了传递参数的功能;
}

Student.prototype = new Person();
Student.constructor = Student;

var stu = new Student();
console.log(stu);
delete stu.age;
console.log(stu.age);

输出结果:

JavaScript原型链继承

什么鬼?我不是已经进行delete删除对象属性了吗?为什么会这样,难道JavaScript代码有BUG? 其实这都是咱一手造成的结果,回顾一下上述提到的问题,就会发现的确会出现这种问题。

寄生组合继承

// 定义一个父类
function Person() {
}
Person.prototype.name = 'Person';

function Student() {
    // 第一处调用父类构造函数位置
    Person.call(this[, ...value1]); // 在子类构造函数中,调用父类构造函数;
}

Student.prototype = Object.create(Person.prototype); // 
Student.prototype.constructor = Student;

var student = new Student();

通过创建一个空对象,其原型指向Person.prototype,然后将空对象赋值给Student.prototype,这种我们就基本上实现了一个比较合适的继承。

Object.create()  方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。

const person = {
  isHuman: false,
  printIntroduction: function() {
    console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};

const me = Object.create(person);

me.name = 'Matthew'; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // Inherited properties can be overwritten

me.printIntroduction();
// Expected output: "My name is Matthew. Am I human? true"

以上就是一些基本的继承方式,笔者不能保证完全正确,仅做参考,也非常期待各位小伙伴的反馈。

转载自:https://juejin.cn/post/7235216665431031865
评论
请登录