segmentfault官方|ES5 继承
_本文原题:ES5 继承
作者:assassin cike
来源:SegmentFault 思否社区
首先要明白两点:
一、非方法属性每个子类实例需要独立
二、方法属性每个子类实例需要共享
为什么?
如果非方法属性为引用类型 , 且非方法属性共享 , 在一个实例中改变 , 其他实例中就会做出改变 , 这样每个实例就会相互影响 , 而方法属性一般是不需要进行改变的 , 只是对方法调用 。
- 方法跟属性分别可以定义在构造函数内部跟prototype上 。
- 继承的目的是子类继承父类的方法跟属性 。
- 代码主要来自于红宝书4
每个函数都有个prototype属性,每个对象都有__proto__属性(在chrome中表现如此,prototype也是如此) 如图 , 属性的查找会从当前层级依次向原型链上查找 , 直到查找到原型链的顶端null , 具体可参考:
js proto(https://www.jianshu.com/p/cd26f07df9ba)
本文插图
既然属性的查找是按照原型链向上查找的 , 且继承就是继承父类的属性跟方法 , 那么就可以利用这个特性 , 进行继承 。
functionSuperType{
this.property = true;
}
SuperType.prototype.getSuperValue = http://news.hoteastday.com/a/function{
returnthis.property;
};
functionSubType{
this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType;
SubType.prototype.getSubValue = http://news.hoteastday.com/a/function{
returnthis.subproperty;
};
letinstance = new SubType;
console.log(instance.getSuperValue); // true可以正确调用父类的方法 , 拿到父类的属性
原型虽然实现了继承 , 但是还是有缺点的
劣势:
1. 子类或者父类的属性为引用类型时 , 改变一个实例的引用类型属性 , 其他实例的该引用类型属性也会发生改变 , 这样其实例就会相互污染了 。
functionSuperType{
this.colors = [ "red", "blue", "green"];
}
functionSubType{} // 继承SuperType
SubType.prototype = new SuperType;
letinstance1 = new SubType;
instance1.colors.push( "black");
console.log(instance1.colors);
// "red,blue,green,black";
letinstance2 = new SubType;
console.log(instance2.colors);
// "red,blue,green,black";
为什么非方法属性不写在prototype上?
因为prototype上的属性的共享的 , 在一个实例上改了该属性 , 其他实例的该属性也会被改掉 。
为什么方法不写在构造函数内部?
- 方法写在子类内部:每次实例化构造函数 , 方法都是新的;方法只是用来调用 , 不需要修改 , 所以实例共享就行了 。
- 方法写在父类内部:不同的子类继承父类都需要实例化父类;方法只是用来调用 , 不需要做修改 , 所以实例共享就行了 , 包括子类实例 。 如果子类需要修改父类方法 , 直接在子类中定义相同方法名 , 进行覆盖就行了 。
盗用构造函数
为了解决父类中属性为引用类型导致子类实例化后 , 引用属性共享的问题 , 跟父类构造函数无法传参的问题 。 引入了“盗用构造函数“方式实现继承 。 思路是在子类构造函数中调用父类构造函数 。
- 不同实例的引用属性不会相互影响 分页标题
this.colors = [ "red", "blue", "green"];
}
functionSubType{
// 继承SuperType
SuperType.call(this);
}
letinstance1 = new SubType;
instance1.colors.push( "black");
console.log(instance1.colors);
// "red,blue,green,black";
letinstance2 = new SubType;
console.log(instance2.colors);
// "red,blue,green";
instance1 instance2两个实例就不会相互影响 。
- 可以为父类构造函数传参
this.name = name;
}
functionSubType(name) {
// 继承SuperType并传参
SuperType.call(this, name);
// 实例属性
this.age = 29;
}
letinstance = new SubType( "geek");
console.log(instance.name); // "geek";
console.log(instance.age); // 29
动态传递参数到父类构造函数
劣势:
- 定义在父类prototype上的方法 , 子类无法继承
this.name = name;
}
SuperType.prototype.say = function{
console.info( "hello");
};
functionSubType(name) {
// 继承SuperType并传参
SuperType.call(this, name);
// 实例属性
this.age = 29;
}
letinstance = new SubType( "geek");
console.log(instance.name); // "geek";
console.log(instance.age); // 29
instance.say // 获取不到该函数
通过 new 实例化后 , 实例才能拿到prototype上的方法 , a.__proto__===Animal.prototype , 所以instance.say不存在
- 定义在父类构造函数中方法无法共享
组合继承
组合继承兼顾原型链继承跟盗用构造函数的优点 , 这样既可以把方法定义在原型上以实现重用 , 又可以看让每个实力都有自己的属性 。
functionSuperType(name) {
this.name = name;
this.colors = [ "red", "blue", "green"];
}
SuperType.prototype.sayName = function{
console.log(this.name);
};
functionSubType(name, age) {
// 继承属性 , 绑定上下文为SubType的实例
SuperType.call(this, name);
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType;
SubType.prototype.sayAge = function{
console.log(this.age);
};
letinstance1 = new SubType( "Nicholas", 29);
instance1.colors.push( "black");
console.log(instance1.colors);
// "red,blue,green,black"
instance1.sayName; // "Nicholas";
instance1.sayAge; // 29
letinstance2 = new SubType( "Greg", 27);
console.log(instance2.colors);
// "red,blue,green";
instance2.sayName; // "Greg";
instance2.sayAge; // 27
- 可以传递参数到父类构造函数
- 两个实例中的引用类型不会相互影响
- 实例可以调用父类的方法 , 且实现方法的共享
SuperType会被调用两次 , SubType实例跟原型链上都有name跟colors属性 。
原型式继承
不定义构造函数通过原型实现对象之前的继承 。
functionobject(o) {分页标题
functionF{}
F.prototype = o;
returnnew F;
}
返回新对象 , 让其原型指向O
letperson = {
name: "Nicholas",
【segmentfault官方|ES5 继承】friends: [ "Shelby", "Court", "Van"],
};
letanotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push( "Rob");
letyetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push( "Barbie");
console.log(person.friends);
// "Shelby,Court,Van,Rob,Barbie";
父对象中的引用属性会在子对象中共享 , 导致相互污染 。
ES5引入了Object.create规范了原型式继承 。
letperson = {
name: "Nicholas",
friends: [ "Shelby", "Court", "Van"],
};
letanotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push( "Rob");
letyetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push( "Barbie");
console.log(person.friends);
// "Shelby,Court,Van,Rob,Barbi
使用Object.create后anotherPerson.__proto__===person成立 , 所以anotherPerson可以拿到person的属性 , 但是同样存在父对象属性共享的问题 , 改了父对象的属性 , 其他的子对象都跟着改变 。
劣势:
父对象的引用类型会在实例中共享 , 这样就会相互污染 。
寄生式继承
寄生式继承跟原型式继承很类似 , 用工厂函数在对返回的新对象包一层 , 给新对象赋值一些属性
工厂函数的定义:
functioncreateAnother(original) {
letclone= object(original);
// 通过调用函数创建一个新对象;
clone.sayHi = function{
// 以某种方式增强这个对象;
console.log( "hi");
};
returnclone; // 返回这个对象
}
使用:
letperson = {
name: "Nicholas",
friends: [ "Shelby", "Court", "Van"],
};
letanotherPerson = createAnother(person);
anotherPerson.sayHi; // "hi"
定义在新对象上的sayHi方法 , 每次调用新对象都是新的 , 无法实现共享 。
劣势:
- 父对象的引用类型会在实例中共享 , 这样就会相互污染 。
- 方法无法实现共享
上面提到 , 组合继承的缺点就是父类构造函数会被调用两次 , 一次是在子类的构造函数中 , 另一次在创建子类原型时调用 。 继承就是要继承父类的属性跟方法 , 组合继承实现了这个目标 , 但是怎么避免重复调用父类构造函数 。
先看下组合继承:
functionSuperType(name) {
this.name = name;
this.colors = [ "red", "blue", "green"];
}
SuperType.prototype.sayName = function{
console.log(this.name);
};
functionSubType(name, age) {
SuperType.call(this, name); // 第二次调用 , 将父类的属性绑定到子类的实例中
SuperType;
this.age = age;
}
SubType.prototype = new SuperType;
// 第一次调用SuperType;
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function{
console.log(this.age);
};
本文插图
分页标题
由图可见s的原型链上依然有name跟colors属性 。 这也是不需要的 。 怎么解决这两个问题?
父类的属性是需要的 , 父类的原型上的方法是需要的 , 重复的父类属性不需要 , 由上图可见重复的父类属性是由于实例化父类给子类原型造成的 , 我们不去实例化父类 , 而是将父类的原型传递给子类的原型就行了 , 结合原型式继承特点可以做到
functionSuperType(name) {
this.name = name;
this.colors = [ "red", "blue", "green"];
}
SuperType.prototype.sayName = function{
console.log(this.name);
};
functionSubType(name, age) {
SuperType.call(this, name); // 将父类的属性绑定到SubType实例中
this.age = age;
}
SubType.prototype = Object.create(SuperType.prototype);
// 将子类的prototype关联到父类的prototype上
SubType.prototype.sayAge = function{
console.log(this.age);
};
使用Object.create解决了父类构造函数调用两次 , 父类属性重复的问题 , 但是子类constructor并没有出现在原型链中
本文插图
下面做出改造:
SubType.prototype = Object.create(SuperType.prototype, {
constructor: {
value: SubType, // 修正 constructor 指向
writable: true,
configurable: true,
},
});
本文插图
SuperType的constructor出现了 , 其实constructor并没什么用 , 只是个约定罢了,参考贺老的解释Java 中对象的 constructor 属性的作用是什么?(https://www.zhihu.com/question/19951896/answer/13457869)
instanceof操作符和 isPrototypeOf 方法正常有效 。 寄生式组合继承可以
算是引用类型继承的最佳模式
- 雪佛兰|雪佛兰推出50项车主特权,发布官方认证二手车平台
- 阿言爱汽车|官方权威认证,2021款WEY VV6儿童呵护版击中宝爸红心
- 苏州日报官方微信|全国百强!苏州三地上榜!
- 航空知识官方账号|一句话证明你是飞行员
- 金陵晚报官方微信|昨晚,新一代青年作家在南京闪耀登场!
- 喃喃话车|沃尔沃生命奇迹俱乐部,一个门槛非常高的官方组织!
- 无锡天一中医医院官方账号|你的身体还年轻吗?从头到脚,一个自测小方法在家就能做
- 郑州同城官方号|单身推荐|87年帅哥月薪上万,期待一屋两人三餐四季的生活
- 郑州同城官方号|单身推荐|92年小姐姐身高165,想找175以上的对象
- |你的青春回来了!“小霸王”申请破产?官方澄清:与我无关