摘要:如何确定原型和实例的关系第一个方法是,,用于检测实例与原型链中出现过的构造函数。所谓寄生组合继承,即通过借用构造函数方式,继承属性,通过原型链形式继承方法。
概述
原型和闭包是JS的两个难点,最近碰到了原型继承的概念,正好在这里总结一下。
既然要实现继承,就一定要有一个父类。
// 定义一个父类 function father(name) { //属性 this.name = name; } // 原型方法 father.prototype.getName = function () { return this.name; }原型链继承
基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法。
回顾一下原型、实例和构造函数的关系。
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象内部的指针。
// 子类 function son(age) { // 属性 this.age = age; }; son.prototype = new father("jason"); son.prototype.getAge = function () { return this.age; } let firstchild = new son("19"); console.log(firstchild.getAge()) // 19
这里需要注意几点的是:
默认原型
原型链的最顶端是Object,所有引用类型默认都是继承于Object的,所以默认也是有toString等方法的。
如何确定原型和实例的关系
第一个方法是,instanceof,用于检测实例与原型链中出现过的构造函数。
console.log(firstchild instanceof Object) //true console.log(firstchild instanceof son) //true console.log(firstchild instanceof father) //true
第二个方法是,isPrototypeOf方法。
console.log(Object.prototype.isPrototypeOf(firstchild)) //true console.log(son.prototype.isPrototypeOf(firstchild)) //true console.log(father.prototype.isPrototypeOf(firstchild)) //true
谨慎定义方法
子类型可能要重写父类型方法,或定义父类没有的方法。不管是啥,这个方法一定要写在替换原型语句的后面。
还有原型链继承的时候,不能使用对象字面量创建原型方法。
例如:
son.prototype = new father("jason"); son.prototype = { getAge: function() { return this.age } }
这样会导致创建一个新的Object实例,而非原来的father。
共享性和传参问题
第一,引用类型的原型属性会被所有实例共享。
function father(name) { this.name = name; this.colors = ["blue", "red", "white"]; } let firstchild = new son("19"); let secondchild = new son("20"); firstchild.colors.push("black"); console.log(firstchild.colors) // ["blue", "red", "white", "black"] console.log(secondchild.colors) // ["blue", "red", "white", "black"]
第二,不能像父类型构造函数传参数,书里准确说法是,没有办法在不影响所有实例的情况下,给父类构造函数传递参数。
小结优点:
非常纯粹的继承关系,实例是子类的实例,也是父类的实例
父类新增原型方法/原型属性,子类都能访问到
简单,易于实现
缺点:
要想为子类新增属性和方法,必须要在new father()这样的语句之后执行,不能放到构造器中
无法实现多继承
来自原型对象的引用属性是所有实例共享的
创建子类实例时,无法向父类构造函数传参
借用构造继承在子类型的构造函数中调用父类的构造函数,使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(不用原型)
function son(age) { father.call(this); this.age = age; }; son.prototype = new father("jason"); son.prototype.getAge = function () { return this.age; } let firstchild = new son("19"); let secondchild = new son("20"); firstchild.colors.push("black"); console.log(firstchild.colors); // ["blue", "red", "white", "black"] console.log(secondchild.colors); // ["blue", "red", "white"]
可以传递参数
方法都在构造函数中定义,函数复用性丢失
总结优点:
由例子可见,解决了1中子类实例共享父类引用属性的问题
创建子类实例时,可以向父类传递参数
可以实现多继承(call多个父类对象)
缺点:
实例并不是父类的实例,只是子类的实例
只能继承父类的实例属性和方法,不能继承原型属性/方法
无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
组合继承也就是将原型链继承和构造函数继承融合,原型链实现对原型属性和方法的继承,构造函数实现对实例属性的继承。
这样既保证了原型上函数的复用,也保证了每个实例有自己的属性。
function son(name, age) { father.call(this, name); this.age = age; }; son.prototype = new father(); son.prototype.getAge = function () { return this.age; } let firstchild = new son("jason", "19"); let secondchild = new son("jason junior", "18"); firstchild.colors.push("black"); console.log(firstchild.colors); // ["blue", "red", "white", "black"] console.log(secondchild.colors); //["blue", "red", "white"] console.log(firstchild.getName()); // jason console.log(secondchild.getName()); // jason junior console.log(firstchild.getAge()); //19 console.log(secondchild.getAge()); //18
特点:
可以继承实例属性/方法,也可以继承原型属性/方法
既是子类的实例,也是父类的实例
不存在引用属性共享问题
可传参
函数可复用
缺点:
调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
原型式继承为父类实例添加新特性,作为子类实例返回
let p = { name: "jason", colors: ["white", "black", "red"] } function object (o) { function F() {}; F.prototype = o; return new F(); } let firstchild = object(p) let secondchild = object(p) firstchild.name = "jason1" firstchild.colors.push("blue") secondchild.name = "jason2" secondchild.colors.push("green") console.log(p.colors) // ["white", "black", "red", "blue", "green"]
ECMAScript 5新增Object.create()方法规范原型式继承。两个参数,一个参数是新对象原型的对象,一个参数是对象定义额外属性的对象,第二个可忽略,就等于上述object函数了
寄生式继承创造一个用于封装继承过程的函数,该函数内部以某种方式增强对象。
function create(o) { let clone = object(o); o.sayHi = function () { console.log("Hi") } return o; }寄生组合继承
组合继承虽然好用,但是也有缺陷,就是会调用两次构造函数,一次在创建时候,一次在内部,那个call方法。
所谓寄生组合继承,即通过借用构造函数方式,继承属性,通过原型链形式继承方法。
沿用寄生方式:
function inheritPrototype (sub, sup) { let prototype = object(sup.prototype); prototype.constructor = sub; sub.prototype = prototype; }
function father(name) { this.name = name; this.colors = ["blue", "red", "white"]; } father.prototype.getName = function () { return this.name; } function son(name, age) { father.call(this, name); this.age = age; }; function object (o) { function F() {}; F.prototype = o; return new F(); } function inheritPrototype (sub, super) { let prototype = object(super.prototype); prototype.constructor = sub; sub.prototype = prototype; } inheritPrototype(son, father); son.prototype.getAge = function () { return this.age; }总结
优点:
堪称完美
缺点:
实现较为复杂
参考 <>总结
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/95002.html
摘要:对象创建的三种方式字面量创建方式系统内置构造函数方式自定义构造函数构造函数原型实例之间的关系实例是由构造函数实例化创建的,每个函数在被创建的时候,都会默认有一个对象。 JS 对象创建的三种方式 //字面量创建方式 var person= { name:jack } //系统内置构造函数方式 var person= new Object(); person.name = jack; ...
摘要:继承了如上,我们通过方法借调了超类的构造函数,实际上是在新创建的实力环境下调用了构造函数。组合继承组合继承的基本思想将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。继承方法在上面这个例子中,构造函数定义了两个属性和。 在ECMAScript中只支持实现继承,而且实现继承主要是依靠原型链来实现的。 1. 什么是原型链 继承基本思想:利用原型让一个引用类型继承另一个...
摘要:组合模式继承结合了构造函数继承时可以为每个属性重新初始化,构造一个副本的优点,以及原型链继承时一次定义处处共享的优点。但令我百思不得其解的是,从上面给出的例子来看,组合继承并没有调用两次超类型构造函数。 最近在阅读《js权威指南》的继承这一章,对于组合模式和寄生组合模式的区别有点混淆,在多次重读以及尝试之后,得到一些心得。 组合模式继承 结合了构造函数继承时可以为每个属性重新初始化,构...
摘要:关于中面向对象的理解面向对象编程它是一种编程思想我们的编程或者学习其实是按照类实例来完成的学习类的继承封装多态封装把实现一个功能的代码封装到一个函数中一个类中以后再想实现这个功能,只需要执行这个函数方法即可,不需要再重复的编写代码。 关于js中面向对象的理解 面向对象编程(oop) 它是一种编程思想 (object-oriented programming ), 我们的编程或者学习其...
摘要:关于中面向对象的理解面向对象编程它是一种编程思想我们的编程或者学习其实是按照类实例来完成的学习类的继承封装多态封装把实现一个功能的代码封装到一个函数中一个类中以后再想实现这个功能,只需要执行这个函数方法即可,不需要再重复的编写代码。 关于js中面向对象的理解 面向对象编程(oop) 它是一种编程思想 (object-oriented programming ), 我们的编程或者学习其...
阅读 2311·2021-11-15 11:38
阅读 2439·2021-11-15 11:37
阅读 2542·2021-08-24 10:00
阅读 2900·2019-08-30 15:56
阅读 1259·2019-08-30 15:53
阅读 3694·2019-08-29 18:43
阅读 2929·2019-08-29 17:01
阅读 3254·2019-08-29 16:25