JS继承的三种方式
当谈到继承时,JavaScript只有一种结构:对象。每个实例对象(object)都有一个私有属性
__proto__
指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__
),层层向上直到一个对象的原型对象为null
。根据定义,null
没有原型,并作为这个原型链中的最后一个环节。几乎所有JavaScript中的对象都是位于原型链顶端的
Object
的实例。尽管这种原型继承通常被认为是JavaScript的弱点之一,但是原型继承模型本身实际上比经典模型更强大。例如,在原型模型的基础上构建经典模型相当简单。
1、基于原型链的继承
通过重写原型prototype
来实现继承,但是子原型中的属性修改会导致父原型也被更改(同一引用)。
js
Teacher.prototype.name = '张三';
function Teacher () {}
Student.prototype = Teacher.prototype;
function Student () {}
var student = new Student();
console.log(student.name); // '张三'
// 弊端:子原型中的属性修改会导致父原型也被更改
Student.prototype.name = '李四';
Student.prototype.age = 20;
console.log(Teacher.prototype); // {name: "李四", age: 20, constructor: ƒ}
2、借用构造函数
使用call/apply
借用的形式来实现继承,这种形式无法访问到父类原型上的属性。
js
Teacher.prototype.name = '张三';
function Teacher () {
this.skill = 'JS/JQ';
}
function Student () {
// Teacher.call(this);
Teacher.apply(this); // 借用构造函数
this.age = 20;
}
var student = new Student();
console.log(student.skill); // 'JS/JQ'
console.log(student.age); // 20
// 弊端:无法访问到父类原型上的属性
console.log(student.name); // undefined
3、圣杯模式
为了解决前面两种方式的弊端,我们采用圣杯模式来实现继承。
js
Teacher.prototype.name = '张三';
function Teacher () {
this.skill = 'JS/JQ';
}
function Student () {
this.age = 20;
}
// 创建一个缓冲层
function Buffer () {}
// 使得Buffer的原型对象与Teacher的原型对象指向同一个引用地址
Buffer.prototype = Teacher.prototype;
var buffer = new Buffer();
// 然后让Student的原型指向Buffer的实例化对象
Student.prototype = buffer;
var student = new Student();
console.log(student); // Student {age: 20}
// 尝试修改原型上的属性
Student.prototype.test = 'test';
console.log(Student.prototype); // Teacher {test: "test"}
console.log(Teacher.prototype); // {name: "张三", constructor: ƒ}
// 父类原型未受到影响
// 获取父类原型上的属性
console.log(student.name); // '张三'
// 能够获取得父类原型上的属性
虽然,圣杯模式下无法修改父类原型上的属性,但是对于引用值来说,还是会被修改的。
js
function Teacher () {}
Teacher.prototype.students = ['马云', '马化腾'];
Teacher.prototype.age = 88;
function Student () {
this.name = '张三';
this.skill = 'HTML/CSS';
}
var Buffer = function () {}
Buffer.prototype = Teacher.prototype;
Student.prototype = new Buffer();
var student = new Student();
console.log(student); // Student {name: "张三", skill: "HTML/CSS"}
student.students.push('马冬梅');
console.log(student.students); // ["马云", "马化腾", "马冬梅"]
console.log(Teacher.prototype.students); // ["马云", "马化腾", "马冬梅"]
封装圣杯模式
js
/**
* 继承 - 圣杯模式
* @name inherit
* @param { Function } Target - 目标构造函数
* @param { Function } Origin - 继承源,父类构造函数
*/
var inherit = (function () {
var Buffer = function () {}
return function (Target, Origin) {
Buffer.prototype = Origin.prototype;
Target.prototype = new Buffer();
Target.prototype.constructor = Target; // 还原构造器
Target.prototype.superClass = Origin; // 标识继承源
}
})();