首页 > 代码库 > 浅谈JS中的继承
浅谈JS中的继承
- JavaScript本身是一种神马语言:
提到继承,我们常常会联想到C#、java等面向对象的高级语言(当然还有C++),因为存在类的概念使得这些语言在实际的使用中抽象成为一个对象,即面向对象。JavaScript这门语言本身就是作为浏览器脚本语言的弱语言,伴随着没有类的概念,JavaScript就成为了一种基于对象的语言而不是面向对象的语言,面向对象就会存在继承,那么基于对象的JavaScript是如何继承的,老生常谈一下。
- JavaScript的4种继承方式:
(1)原型继承
function Parent() { this.name = "dqhan"; this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘]; this.action = function () { console.log(‘Iamdqhan‘); }; } function Child() { //do someting } Child.prototype = new Parent(); //mark var child1 = new Child(); var child2 = new Child();
原型继承是最基础的继承方式,核心就是重写子类原型,是父类实例对象充当子类原型,
优点:通俗易懂
缺点:(1)原型中的引用类型共享(2)无法向父类构造函数传参
child1.arr.push(‘houhou‘);
console.log(child1.arr); // ["haha", "heihei", "hehe", "houhou"]
console.log(child2.arr); // ["haha", "heihei", "hehe", "houhou"]
原因就是操作child1中的arr时,首先回到child1下查找arr,当没有时会在child1的原型里查找,此时arr为child1原型中的属性,由于child2与child1的原型同为Parent的实例,此时更改arr值时,child2也会变,即原型共享。
那么如果现在更改属性name呢?
child1.name = "newdqhan";
console.log(child2.name); // "dqhan"
这又是为什么呢?因为child1.name会直接在child下重新定义一个name属性,这个name并不是原型上的属性,也就不会更改原型,如图。
(2)借用构造函数实现继承
function Parent(value) { this.name = "dqhan"; this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘]; this.value = http://www.mamicode.com/value;"value1"); var child2 = new Child("value2");
child1.action === child2.action //false
看一下结果
利用Call的方式将Child方法中的this传入Parent方法中,然后重新定义一遍Parent中的属性及方法
Call方法我们这个换一种写法:
function Child(value) { this.tempParent = Parent; this.tempParent(value); delete this.tempParent }
在Child函数中引用Parent构造函数并执行,此时就会在Child中定义了一遍Parent中的属性及方法。看到了这里我相信你一定会产生一个疑惑就是为什么不在Child中直接执行一次Parent函数呢。
看一下结果
并没有达到理想的结果,这是为啥呢?因为在Child中不引用Parent,直接调用,Parent中的this默认为Window对象(严格开发模式下为Undefined),目的就是为了传递this。
function Parent() { this.name = "dqhan"; //this 为 Window 对象 this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘]; this.action = function () { console.log(‘Iamdqhan‘); }; } function Child(){ Parent(); } var child = new Child();
function Parent() {
"use strict"; this.name = "dqhan"; //this 为 undefined this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘]; this.action = function () { console.log(‘Iamdqhan‘); }; } function Child(){ Parent(); } var child = new Child();
优点:摒弃了原型,避免了原型共享;解决了向父类构造函数传参的问题。
缺点:没生成一个新对象,都会重新定义一次function,严重影响内存。
(3)组合继承:
这种继承方式相比第二种将父类方法中的函数定义在了原型内
function Parent() { this.name = "dqhan"; this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘]; } Parent.prototype.action = function () { console.log(‘Iamdqhan‘); }; function Child() { Parent.call(this); }
Child.prototype = new Parent(); var child1 = new Child("value1"); var child2 = new Child("value2"); child1.action === child2.action // true
结果:
优点:这种方式成功的避免了重复定义function的尴尬情况,同时解决了原型共享的问题。
缺点:如果有两个子类继承父类,但是父类的属性有一个子类不用,怎么搞?这个是没法避免的,而且父类的属性全部在子类的原型上,很不美观。
(4)寄生组合继承:
为了扣掉组合继承中原型中不需要的属性,看到为了满足这一点,可不可以介样:
function Parent() { this.name = "dqhan"; this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘]; } Parent.prototype.action = function () { console.log(‘Iamdqhan‘); }; function Child() { Parent.call(this); }; Child.prototype = Parent.prototype;
结果:
乍一看好像对,实际Child中的__proto__为Object,并不是Parent,已经背离了Child继承Parent的目的。
改进:
function Parent() { this.name = "dqhan"; this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘]; } Parent.prototype.action = function () { console.log(‘Iamdqhan‘); }; function Child() { Parent.call(this); }; function initObject(obj) { var F = function () { }; F.prototype = obj; return new F(); } //Child.prototype = Parent.prototype; var newPrototype = initObject(Parent.prototype); newPrototype.constructor = Child; Child.prototype = newPrototype; var child1 = new Child("value1"); var child2 = new Child("value2");
结果:
Child的__proto__已经为Parent,而且去掉了原型上我们不要的属性只留下父类在原型上定义的方法,核心目的就是为了更改原型链的设置。
浅谈JS中的继承