js原型、原型链及继承实现方式

js继承主要包括原型链继承、借用构造函数继承、组合继承(原型链+构造函数)、组合继承优化、寄生组合继承5中方式,后3中都是由前两个组合优化而来,所以要先了解原型及原型链相关内容。
1、原型对象及原型链
在js中,一切皆对象,所以原型也是一个对象,称为原型对象。
在js中,每个函数类型的数据,都有一个prototype的属性,该属性所指向的对象就是原型对象。对于原型对象而言,其constructor属性则指回构造函数(构造函数与普通函数本质上没有差别,只是可以用new 关键字创建对象)。原型对象最主要的作用是存放对象的共有属性和共有方法。
构造函数类似java中的类,每次创建实例对象时,都会重复创建一次构造函数里面的属性和方法,这就有些浪费了。如果把共有属性和方法定义到构造函数的原型对象里,各个实例对象直接原型对象的方法即可(通过原型链实现),避免浪费空间。
示例:

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.say = function () {
    console.log("构造函数方法--- ", this.name, this.age);
  }
}
Person.prototype.sex = "女";
Person.prototype.color = ["yellow", "red", "blue"];
Person.prototype.hello = function () {
  console.log("原型对象方法---- hello ", this.sex);
}
let p1 = new Person("dlj1", 20);
let p2 = new Person("dlj2", 30);
console.log("p1 is ", p1);
console.log("p2 is ", p2);
p2.hello();
console.log("p1.color ", p1.color.join("/"));
console.log("person 原型对象 ", p1.__proto__, p1.constructor.prototype == p1.__proto__, p1.__proto__ == Person.prototype, Person.prototype.constructor == p1.constructor);
console.log("person 原型对象上级原型对象包含join方法 ", p1.color.__proto__.hasOwnProperty("join"));

image.png

从上面的示例可以看出,实例对象中没有sex属性和hello方法,但是并不影响p2调用hello方法,就是因为当实例对象内没有调用的方法或属性时,就会向对应构造函数的原型对象中查找,如果原型对象中也没有,则会继续向原型对象的原型对象上查找,直到最顶级Object.prototype(所有原型对象都是object的实例),如果最顶级没有找到则返回undefined,这个顺着proto逐步向上查找,形成了像链条一样的结构就叫原型链(也叫隐式原型链)。例如p1的原型对象中没有join方法,p1.color可以调用join就是因为上级原型对象(array)中包含join方法。

2、继承方式介绍
1)原型链继承
将父类实例作为子类原型,由于方法定义在父类原型上,复用了父类构造函数的方法,所以实现了方法复用。
示例:

function Person(name) {
  this.name = name || "父类";
  this.say = function () {
    console.log("父类构造函数方法--- ", this.name);
  }
}
Person.prototype.sex = "女";
Person.prototype.color = ["yellow", "red", "blue"];
Person.prototype.hello = function () {
  console.log("父类原型对象方法---- hello ", this.sex);
}
function Child(age) {
  this.age = age;
}
Child.prototype = new Person("dlj");
Child.prototype.constrctor = Child;
Child.prototype.address = "北京";
Child.prototype.cHello = function () {
  console.log("子类原型对象方法---- chello ", this.sex);
}
let chd1 = new Child("22");
console.log(chd1.say());
console.log(chd1.hello());
console.log(chd1.cHello());
console.log("age name sex --- ", chd1.age, chd1.name, chd1.sex);
console.log("chd1  ", chd1);
console.log("chd1.__proto__   ", chd1.__proto__);
console.log("chd1.__proto__.__proto__   ", chd1.__proto__.__proto__);
console.log("chd1.__proto__  has color is ", chd1.__proto__.hasOwnProperty("color"));
chd1.color.push("aa");
console.log(new Person().color);
image.png

从上面的示例可以看出:
①子类原型对象中包含了父类构造函数中的属性、方法和子类原型对象中的属性、方法,但是不包含父类原型对象中方法和属性color。
②子类实例能调用父类原型对象中的hello方法 color属性则是由于父类原型对象是子类原型对象的原型对象。
③父类原型对象中引用类型数据会在各个实例中共享,一个子类实例修改父类原型对象中引用属性的值(color),其他实例对象调用该引用属性时,值也是修改后的。
④只能继承一个父类,不能多继承。
注意:子类原型对象上定义属性和方法必须在Child.prototype = new Person("dlj")之后,否则子类原型对象上独有的属性和方法会丢失。
2)借用构造函数
借用父类的构造函数来增强子类实例,相当于复制父类的实例属性给子类,所以子类实例之间相互独立,不共享父类的引用属性,并可实现多继承(call或apply).
示例:

function Person1(name) {
 this.name = name || "父类";
 this.job = ["job1", "job2"];
 this.say = function () {
   console.log("父类构造函数方法--- ", this.name);
 }
}
Person1.prototype.sex = "女";
Person1.prototype.color = ["yellow", "red", "blue"];
Person1.prototype.hello = function () {
 console.log("父类原型对象方法---- hello ", this.sex);
}
function Person2(name) {
 this.name2 = name || "父类2";
 this.job2 = ["job1", "job2"];
 this.say2 = function () {
   console.log("父类构造函数方法--- ", this.name2);
 }
}
Person2.prototype.sex2 = "女";
Person2.prototype.color2 = ["yellow", "red", "blue"];
Person2.prototype.hello2 = function () {
 console.log("父类原型对象方法---- hello ", this.sex2);
}
function Child(name, age) {
 Person1.call(this, name);
 Person2.call(this, name);
 this.age = age;
 this.hello3 = function () {
   console.log("子类构造函数方法--- ", this.age);
 }
}
Child.prototype.address = "北京";
Child.prototype.cHello = function () {
 console.log("子类原型对象方法---- chello ");
}
let chd1 = new Child("dlj", 11);
let chd2 = new Child("dlj1", 111);
console.log("chi1 is ", chd1);
console.log("chd1.__proto__ ", chd1.__proto__);
console.log("chd1.__proto__.__proto__  ", chd1.__proto__.__proto__);
chd1.job2.push("job3");
console.log("chd1.job2 ", chd1.job2, chd2.job2);
console.log("chd1.sex ", chd1.sex);//undefined 子类实例未继承父类原型中属性
chd1.say2();
//console.log("chd1.hello ", chd1.hello());//报错 chd1.hello is not a function 子类实例未继承父类原型中和方法
image.png

从示例中可以看出:
①因为通过父类构造函数继承,不涉及父类原型对象,所以父类原型对象中的属性和方法不可用(无法方法复用),子类只能调用自身和继承父类构造函数中的方法和属性。
②子类可以继承多个父类,实现多继承。
③父类中引用属性(job2)在子类实例中相互独立。
3)组合继承
将借用构造函数和原型链继承组合使用,既可以实现父类原型对象方法的复用,有可以使父类中引用属性相互独立并实现多继承。

function Person1(name) {
  this.name = name || "父类";
  this.job = ["job1", "job2"];
  this.say = function () {
    console.log("父类构造函数方法--- ", this.name);
  }
}
Person1.prototype.sex = "女";
Person1.prototype.color = ["yellow", "red", "blue"];
Person1.prototype.hello = function () {
  console.log("父类原型对象方法---- hello ", this.sex);
}
function Person2(name) {
  this.name2 = name || "父类2";
  this.job2 = ["job1", "job2"];
  this.say2 = function () {
    console.log("父类构造函数方法--- ", this.name2);
  }
}
Person2.prototype.sex2 = "女";
Person2.prototype.color2 = ["yellow", "red", "blue"];
Person2.prototype.hello2 = function () {
  console.log("父类原型对象方法---- hello ", this.sex2);
}
function Child(name, age) {
   Person1.call(this, name);
  Person2.call(this, name);
  this.age = age;
  this.hello3 = function () {
    console.log("子类构造函数方法--- ", this.age);
  }
}
Child.prototype = new Person1("dlj");
Child.prototype.constrctor = Child;

Child.prototype.address = "北京";
Child.prototype.cHello = function () {
  console.log("子类原型对象方法---- chello ");
}
let chd1 = new Child("dlj", 11);
let chd2 = new Child("dlj1", 111);
console.log("chi1 is ", chd1);
console.log("chd1.__proto__ ", chd1.__proto__);
console.log("chd1.__proto__.__proto__  ", chd1.__proto__.__proto__);
chd1.job2.push("job3");
console.log("chd1.job2 ", chd1.job2, chd2.job2);
console.log("chd1.sex ", chd1.sex);
chd1.say2();
chd1.hello();
image.png

从示例可以看出子类实例中属性、方法与子类原型对象的属性和方法有重复,这也算是组合继承唯一的缺点了。
4)组合继承优化
3)的实现方式之所以造成属性或方法重复,是因为调用了两次创建父类实例的方法,这是将父类原型对象直接赋值给子类原型可以避免一次创建父类实例方法的调用,从而解决重复的问题。将上个示例中Child.prototype = new Person1("dlj") 改为Child.prototype = Person1.prototype(或者改为Child.prototype = Object.create(Person1.prototype));其他不动,与3)相比,重复属性和方法被去掉了。
示例:


image.png

改为Child.prototype = Object.create(Person1.prototype))时

接3)中示例
console.log("Child.prototype.__proto__ ", Child.prototype.__proto__);
console.log("Person1.prototype ", Person1.prototype);
console.log(Child.prototype.__proto__ == Person1.prototype);

//Child.prototype = new Person1("dlj")时 
Child.prototype.__proto__  {}
Person1.prototype  Person1 {
  sex: '女',
  color: [ 'yellow', 'red', 'blue' ],
  hello: [Function],
  constrctor: [Function: Child],
  address: '北京',
  cHello: [Function]
}
false

//Child.prototype = Object.create(Person1.prototype)时
Child.prototype.__proto__  Person1 {
  sex: '女',
  color: [ 'yellow', 'red', 'blue' ],
  hello: [Function]
}
Person1.prototype  Person1 {
  sex: '女',
  color: [ 'yellow', 'red', 'blue' ],
  hello: [Function]
}
true

Object.create(object,propertiesObject)方法创建一个新对象,使用第一个参数来提供创建对象的proto(以第一个参数作为新对象的构造函数的原型),第二个可选参数,是添加到新创建对象的属性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容