详解js继承

详解js继承

来源视频讲解:https://www.bilibili.com/video/BV1J54y1y7u9/?spm_id_from=333.337.search-card.all.click&vd_source=575ceee169404991816c658859de0f7f

第⼀部分:预备知识
1、构造函数的属性

funcion A(name) {
  this.name = name; // 实例基本属性 (该属性,强调私有,不共享)
  this.arr = [1]; // 实例引⽤属性 (该属性,强调私⽤,不共享)
  this.say = function() { // 实例引⽤属性 (该属性,强调复⽤,需要共享)
    console.log('hello')
  }
}

注意:数组和⽅法都属于‘实例引⽤属性’,但是数组强调私有、不共享的。⽅法需要复⽤、共享。
构造函数中,很少有数组形式的引⽤属性,⼤部分情况都是:基本属性 + ⽅法。
2、什么是原型对象
简单来说,每个函数都有prototype属性,它就是原型对象,通过函数实例化出来的对象有proto属性,指向原型对象。
let a = new A()
a.proto == A.prototype
// prototype的结构如下
A.prototype = {
constructor: A,
...其他的原型属性和⽅法
}
3、原型对象的作⽤
原型对象的⽤途是为每个实例对象存储共享的⽅法和属性,它仅仅是⼀个普通对象。并且所有的实例是共享同⼀个原型对象,因此有别于实例⽅法或属性,原型对象仅有⼀份。⽽实例有很多份,且实例属性和⽅法是独⽴的。在构造函数中:为了属性(实例基本属性)的私有性、以及⽅法(实例引⽤属性)的复⽤、共享。我们提倡:
将属性封装在构造函数中
将⽅法定义在原型对象上
详解js继承 2
funcion A(name) {
this.name = name; // (该属性,强调私有,不共享)
}
A.prototype.say = function() { // 定义在原型对象上的⽅法 (强调复⽤,需要共享)
console.log('hello')
}
// 不推荐的写法:原因
A.prototype = {
say: function() {
console.log('hello')
}
}
第⼆部分:五种js 继承⽅式
⽅式1、原型链继承
核⼼:将⽗类实例作为⼦类原型
优点:⽅法复⽤
由于⽅法定义在⽗类的原型上,复⽤了⽗类构造函数的⽅法。⽐如say⽅法。
缺点:
创建⼦类实例的时候,不能传⽗类的参数(⽐如name)。
⼦类实例共享了⽗类构造函数的引⽤属性,⽐如arr属性。
⽆法实现多继承。
function Parent(name) {
this.name = name || '⽗亲'; // 实例基本属性 (该属性,强调私有,不共享)
this.arr = [1]; // (该属性,强调私有)
}
Parent.prototype.say = function() { // -- 将需要复⽤、共享的⽅法定义在⽗类原型上
console.log('hello')
}
function Child(like) {
this.like = like;
}
Child.prototype = new Parent() // 核⼼,但此时Child.prototype.constructor==Parent
Child.prototype.constructor = Child // 修正constructor指向
let boy1 = new Child()
let boy2 = new Child()
// 优点:共享了⽗类构造函数的say⽅法
console.log(boy1.say(), boy2.say(), boy1.say === boy2.say); // hello , hello , true
// 缺点1:不能向⽗类构造函数传参
console.log(boy1.name, boy2.name, boy1.name===boy2.name); // ⽗亲,⽗亲,true
详解js继承 3
// 缺点2: ⼦类实例共享了⽗类构造函数的引⽤属性,⽐如arr属性
boy1.arr.push(2);
// 修改了boy1的arr属性,boy2的arr属性,也会变化,因为两个实例的原型上(Child.prototype)有了⽗类构造函数的实例属性arr;
所以只要修改了boy1.arr,boy2.arr的属性也会变化。
console.log(boy2.arr); // [1,2]
注意1:修改boy1的name属性,是不会影响到boy2.name。因为设置boy1.name相当于在⼦类实例新增了name属性。
注意2:
console.log(boy1.constructor); // Parent 你会发现实例的构造函数居然是Parent。
⽽实际上,我们希望⼦类实例的构造函数是Child,所以要记得修复构造函数指向。
修复如下:Child.prototype.constructor = Child;
⽅式2、借⽤构造函数
核⼼:借⽤⽗类的构造函数来增强⼦类实例,等于是复制⽗类的实例属性给⼦类。
优点:实例之间独⽴。
创建⼦类实例,可以向⽗类构造函数传参数。
⼦类实例不共享⽗类构造函数的引⽤属性。如arr属性
可实现多继承(通过多个call或者apply继承多个⽗类)
缺点:
⽗类的⽅法不能复⽤
由于⽅法在⽗构造函数中定义,导致⽅法不能复⽤(因为每次创建⼦类实例都要创建⼀遍⽅法)。
⽐如say⽅法。(⽅法应该要复⽤、共享)
⼦类实例,继承不了⽗类原型上的属性。(因为没有⽤到原型)
function Parent(name) {
this.name = name; // 实例基本属性 (该属性,强调私有,不共享)
this.arr = [1]; // (该属性,强调私有)
this.say = function() { // 实例引⽤属性 (该属性,强调复⽤,需要共享)
console.log('hello')
}
}
function Child(name,like) {
Parent.call(this,name); // 核⼼ 拷⻉了⽗类的实例属性和⽅法
this.like = like;
}
let boy1 = new Child('⼩红','apple');
let boy2 = new Child('⼩明', 'orange ');
// 优点1:可向⽗类构造函数传参
console.log(boy1.name, boy2.name); // ⼩红, ⼩明
// 优点2:不共享⽗类构造函数的引⽤属性
boy1.arr.push(2);
console.log(boy1.arr,boy2.arr);// [1,2] [1]
详解js继承 4
// 缺点1:⽅法不能复⽤
console.log(boy1.say === boy2.say) // false (说明,boy1和boy2的say⽅法是独⽴,不是共享的)
// 缺点2:不能继承⽗类原型上的⽅法
Parent.prototype.walk = function () { // 在⽗类的原型对象上定义⼀个walk⽅法。
console.log('我会⾛路')
}
boy1.walk; // undefined (说明实例,不能获得⽗类原型上的⽅法)
⽅式3、组合继承
核⼼:通过调⽤⽗类构造函数,继承⽗类的属性并保留传参的优点;然后通过将⽗类实例作为
⼦类原型,实现函数复⽤。
优点:
保留构造函数的优点:创建⼦类实例,可以向⽗类构造函数传参数。
保留原型链的优点:⽗类的⽅法定义在⽗类的原型对象上,可以实现⽅法复⽤。
不共享⽗类的引⽤属性。⽐如arr属性
缺点:
由于调⽤了2次⽗类的构造⽅法,会存在⼀份多余的⽗类实例属性,具体原因⻅⽂末。
注意:'组合继承'这种⽅式,要记得修复Child.prototype.constructor指向
第⼀次Parent.call(this);从⽗类拷⻉⼀份⽗类实例属性,作为⼦类的实例属性,第⼆次
Child.prototype = new Parent();创建⽗类实例作为⼦类原型,Child.protype中的⽗类属性和⽅法
会被第⼀次拷⻉来的实例属性屏蔽掉,所以多余。
function Parent(name) {
this.name = name; // 实例基本属性 (该属性,强调私有,不共享)
this.arr = [1]; // (该属性,强调私有)
}
Parent.prototype.say = function() { // --- 将需要复⽤、共享的⽅法定义在⽗类原型上
console.log('hello')
}
function Child(name,like) {
Parent.call(this,name,like) // 核⼼ 第⼆次
this.like = like;
}
Child.prototype = new Parent() // 核⼼ 第⼀次
Child.prototype.constructor = Child // 修正constructor指向
let boy1 = new Child('⼩红','apple')
let boy2 = new Child('⼩明','orange')
// 优点1:可以向⽗类构造函数传参数
console.log(boy1.name,boy1.like); // ⼩红,apple
// 优点2:可复⽤⽗类原型上的⽅法
console.log(boy1.say === boy2.say) // true
详解js继承 5
// 优点3:不共享⽗类的引⽤属性,如arr属性
boy1.arr.push(2)
console.log(boy1.arr,boy2.arr); // [1,2] [1] 可以看出没有共享arr属性。
// 缺点1:由于调⽤了2次⽗类的构造⽅法,会存在⼀份多余的⽗类实例属性
其实Child.prototype = new Parent()
console.log(Child.prototype.proto === Parent.prototype); // true
因为Child.prototype等于Parent的实例,所以proto指向Parent.prototype
⽅式4、组合继承优化1
核⼼:
通过这种⽅式,砍掉⽗类的实例属性,这样在调⽤⽗类的构造函数的时候,就不会初始化两次实
例,避免组合继承的缺点。
优点:
只调⽤⼀次⽗类构造函数。
保留构造函数的优点:创建⼦类实例,可以向⽗类构造函数传参数。
保留原型链的优点:⽗类的实例⽅法定义在⽗类的原型对象上,可以实现⽅法复⽤。
缺点:
修正构造函数的指向之后,⽗类实例的构造函数指向,同时也发⽣变化(这是我们不希望的)
注意:'组合继承优化1'这种⽅式,要记得修复Child.prototype.constructor指向
原因是:不能判断⼦类实例的直接构造函数,到底是⼦类构造函数还是⽗类构造函数。
function Parent(name) {
this.name = name; // 实例基本属性 (该属性,强调私有,不共享)
this.arr = [1]; // (该属性,强调私有)
}
Parent.prototype.say = function() { // --- 将需要复⽤、共享的⽅法定义在⽗类原型上
console.log('hello')
}
function Child(name,like) {
Parent.call(this,name,like) // 核⼼
this.like = like;
}
Child.prototype = Parent.prototype // 核⼼ ⼦类原型和⽗类原型,实质上是同⼀个

Child.prototype.constructor = Child
详解js继承 6
let boy1 = new Child('⼩红','apple')
let boy2 = new Child('⼩明','orange')
let p1 = new Parent('⼩爸爸')
// 优点1:可以向⽗类构造函数传参数
console.log(boy1.name,boy1.like); // ⼩红,apple
// 优点2:可复⽤⽗类原型上的⽅法
console.log(boy1.say === boy2.say) // true
// 缺点1:当修复⼦类构造函数的指向后,⽗类实例的构造函数指向也会跟着变了。
没修复之前:console.log(boy1.constructor); // Parent
修复代码:Child.prototype.constructor = Child
修复之后:console.log(boy1.constructor); // Child
console.log(p1.constructor);// Child 这⾥就是存在的问题(我们希望是Parent)
具体原因:因为是通过原型来实现继承的,Child.prototype的上⾯是没有constructor属性的,
就会往上找,这样就找到了Parent.prototype上⾯的constructor属性;当你修改了⼦类实例的
construtor属性,所有的constructor的指向都会发⽣变化。
⽅式5、组合继承优化2 ⼜称 寄⽣组合继承 --- 完美⽅式
核⼼:
优点:完美
缺点:---
function Parent(name) {
this.name = name; // 实例基本属性 (该属性,强调私有,不共享)
this.arr = [1]; // (该属性,强调私有)
}
Parent.prototype.say = function() { // --- 将需要复⽤、共享的⽅法定义在⽗类原型上
console.log('hello')
}
function Child(name,like) {
Parent.call(this,name,like) // 核⼼
this.like = like;
}
// 核⼼ 通过创建中间对象,⼦类原型和⽗类原型,就会隔离开。不是同⼀个啦,有效避免了⽅式4的缺点。
Child.prototype = Object.create(Parent.prototype)
// 这⾥是修复构造函数指向的代码
Child.prototype.constructor = Child
let boy1 = new Child('⼩红','apple')
let boy2 = new Child('⼩明','orange')
let p1 = new Parent('⼩爸爸')
注意:这种⽅法也要修复构造函数的
修复代码:Child.prototype.constructor = Child
详解js继承 7
修复之后:console.log(boy1.constructor); // Child
console.log(p1.constructor); // Parent 完美
第三部分:其他相关问题
1、Object.create(object, propertiesObject)
Object.create()⽅法创建⼀个新对象,使⽤第⼀个参数来提供新创建对象的proto(以第⼀个参
数作为新对象的构造函数的原型对象);
⽅法还有第⼆个可选参数,是添加到新创建对象的属性,写法如下。
const a = Object.create(Person.prototype, {
age: {
value: 12,
writable:true,
configurable:true,
}
})
new 与 Object.create() 的区别?
new 产⽣的实例,优先获取构造函数上的属性;构造函数上没有对应的属性,才会去原型上查找;
如果构造函数中以及原型中都没有对应的属性,就会报错。Object.create() 产⽣的对象,只会在原
型上进⾏查找属性,原型上没有对应的属性,就会报错。
let Base1 = function() {
this.a = 1
}
let o1 = new Base1()
let o2 = Object.create(Base1.prototype)
console.log(o1.a); // 1
console.log(o2.a); // undefined
let Base2 = function() {}
Base2.prototype.a = 'aa'
let o3 = new Base2()
let o4 = Object.create(Base2.prototype)
console.log(o3.a); // aa
console.log(o4.a); // aa
let Base3 = function() {
this.a = 1
}
Base3.prototype.a = 'aa'
let o5 = new Base3()
let o6 = Object.create(Base3.prototype)
详解js继承 8
console.log(o5.a); // 1
console.log(o6.a); // aa
2、new 的过程
创建新对象(如obj)。
将新对象的proto指向构造函数的prototype对象。
执⾏构造函数,为这个新对象添加属性,并将this指向创建的新对象obj。
当构造函数本⾝返回值为对象时,返回该对象,否则返回新对象。
//创建Person构造函数,参数为name,age
function Person(name,age){
this.name = name;
this.age = age;
}
function _new(){
//1.拿到传⼊的参数中的第⼀个参数,即构造函数名Func
var Func = [].shift.call(arguments);
//2.创建⼀个空对象obj,并让其继承Func.prototype
var obj = Object.create(Func.prototype);
//3.执⾏构造函数,并将this指向创建的空对象obj
const result = Func.apply(obj,arguments)
//4.当函数也有返回值且为对象时返回该对象,否则返回创建的新对象obj
return (result instanceOf Object ? result : obj)
}
let ming = _new(Person,'xiaoming',18);
console.log(ming);
[].shift.call表⽰删除并返回auguments[0]。也可以通过以下⽅式取得函数名和函数的参数:
function _new(Func, ...params){
...
}
Object.create创建obj,使得obj.proto = Func.prototype
3、为什么‘组合继承’这种⽅式,会执⾏两次⽗类构造函数??
第⼀次:Child.prototype = new Parent()
‘new 的过程’的第三步,其实就是执⾏了⽗类构造函数。
详解js继承 9
第⼆次:Parent.call(this,name,like)
call的作⽤是改变函数执⾏时的上下⽂。⽐如:A.call(B)。其实,最终执⾏的还是A函数,只不过是
⽤B来调⽤⽽已。所以,你就懂了Parent.call(this,name,like) ,也就是执⾏了⽗类构造函数Person。

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

推荐阅读更多精彩内容

  • 什么是继承? 简单说就是:在js中获取存在对象已有属性和方法的一种方式.下面我总结了几个继承: 一丶原型链继承 基...
    G_kai阅读 626评论 0 0
  • 其实要总结这几个概念已经很久了,只是之前一直都觉得自己还不算完全掌握,而且知识点还不够系统,所以一直拖着,但是最近...
    Katherine的小世界阅读 677评论 0 5
  • 用过 React的读者知道,经常用 extends继承 React.Component: // 部分源码 func...
    金色888阅读 229评论 0 0
  • 原文链接:zhuanlan.zhihu.com (一) 原型链继承: function Parent(name) ...
    越努力越幸运_952c阅读 284评论 0 2
  • 构造函数继承 类式继承是在函数对象内调用父类的构造函数,使得自身获得父类的方法和属性。call和apply方法为类...
    前端大神888阅读 157评论 0 0