前言
先从面向对象讲起,本瓜认为:面向对象编程,它的最大能力就是:复用!
咱常说,面向对象三大特点,封装、继承、多态。
这三个特点,以“继承”为核心。封装成类,是为了继承,继承之后再各自发展(重写),可理解为多态。所以,根本目的是为了继承,即“复用“!
如果你用 javascript 面向对象的能力来编程的话,能想到的,也只供使用的就是:基于原型。
因为这门语言设计就是这样,我们之前也提过:javascript的语言设计主要受到了self(一种基于原型的编程语言)和 scheme(一门函数式编程语言)的影响;
它复用的能力就是来自原型!
好了,有这个认知基础,我们再看原型继承。
原型链继承
原型继承最直接的一种实现就是:原型链继承
ecma-262 把原型链定义为 ecmascript 的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。
我们来看看原型链继承的代码实现:
function supertype() { this.property = true; } function subtype() { this.subproperty = false; } supertype.prototype.getsupervalue = function() { return this.property; }; subtype.prototype.getsubvalue = function () { return this.subproperty; }; subtype.prototype = new supertype(); // 对 subtype 得原型链重新指定,是原型链继承 let instance = new subtype(); console.log(instance.getsupervalue()); // true
还需要再额外说明查找关系吗??不懂得工友可见这篇
这里还是用代码展示下它们的指向关系吧:
上面例子中有 1 个对象 instance , 两个函数,supertype 和 subtype 。函数是上帝,对象是基本物质。
继承来自两方面:
- 1. 继承自祖先(遗产);
- 2. 继承自上帝(天赋);
// 继承自祖先(遗产) instance.__proto__ === subtype.prototype // true subtype.prototype.__proto__ === supertype.prototype // true // 继承自上帝(天赋) supertype.__proto__ === function.prototype // true subtype.__proto__ === function.prototype // true supertype.prototype.__proto__ === object.prototype // true object.prototype.__proto__ === null // true
当然,我们并不是来讲原型链的。重点是:点出原型链继承的“问题”!!
它的主要问题出现在:原型中包含引用值的时候,原型中包含的引用值会在所有实例间共享。
function supertype() { this.colors = ["red", "blue", "green"]; } function subtype() {} subtype.prototype = new supertype() // 原型链继承 let s1 = new subtype() let s2 = new subtype() s1.colors.push("yellow") console.log(s1.colors) // ['red', 'blue', 'green', 'yellow'] console.log(s2.colors) // ['red', 'blue', 'green', 'yellow']
colors 是个数组,引用值,当它共享给 subtype 的时候,用的是引用值,当我们实例化的时候,如果其中一个实力对它做出了修改,将会影响到其它实例的引用。
其实,我们也知道,很少在业务代码中这样去写继承:subtype.prototype = new supertype() ,原型链继承会造成复用的混乱,所以它基本不会被单独使用。
构造函数继承
构造函数继承,也叫做:“盗用构造函数”,“对象伪装”或“经典继承”。
基本思路:在子类构造函数中用 apply()和 call()方法调用父类构造函数。
上一小节的例子改造为:
function supertype() { this.colors = ["red", "blue", "green"]; } function subtype() { supertype.call(this) // 构造函数继承 } let s1 = new subtype() let s2 = new subtype() s1.colors.push("yellow") console.log(s1.colors) // ['red', 'blue', 'green', 'yellow'] console.log(s2.colors) // ['red', 'blue', 'green']
完美解决原型链继承的问题,但是它也有它的问题,也是使用构造函数模式自定义类型的问题,
即:必须在构造函数中定义方法(在原型上定义方法,子类是访问不到的),函数不能重用。
function supertype() { } function subtype() { supertype.call(this) // 构造函数继承 } supertype.prototype.fn = ()=>{} let s1 = new subtype() console.log(s1.fn) // undefined
function supertype() { this.fn=()=>{} } function subtype() { supertype.call(this) // 构造函数继承 } let s1 = new subtype() let s2 = new subtype() console.log(s1.fn === s2.fn) // false
而这一点,在原型链继承中,又是可以的。。。
function supertype() {} function subtype() {} supertype.prototype.fn = ()=>{} subtype.prototype = new supertype() // 原型链继承 let s1 = new subtype() console.log(s1.fn) // ()=>{}
function supertype() { this.fn=()=>{} } function subtype() {} subtype.prototype = new supertype() // 原型链继承 let s1 = new subtype() let s2 = new subtype() console.log(s1.fn === s2.fn) // true
所以,综上,原型链继承和构造函数继承的 “毛病” 分别是:
- 原型链继承:所有继承的属性和方法都会在对象实例间共享,无法做到实例私有。
- 构造函数继承:子类不能访问父类原型上的方法。
咱就是说,这东西怎么这么拧巴呢。。。
于是乎一个规避二者“毛病”的继承方式出现了:组合继承~~
组合继承
目前最流行的继承模式是组合继承!
思路是:使用原型链继承原型上的属性和方法,而通过构造函数继承实例属性。
function supertype(name){ this.name = name; this.colors = ["red", "blue", "green"]; } function subtype(name, age){ supertype.call(this, name) // 构造函数继承 this.age = age; } supertype.prototype.sayname = function() { console.log(this.name); } subtype.prototype = new supertype() // 原型链继承 subtype.prototype.sayage = function() { console.log(this.age); } let s1 = new subtype("nicholas", 29) let s2= new subtype("greg", 27) s1.colors.push("yellow") console.log(s1.colors) // ['red', 'blue', 'green', 'yellow'] console.log(s2.colors) // ['red', 'blue', 'green'] s1.sayname() // nicholas s2.sayname() // greg s1.sayage() // 29 s2.sayage() // 27
组合继承,总结起来就是,属性(特别是引用值)通过构造函数去继承,而公用的、需要复用的方法用原型链去继承!!
说实话,js 继承真的很奇怪。。。并不是面向对象语言,又要通过原型链去模拟面向对象,真的很多小坑的点需要去注意。(哈哈哈,想想还是函数式好,清晰)
以上就是js精髓原型链继承及构造函数继承问题纠正的详细内容,更多关于js原型链继承构造函数继承的资料请关注其它相关文章!