原型鏈繼承
方法:子構(gòu)造函數(shù)的prototype指向?yàn)楦笜?gòu)造函數(shù)的實(shí)例,因?yàn)樵玩準(zhǔn)?strong>proto的鏈表,父構(gòu)造函數(shù)的實(shí)例的proto指向父構(gòu)造函數(shù)實(shí)例的原型。
function Parent(){
this.name = 'johe'
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(){
}
//原型必須是對(duì)象,所以為Parent的實(shí)例
Child.prototype = new Parent()
var child1 = new Child()
child1.getName()
問(wèn)題:
- 引用類型的屬性被所有實(shí)例共享
- 創(chuàng)建Child的實(shí)例時(shí),不能向parent傳參
function Parent () {
this.names = ['kevin', 'daisy'];
}
function Child () {
}
Child.prototype = new Parent();
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy", "yayu"]
這是因?yàn)镻arent在實(shí)例化之后,成為了Child的原型不狮,原型上的屬性和方法是共享的。
借用構(gòu)造函數(shù)(經(jīng)典繼承)
調(diào)用父構(gòu)造函數(shù)
function Parent(){
this.names = ['kevin','daisy'];
}
function Child(){
Parent.call(this);
}
var Child1 = new Child()
Child1.names.push('johe')
var Child2 = new Child()
//['kevin','daisy']
Child2.names
優(yōu)點(diǎn):
- 避免了引用類型的屬性被所有實(shí)例共享
- 可以在Child中向Parent傳參
缺點(diǎn): - 方法都在構(gòu)造函數(shù)中定義,每次創(chuàng)建實(shí)例都會(huì)創(chuàng)建一遍方法
- instanceof檢驗(yàn)為false
function Parent(name){
this.name = name;
}
function Child(name){
Parent.call(this,name)
}
var Child1 = new Child('johe')
//johe
Child1.name
組合繼承(原型鏈繼承和經(jīng)典繼承)
優(yōu)點(diǎn):借用構(gòu)造函數(shù)繼承解決了傳參問(wèn)題和實(shí)例屬性被共享的問(wèn)題续室,原型鏈繼承能夠滿足共享方法不被重復(fù)創(chuàng)建。
缺點(diǎn):調(diào)用了兩次父構(gòu)造函數(shù)
function Parent(name){
this.name = name
this.names = ['johe']
}
Parent.prototype.getName = function(){
return this.name
}
function Child(name,age){
//調(diào)用父級(jí)的構(gòu)造方法谒养,實(shí)現(xiàn)實(shí)例屬性
Parent.call(this,name)
this.age = age
}
//這里不用參數(shù)是因?yàn)樽訕?gòu)造函數(shù)調(diào)用父構(gòu)造函數(shù)時(shí)已實(shí)現(xiàn)實(shí)例屬性挺狰,有實(shí)例屬性的情況下不會(huì)從原型鏈中查找
Child.prototype = new Parent()
var child1 = new Child('johe',18)
child1.names.push("johe2")
console.log(child1.name);//johe
console.log(child1.age);18
//["johe","johe2"]
console.log(child1.names);
var child2 = new Child('child2',19)
console.log(child2.name);//child2
console.log(child2.age);19
//["johe"]
console.log(child1.names);
原型式繼承(Object.create)
Object.create的模擬實(shí)現(xiàn)
function createObj(o){
function F();
F.protoype = o;
return new F()
}
缺點(diǎn):
包含引用類型的屬性值始終都會(huì)共享響應(yīng)的值,這點(diǎn)跟原型鏈繼承一樣买窟。
寄生組合式繼承(組合繼承優(yōu)化)
組合繼承的最大缺點(diǎn)就是會(huì)調(diào)用兩次父構(gòu)造函數(shù)
一次是設(shè)置子類型實(shí)例的原型:
Child.protoype = new Parent()
一次是創(chuàng)建子類型實(shí)例的時(shí)候:
function Child(name,age){
Parent.call(this,name)
}
var child1 = new Child('johe',18)
這個(gè)時(shí)候Child.prototype這個(gè)對(duì)象內(nèi)的屬性其實(shí)是沒用的丰泊,因?yàn)樽宇愋蛯?shí)例已經(jīng)調(diào)用了父構(gòu)造函數(shù)進(jìn)行了屬性實(shí)例化。
所以就用到了寄生組合式繼承始绍,讓Child.prototype間接的訪問(wèn)到Parent.prototype
function Parent(name){
this.name = name
this.names = ['johe']
}
Parent.prototype.getName = function(){return this.name}
function Child(name,age){
Parent.call(this,name)
this.age = age
}
function F(){}
F.prototype = Parent.prototype
Child.prototype = new F()
var child1 = new Child('johe',18)
封裝繼承方法:
function Parent(name){
this.name = name
this.names = ['johe']
}
Parent.prototype.getName = function(){return this.name}
function Child(name,age){
Parent.call(this,name)
this.age = age
}
function createObject(o){
function F(){}
F.prototype = o
return new F();
}
function setPrototype(child,parent){
var prototype = createObject(parent)
prototype.constructor = Child
Child.protoype = prototype
}
setPrototype(Child,Parent)
這種方式的高效率體現(xiàn)它只調(diào)用了一次 Parent 構(gòu)造函數(shù)瞳购,并且因此避免了在 Parent.prototype 上面創(chuàng)建不必要的、多余的屬性亏推。與此同時(shí)学赛,原型鏈還能保持不變;因此吞杭,還能夠正常使用 instanceof 和 isPrototypeOf盏浇。開發(fā)人員普遍認(rèn)為寄生組合式繼承是引用類型最理想的繼承范式
這里為什么不直接使用Child.prototype=Parent.prototype,是因?yàn)镻arent.constructor應(yīng)該指向Parent,并且如果我們需要給Child實(shí)例的原型設(shè)置方法和屬性時(shí)篇亭,會(huì)影響到Parent的實(shí)例缠捌,這明顯是不合理的。
ES6的繼承
ECMAScript6 引入了一套新的關(guān)鍵字用來(lái)實(shí)現(xiàn) class译蒂。使用基于類語(yǔ)言的開發(fā)人員會(huì)對(duì)這些結(jié)構(gòu)感到熟悉曼月,但它們是不同的。JavaScript 仍然基于原型柔昼。這些新的關(guān)鍵字包括 class, constructor哑芹,static,extends 和 super捕透。
class Fruits{
constructor(type,size){
this.type = type;
this.size = size;
}
static sayHello(){
console.log("hello");
}
sayType(){
console.log('my type is '+this.type);
return this.type;
}
}
class Banana extends Fruits{
constructor(type,size,color){
super(type,size);
this.color = color;
}
sayColor(){
console.log('my color is '+ this.color);
return this.color;
}
}
let fruit = new Fruits("fruit","small");
let banana = new Banana("banana","small","yellow");
//parent: Fruits {type:'fruit',size:'small'}
console.log('parent:',fruit);
//child: Banana {type:'banana',size:'small',color:'yellow' }
console.log('child:',banana);
//hello
console.log(Fruits.sayHello());
console.log(Banana.sayHello());
//true
console.log(Fruits.hasOwnProperty("sayHello"));
//false
console.log(Banana.hasOwnProperty("sayHello"));
//false
console.log(fruit.hasOwnProperty("sayType"))
//true
console.log(Fruits.prototype.hasOwnProperty("sayType"));
//my type is banana
console.log(banana.sayType());
//false
console.log(banana.hasOwnProperty("sayType"));
//Fruits {}
console.log(banana.__proto__);
首先查看class語(yǔ)法糖做了什么:
- 將構(gòu)造函數(shù)內(nèi)的this屬性實(shí)例化
- 將構(gòu)造函數(shù)外的非static函數(shù)給設(shè)置到構(gòu)造函數(shù)的原型對(duì)象中聪姿,共享屬性
- static函數(shù)設(shè)置為構(gòu)造函數(shù)的屬性
用ES5實(shí)現(xiàn)就是組合方式來(lái)創(chuàng)建對(duì)象:
function Fruits(type,size){
this.type = type;
this.size = size;
}
Fruits.sayHello = function(){
console.log('Hello');
}
Fruits.prototype = {
constructor:Fruits,
sayType:function(){
console.log('my type is' +this.type);
return this.type;
}
}
再看看extends做了什么:
- 繼承父類的實(shí)例屬性(調(diào)用父類的構(gòu)造函數(shù))
- 繼承父類的共享屬性(原型鏈上有父類的原型對(duì)象)
- 子類構(gòu)造函數(shù)的proto指向父類構(gòu)造函數(shù)(兩個(gè)都是對(duì)象)(繼承靜態(tài)函數(shù))
//true
console.log(Banana.__proto === Fruits);
//true
console.log(banana instance of Fruits);
es6繼承的es5實(shí)現(xiàn)
知道extends做了什么之后碴萧,我們可以知道其轉(zhuǎn)化成es5就是寄生組合式繼承(組合=原型鏈繼承+借用構(gòu)造函數(shù)),并且設(shè)置子類構(gòu)造函數(shù)的proto為父類構(gòu)造函數(shù).
function Fruits(type,size){
this.type = type;
this.size = size;
}
Fruits.sayHello = function(){
console.log('Hello');
}
Fruits.prototype = {
constructor:Fruits,
sayType:function(){
console.log('my type is' +this.type);
return this.type;
}
}
function Banana(type,size,color){
Fruits.call(this,type,size);
this.color = color;
}
function createObject(Parent){
function Empty(){};
Empty.prototype = Parent.prototype;
return new Empty();
}
Banana.prototype = createObject(Fruits);
Banana.prototype.constructor = Banana;
Banana.prototype.sayColor = function(){
console.log(this.color);
}
Banana.__proto__ = Fruits;
通過(guò)babeljs轉(zhuǎn)碼成ES5來(lái)查看末购,更嚴(yán)謹(jǐn)?shù)膶?shí)現(xiàn)破喻。
//es6版本
class Parent{
constructor(name){
this.name = name;
}
static sayHello(){
console.log('hello');
}
sayName(){
console.log('my name is ' + this.name);
return this.name;
}
}
class Child extends Parent{
constructor(name, age){
super(name);
this.age = age;
}
sayAge(){
console.log('my age is ' + this.age);
return this.age;
}
}
// 對(duì)轉(zhuǎn)換后的代碼進(jìn)行了簡(jiǎn)要的注釋
"use strict";
// 主要是對(duì)當(dāng)前環(huán)境支持Symbol和不支持Symbol的typeof處理
function _typeof(obj) {
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function _typeof(obj) {
return typeof obj;
};
} else {
_typeof = function _typeof(obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
// _possibleConstructorReturn 判斷Parent。call(this, name)函數(shù)返回值 是否為null或者函數(shù)或者對(duì)象盟榴。
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
// 如何 self 是void 0 (undefined) 則報(bào)錯(cuò)
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
// 獲取__proto__
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
// 寄生組合式繼承的核心
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
// Object.create()方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來(lái)提供新創(chuàng)建的對(duì)象的__proto__擎场。
// 也就是說(shuō)執(zhí)行后 subClass.prototype.__proto__ === superClass.prototype; 這條語(yǔ)句為true
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
// 設(shè)置__proto__
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
// instanceof操作符包含對(duì)Symbol的處理
function _instanceof(left, right) {
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
return right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}
function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
// 設(shè)置共享屬性和靜態(tài)屬性到不同的對(duì)象上
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
//將共享屬性設(shè)置到原型對(duì)象上,靜態(tài)屬性設(shè)置到構(gòu)造函數(shù)上
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
// ES6
var Parent = function () {
function Parent(name) {
_classCallCheck(this, Parent);
this.name = name;
}
_createClass(Parent, [{
key: "sayName",
value: function sayName() {
console.log('my name is ' + this.name);
return this.name;
}
}], [{
key: "sayHello",
value: function sayHello() {
console.log('hello');
}
}]);
return Parent;
}();
var Child = function (_Parent) {
_inherits(Child, _Parent);
function Child(name, age) {
var _this;
_classCallCheck(this, Child);
// Child.__proto__ => Parent
// 所以也就是相當(dāng)于Parent.call(this, name); 是super(name)的一種轉(zhuǎn)換
// _possibleConstructorReturn 判斷Parent.call(this, name)函數(shù)返回值 是否為null或者函數(shù)或者對(duì)象姨夹。
_this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name));
_this.age = age;
return _this;
}
_createClass(Child, [{
key: "sayAge",
value: function sayAge() {
console.log('my age is ' + this.age);
return this.age;
}
}]);
return Child;
}(Parent);
var parent = new Parent('Parent');
var child = new Child('Child', 18);
console.log('parent: ', parent); // parent: Parent {name: "Parent"}
Parent.sayHello(); // hello
parent.sayName(); // my name is Parent
console.log('child: ', child); // child: Child {name: "Child", age: 18}
Child.sayHello(); // hello
child.sayName(); // my name is Child
child.sayAge(); // my age is 18
從babel轉(zhuǎn)譯我們可以認(rèn)識(shí)到幾點(diǎn):
- 盡量使用Object.create來(lái)創(chuàng)造父構(gòu)造函數(shù)的實(shí)例
//創(chuàng)建一個(gè)superClass的實(shí)例,constructor屬性指向subClass
subClass.prototype = Object.create(superClass&&superClass.prototype,{
constructor:{
value:subClass,
writable:true,
configurable:trues
}
});
//替代方案
function Empty(){};
Empty.prototype = superClass.prototype;
subClass.protoype = new Empty();
subClass.protoype.constructor = subClass;
- 盡量使用Object.setPrototypeOf而不是proto
Object.setPrototypeOf(subClass,superClass);
//替代方案榄鉴,不推薦
subClass.__proto__ = superClass.__proto__
模擬一下轉(zhuǎn)換實(shí)現(xiàn):
class Fruit{
constructor(name){
this.name = name;
}
static sayHello(){
console.log("hello");
}
sayName(){
console.log(this.name);
}
}
class Banana extends Fruit{
constructor(name,color){
super(name);
this.color = color;
}
sayColor(){
console.log(this.color);
}
}
//轉(zhuǎn)換實(shí)現(xiàn):
function createPropertiesByObject(target,props){
for(var i =0;i<props.length;i++){
let descriptor = props[i];
//省略
Object.defineProperties(target,descriptor.key,descriptor);
}
}
function createProperties(target,protoProps,staticProps){
createPropertiesByObject(target.prototype,protoProps);
createPropertiesByObject(target,staticProps);
}
function createPolyFill(Parent){
function Empty(){};
Empty.protoype = Parent.prototype;
return new Empty();
}
function inherit(Child,Parent){
if(typeof Child!=='function'||typeof Parent!=='function'){
throw new Error("");
}
Child.prototype = Object.create ? Object.create(Parent&&Parent.prototype,{
constructor:{
value:Child,
writable:true,
configurable:true
}
}) : createPolyFill(Parent);
if(!Object.create){
Child.prototype.constructor = Child;
}
}
function setPrototypeOf(Child,Parent){
setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf : function(C,P){
C.__proto__ = P
}
setPrototypeOf(Child,Parent);
}
var Fruit = function(){
function Fruit(){
this.name = name;
}
createProperties(Fruit,[{
key:"sayName",
value:function(){
console.log(this.name)
}
}],[{
key:"sayHello",
value:function(){
console.log("hello");
}
}])
return Fruit;
}()
var Banana = function(Parent){
inherit(Banana,Parent);
function Banana(name,color){
Fruit.call(name);
this.color = color;
}
createProperties(Banana,[{
key:"sayColor",
value:function(){
console.log(this.color)
}
}])
setPrototypeOf(Banana,Parent);
return Banana;
}(Fruit)