javascript 面向对象(实现继承的几种方式)

7次阅读

共计 2921 个字符,预计需要花费 8 分钟才能阅读完成。

1、原型链继承
核心:将父类的实例作为子类的原型
缺点:父类新增原型方法 / 原型属性,子类都能访问到,父类一变其它的都变了
function Person (name) {
this.name = name;
};

Person.prototype.getName = function () { // 对原型进行扩展
return this.name;
};

function Parent (age) {
this.age = age;
};

Parent.prototype = new Person(‘ 老明 ’); // 这一句是关键 // 通过构造器函数创建出一个新对象,把老对象的东西都拿过来。

Parent.prototype.getAge = function () {
return this.age;
};

// Parent.prototype.getName = function () { // 可以重写从父类继承来的方法, 会优先调用自己的。
// console.log(222);
// };

var result = new Parent(22);
console.log(result.getName()); // 老明 // 调用了从 Person 原型中继承来的方法 (继承到了当前对象的原型中)
console.log(result.getAge()); //22 // 调用了从 Parent 原型中扩展来的方法
2、构造继承
基本思想借用构造函数的基本思想就是利用 call 或者 apply 把父类中通过 this 指定的属性和方法复制(借用)到子类创建的实例中。因为 this 对象是在运行时基于函数的执行环境绑定的。也就是说,在全局中,this 等于 window,而当函数被作为某个对象的方法调用时,this 等于那个对象。call、apply 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
所以,这个借用构造函数就是,new 对象的时候 (new 创建的时候,this 指向创建的这个实例),创建了一个新的实例对象,并且执行 Parent 里面的代码,而 Parent 里面用 call 调用了 Person,也就是说把 this 指向改成了指向新的实例,所以就会把 Person 里面的 this 相关属性和方法赋值到新的实例上,而不是赋值到 Person 上面, 所以所有实例中就拥有了父类定义的这些 this 的属性和方法。
因为属性是绑定到 this 上面的,所以调用的时候才赋到相应的实例中,各个实例的值就不会互相影响了。
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
缺点:方法都在构造函数中定义,只能继承父类的实例属性和方法,不能继承原型属性 / 方法,无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
function Person (name) {
this.name = name;
this.friends = [‘ 小李 ’,’ 小红 ’];
this.getName = function () {
return this.name;
}
};

// Person.prototype.geSex = function () { // 对原型进行扩展的方法就无法复用了
// console.log(“ 男 ”);
// };

function Parent = (age) {
Person.call(this,’ 老明 ’); // 这一句是核心关键
// 这样就会在新 parent 对象上执行 Person 构造函数中定义的所有对象初始化代码,
// 结果 parent 的每个实例都会具有自己的 friends 属性的副本
this.age = age;
};

var result = new Parent(23);
console.log(result.name); // 老明
console.log(result.friends); //[“ 小李 ”, “ 小红 ”]
console.log(result.getName()); // 老明
console.log(result.age); //23
console.log(result.getSex()); // 这个会报错,调用不到父原型上面扩展的方法
3、组合继承
组合继承(所有的实例都能拥有自己的属性,并且可以使用相同的方法,组合继承避免了原型链和借用构造函数的缺陷,结合了两个的优点,是最常用的继承方式)
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后再通过将父类实例作为子类原型,实现函数复用
缺点:调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
function Person (name) {
this.name = name;
this.friends = [‘ 小李 ’,’ 小红 ’];
};

Person.prototype.getName = function () {
return this.name;
};

function Parent (age) {
Person.call(this,’ 老明 ’); // 这一步很关键
this.age = age;
};

Parent.prototype = new Person(‘ 老明 ’); // 这一步也很关键
var result = new Parent(24);
console.log(result.name); // 老明
result.friends.push(“ 小智 ”); //
console.log(result.friends); //[‘ 小李 ’,’ 小红 ’,’ 小智 ’]
console.log(result.getName()); // 老明
console.log(result.age); //24

var result1 = new Parent(25); // 通过借用构造函数都有自己的属性,通过原型享用公共的方法
console.log(result1.name); // 老明
console.log(result1.friends); //[‘ 小李 ’,’ 小红 ’]
4、寄生组合继承
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法 / 属性,避免的组合继承的缺点
缺点:堪称完美,但实现较为复杂
function Person(name) {
this.name = name;
this.friends = [‘ 小李 ’,’ 小红 ’];
}

Person.prototype.getName = function () {
return this.name;
};

function Parent(age) {
Person.call(this,” 老明 ”);
this.age = age;
}

(function () {
var Super = function () {}; // 创建一个没有实例方法的类
Super.prototype = Person.prototype;
Parent.prototype = new Super(); // 将实例作为子类的原型
})();

var result = new Parent(23);
console.log(result.name);
console.log(result.friends);
console.log(result.getName());
console.log(result.age);

正文完
 0