Open yuanyuanbyte opened 2 years ago
本系列的主题是 JavaScript 深入系列,每期讲解一个技术要点。如果你还不了解各系列内容,文末点击查看全部文章,点我跳转到文末。
如果觉得本系列不错,欢迎 Star,你的支持是我创作分享的最大动力。
funcion A(name) { this.name = name; // 实例基本属性 (该属性,强调私有,不共享) this.arr = [1]; // 实例引⽤属性 (该属性,强调私⽤,不共享) this.say = function () { // 实例引⽤属性 (该属性,强调复⽤,需要共享) console.log('hello') } } 注意:数组和⽅法都属于‘ 实例引⽤ 属性’,但是数组强调私有、不共享的。⽅法需要复⽤、共享。 在构造函数中,⼀般很少有数组形式的引⽤属性,⼤部分情况都是: 基本属性 + ⽅法。
简单来说,每个函数都有prototype属性,它就是原型对象,通过函数实例化出来的对象有个proto属性,指向原型对象。
let a = new A() a.__proto__ == A.prototype // prototype的结构如下 A.prototype = { constructor: A, ...其他的原型属性和⽅ 法 }
原型对象的⽤途是为每个实例对象存储共享的⽅法和属性,它仅仅是⼀个普通对象⽽已。并且所有的实例是共享同⼀个原型对象,因此有别于实例⽅法或属性,原型对象仅有⼀份。⽽实例有很多份,且实例属性和⽅法是独⽴的。在构造函数中:为了属性(实例基本属性)的私有性、以及⽅法(实例引⽤属性)的复⽤、共享。我们提倡:
funcion A(name) { this.name = name; // (该属性,强调私有,不共享) } A.prototype.say = function () { // 定义在原型对象上的⽅法 (强调复⽤,需要共享) console.log('hello') } // 不推荐的写法:[原因](https://blog.csdn.net/kkkkkxiaofei/article/details/46474303) A.prototype = { say: function () { console.log('hello') } }
核心: 将⽗类实例作为⼦类原型
优点: ⽅法复⽤
由于⽅法定义在⽗类的原型上,复⽤了⽗类构造函数的⽅法。⽐如say⽅法。
缺点:
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 // 缺点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;
核心:借⽤⽗类的构造函数来增强⼦类实例,等于是复制⽗类的实例属性给⼦类。
优点:实例之间独⽴。
⽗类的⽅法不能复⽤ 由于⽅法在⽗构造函数中定义,导致⽅法不能复⽤(因为每次创建⼦类实例都要创建⼀遍⽅法)。 ⽐如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 (说明实例,不能获得⽗类原型上的⽅法)
核心:通过调⽤⽗类构造函数,继承⽗类的属性并保留传参的优点;然后通过将⽗类实例作为⼦类原型,实现函数复⽤。
优点:
注意:'组合继承'这种⽅式,要记得修复Child.prototype.constructor指向
第⼀次Parent.call(this);从⽗类拷⻉⼀份⽗类实例属性,作为⼦类的实例属性,第⼆次 Child.prototype = new Parent();创建⽗类实例作为⼦类原型,Child.protype中的⽗类属性和⽅法会被第⼀次拷⻉来的实例属性屏蔽掉,所以多余。
为什么‘组合继承’这种⽅式,会执⾏两次⽗类构造函数?? 第⼀次:Child.prototype = new Parent() ‘new 的过程’的第三步,其实就是执⾏了⽗类构造函数。详解js继承 9 第⼆次:Parent.call(this,name,like) call的作⽤是改变函数执⾏时的上下⽂。⽐如:A.call(B)。其实,最终执⾏的还是A函数,只不过是 ⽤B来调⽤⽽已。所以,你就懂了Parent.call(this,name,like) ,也就是执⾏了⽗类构造函数Person
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
核心:
通过这种⽅式,砍掉⽗类的实例属性,这样在调⽤⽗类的构造函数的时候,就不会初始化两次实例,避免组合继承的缺点。
注意:'组合继承优化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的指向都会发⽣ 变化。
核⼼: 优点:完美 缺点:---
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 完美😊
查看全部文章
各系列文章汇总:https://github.com/yuanyuanbyte/Blog
我是圆圆,一名深耕于前端开发的攻城狮。
本系列的主题是 JavaScript 深入系列,每期讲解一个技术要点。如果你还不了解各系列内容,文末点击查看全部文章,点我跳转到文末。
如果觉得本系列不错,欢迎 Star,你的支持是我创作分享的最大动力。
第⼀部分:预备知识
1、构造函数的属性
2、什么是原型对象
简单来说,每个函数都有prototype属性,它就是原型对象,通过函数实例化出来的对象有个proto属性,指向原型对象。
3、原型对象的作用
原型对象的⽤途是为每个实例对象存储共享的⽅法和属性,它仅仅是⼀个普通对象⽽已。并且所有的实例是共享同⼀个原型对象,因此有别于实例⽅法或属性,原型对象仅有⼀份。⽽实例有很多份,且实例属性和⽅法是独⽴的。在构造函数中:为了属性(实例基本属性)的私有性、以及⽅法(实例引⽤属性)的复⽤、共享。我们提倡:
第⼆部分:五种js 继承方式
方式1、原型链继承
核心: 将⽗类实例作为⼦类原型
优点: ⽅法复⽤
由于⽅法定义在⽗类的原型上,复⽤了⽗类构造函数的⽅法。⽐如say⽅法。
缺点:
方式2、借用构造函数
核心:借⽤⽗类的构造函数来增强⼦类实例,等于是复制⽗类的实例属性给⼦类。
优点:实例之间独⽴。
缺点:
⽗类的⽅法不能复⽤ 由于⽅法在⽗构造函数中定义,导致⽅法不能复⽤(因为每次创建⼦类实例都要创建⼀遍⽅法)。 ⽐如say⽅法。(⽅法应该要复⽤、共享)
⼦类实例,继承不了⽗类原型上的属性。(因为没有⽤到原型)
方式3、组合继承
核心:通过调⽤⽗类构造函数,继承⽗类的属性并保留传参的优点;然后通过将⽗类实例作为⼦类原型,实现函数复⽤。
优点:
缺点:
注意:'组合继承'这种⽅式,要记得修复Child.prototype.constructor指向
第⼀次Parent.call(this);从⽗类拷⻉⼀份⽗类实例属性,作为⼦类的实例属性,第⼆次 Child.prototype = new Parent();创建⽗类实例作为⼦类原型,Child.protype中的⽗类属性和⽅法会被第⼀次拷⻉来的实例属性屏蔽掉,所以多余。
其实Child.prototype = new Parent() console.log(Child.prototype.proto === Parent.prototype); // true 因为Child.prototype等于Parent的实例,所以proto指向Parent.prototype
⽅式4、组合继承优化1
核心:
通过这种⽅式,砍掉⽗类的实例属性,这样在调⽤⽗类的构造函数的时候,就不会初始化两次实例,避免组合继承的缺点。
优点:
缺点:
注意:'组合继承优化1'这种⽅式,要记得修复Child.prototype.constructor指向
原因是:不能判断⼦类实例的直接构造函数,到底是⼦类构造函数还是⽗类构造函数。
⽅式5、组合继承优化2 ⼜称 寄⽣组合继承 --- 完美⽅式
核⼼: 优点:完美 缺点:---
原文
查看全部文章
博文系列目录
交流
各系列文章汇总:https://github.com/yuanyuanbyte/Blog
我是圆圆,一名深耕于前端开发的攻城狮。