上一篇我讲了下继承的基础知识-原型和原型链。看到有人读完个人技术分享后而有所得,我很开心;看到有人提意见我也虚心接受。前端
在讲继承的几种方式前我打算先说一下——《孔乙己》。es6
《孔乙己》一文中我印象最深的是孔己乙的一个动做和一句对白一个提问。面试
一个动做:排出九文大钱
一句对白:窃书不能算偷……读书人的事,能算偷么
一个提问:回香豆的回字,怎样写的bash
孔乙己这种深受科举教育毒害的读书人,常会注意一些没有用的字,并且把这当作学问和本领。会‘回’的几种写法就是有本领吗?微信
我正思考这个问题时。好像有一个面试官在回答: 会‘回’的几种写法是否是本领我不清楚,不过我想知道你会几种继承的写法。网络
So 正月初七开工大吉,了解继承的几种方式,不失为一种有趣的迎新方式。数据结构
在 JavaScript
中继承是很是重要的一个概念。咱们有必要去了解,请你们多指教。app
目的:简化代码逻辑和结构,实现代码重用函数
接下来咱们一块儿学习下 8 种 JavaScript
实现继承的方法。post
推荐组合继承(四)、寄生组合式继承(七)、ES6 继承(八)
基本思想是利用原型让一个引用类型继承另外一个引用类型的方法和实例。
function staff(){
this.company = 'ABC';
}
staff.prototype.companyName = function(){
return this.company;
}
function employee(name,profession){
this.employeeName = name;
this.profession = profession;
}
// 继承 staff
employee.prototype = new staff();
// 将这个对象的 constructor 手动改为 employee,不然还会是 staff
employee.prototype.constructor = employee;
// 不使用对象字面量方式建立原型方法,会重写原型链
employee.prototype.showInfo = function(){
return this.employeeName + "'s profession is " + this.profession;
}
let instance = new employee('Andy','front-end');
// 测试
console.log(instance.companyName()); // ABC
console.log(instance.showInfo()); // "Andy's profession is front-end"
// 经过 hasOwnProperty() 方法来肯定自身属性与其原型属性
console.log(instance.hasOwnProperty('employeeName')) // true
console.log(instance.hasOwnProperty('company')) // false
// 经过 isPrototypeOf() 方法来肯定原型和实例的关系
console.log(employee.prototype.isPrototypeOf(instance)); // true
console.log(staff.prototype.isPrototypeOf(instance)); // true
console.log(Object.prototype.isPrototypeOf(instance)); // true
复制代码
原型链实现继承最大的问题是:
当原型中存在引用类型值时,实例能够修改其值。
function staff(){
this.test = [1,2,3,4];
}
function employee(name,profession){
this.employeeName = name;
this.profession = profession;
}
employee.prototype = new staff();
let instanceOne = new employee();
let instanceTwo = new employee();
instanceOne.test.push(5);
console.log(instanceTwo.test); // [1, 2, 3, 4, 5]
复制代码
鉴于此问题:因此咱们在实践中会少单独使用原型链实现继承。
hasOwnProperty()
方法来肯定自身属性与其原型属性isPrototypeOf()
方法来肯定原型和实例的关系此方法和方法一区别就是将:
employee.prototype = new staff();
复制代码
改为:
Employee.prototype = Person.prototype;
复制代码
此方法能够解决原型中引用类型值被修改的问题
function staff(){
this.test = [1,2,3];
}
staff.prototype.companyName = function(){
return this.company;
}
function employee(name,profession){
staff.call(this);
this.employeeName = name;
this.profession = profession;
}
// 不使用对象字面量方式建立原型方法,会重写原型链
employee.prototype.showInfo = function(){
return this.employeeName + "'s profession is " + this.profession;
}
let instanceOne = new employee('Andy','front-end');
let instanceTwo = new employee('Mick','after-end');
instanceOne.test.push(4);
// 测试
console.log(instanceTwo.test); // [1,2,3]
// console.log(instanceOne.companyName()); // 报错
// 经过 hasOwnProperty() 方法来肯定自身属性与其原型属性
console.log(instanceOne.hasOwnProperty('test')) // true
// 经过 isPrototypeOf() 方法来肯定原型和实例的关系
console.log(staff.prototype.isPrototypeOf(instanceOne)); // false
复制代码
从上面的结果能够看出:
instanceOne
与 staff
已经没有原型链的关系了指的是将原型链技术和借用构造函数技术结合起来,两者皆取其长处的一种经典继承方式。
function staff(){
this.company = "ABC";
this.test = [1,2,3];
}
staff.prototype.companyName = function(){
return this.company;
}
function employee(name,profession){
// 继承属性
staff.call(this);
this.employeeName = name;
this.profession = profession;
}
// 继承方法
employee.prototype = new staff();
employee.prototype.constructor = employee;
employee.prototype.showInfo = function(){
return this.employeeName + "'s profession is " + this.profession;
}
let instanceOne = new employee('Andy','front-end');
let instanceTwo = new employee('Mick','after-end');
instanceOne.test.push(4);
// 测试
console.log(instanceTwo.test); // [1,2,3]
console.log(instanceOne.companyName()); // ABC
// 经过 hasOwnProperty() 方法来肯定自身属性与其原型属性
console.log(instanceOne.hasOwnProperty('test')) // true
// 经过 isPrototypeOf() 方法来肯定原型和实例的关系
console.log(staff.prototype.isPrototypeOf(instanceOne)); // true
复制代码
staff
会被调用 2 次:第 1 次是employee.prototype = new staff();
,第 2 次是调用 staff.call(this)
。利用一个临时性的构造函数(空对象)做为中介,将某个对象直接赋值给构造函数的原型。
function object(obj){
function F(){}
F.prototype = obj;
return new F();
}
复制代码
本质上 object()
对传入其中的对象执行了一次浅复制,将构造函数 F
的原型直接指向传入的对象。
var employee = {
test: [1,2,3]
}
let instanceOne = object(employee);
let instanceTwo = object(employee);
// 测试
instanceOne.test.push(4);
console.log(instanceTwo.test); // [1, 2, 3, 4]
复制代码
另,ES5 中存在 Object.create()
的方法规范化了原型式继承,可以代替 object
方法。
要点:在原型式继承的基础上,经过封装继承过程的函数加强对象,返回对象
function createAnother(original){
var clone = object(original); // 经过调用 object() 函数建立一个新对象
clone.sayHi = function(){ // 以某种方式来加强对象
alert("hi");
};
return clone; // 返回这个对象
}
复制代码
createAnother
函数的主要做用是为构造函数新增属性和方法,以加强函数。
该方法主要是解决组合继承调用两次超类构造函数的问题。
function inheritPrototype(sub, super){
var prototype = Object.create(super.prototype); // 建立对象,父原型的副本
prototype.constructor = sub; // 加强对象
sub.prototype = prototype; // 指定对象,赋给子的原型
}
function staff(){
this.company = "ABC";
this.test = [1,2,3];
}
staff.prototype.companyName = function(){
return this.company;
}
function employee(name,profession){
staff.call(this, name);
this.employeeName = name;
this.profession = profession;
}
// 将父类原型指向子类
inheritPrototype(employee,staff)
let instanceOne = new employee("Andy", "A");
let instanceTwo = new employee("Rose", "B");
instanceOne.test.push(4);
// 测试
console.log(instanceTwo.test); // [1,2,3]
console.log(instanceOne.companyName()); // ABC
// 经过 hasOwnProperty() 方法来肯定自身属性与其原型属性
console.log(instanceOne.hasOwnProperty('test')) // true
// 经过 isPrototypeOf() 方法来肯定原型和实例的关系
console.log(staff.prototype.isPrototypeOf(instanceOne)); // true
复制代码
开发人员广泛认为寄生组合式继承是引用类型最理想的继承范式,
Class 能够经过 extends
关键字实现继承,这比 ES5 的经过修改原型链实现继承,要清晰和方便不少。
class staff {
constructor(){
this.company = "ABC";
this.test = [1,2,3];
}
companyName(){
return this.company;
}
}
class employee extends staff {
constructor(name,profession){
super();
this.employeeName = name;
this.profession = profession;
}
}
// 将父类原型指向子类
let instanceOne = new employee("Andy", "A");
let instanceTwo = new employee("Rose", "B");
instanceOne.test.push(4);
// 测试
console.log(instanceTwo.test); // [1,2,3]
console.log(instanceOne.companyName()); // ABC
// 经过 Object.getPrototypeOf() 方法能够用来从子类上获取父类
console.log(Object.getPrototypeOf(employee) === staff)
// 经过 hasOwnProperty() 方法来肯定自身属性与其原型属性
console.log(instanceOne.hasOwnProperty('test')) // true
// 经过 isPrototypeOf() 方法来肯定原型和实例的关系
console.log(staff.prototype.isPrototypeOf(instanceOne)); // true
复制代码
super
关键字,它在这里表示父类的构造函数,用来新建父类的 this
对象。
- 子类必须在
constructor
方法中调用super
方法,不然新建实例时会报错。这是由于子类没有本身的this
对象,而是继承父类的this
对象,而后对其进行加工。- 只有调用
super
以后,才可使用this
关键字,不然会报错。这是由于子类实例的构建,是基于对父类实例加工,只有super
方法才能返回父类实例。
`super` 虽然表明了父类 `A` 的构造函数,可是返回的是子类 `B` 的实例,即` super` 内部的 `this ` 指的是 `B`,所以 `super()` 在这里至关于 A.prototype.constructor.call(this)
ES5 的继承,实质是先创造子类的实例对象 this
,而后再将父类的方法添加到 this
上面(Parent.apply(this)
)。
ES6 的继承机制彻底不一样,实质是先创造父类的实例对象 this
(因此必须先调用 super()
方法),而后再用子类的构造函数修改 this
。
function _inherits(subType, superType) {
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
value: subType,
enumerable: false,
writable: true,
configurable: true
}
});
if (superType) {
Object.setPrototypeOf
? Object.setPrototypeOf(subType, superType)
: subType.__proto__ = superType;
}
}
复制代码
由此能够看出:
__proto__
属性,表示构造函数的继承,老是指向父类。prototype
属性的 __proto__
属性,表示方法的继承,老是指向父类的 prototype
属性。另:ES6 能够自定义原生数据结构(好比Array、String等)的子类,这是 ES5 没法作到的。
以上八种继承方式是比较常见的继承方式,假若了解了这些方式的机制,在之后的面试中原型链与继承的问题也就不在话下了。
先后写了两个多星期,最主要的缘由宝宝刚进入个人生活,无休的照顾宝宝,换尿布、喂奶、换衣之类花费了大量精力和时间。这篇文章也是在宝宝睡觉的间隙写成的,文章的内容若是以为简陋,也请你们多包涵,提出宝贵的意见,往后有时间必定修改。
新年伊始,不忘初心
《前端词典》这个系列会持续更新,每一期我都会讲一个出现频率较高的知识点。但愿你们在阅读的过程中能够斧正文中出现不严谨或是错误的地方,本人将不胜感激;若经过本系列而有所得,本人亦将不胜欣喜。
若是你以为个人文章写的还不错,能够关注个人微信公众号,公众号里会提早剧透呦。
你也能够添加个人微信 wqhhsd, 欢迎交流。
【前端词典】前端须要理解的网络基础