Javascript实现对象的继承
2014-03-10 17:47
483 查看
在Java和C#中,你可以简单的理解class是一个模子,对象就是被这个模子压出来的一批一批月饼。压个啥样,就得是个啥样,不能随便动,动一动就坏了。
而在Javascript中,没有模子,月饼被换成了面团,你可以捏成自己想要的样子。对象属性,方法不合适,可以修改。没有的方法,可以自己创建,发挥的自由度很大。
继承是指一个对象直接使用另一对象的属性和方法。
许多面向对象语言都支持两种继承方式:接口继承和实现继承。
接口继承:只继承方法签名;
实现继承:继承实际的方法。
Javascript是弱类型语言,大小写敏感。它不是原生支持继承,而是通过prototype去模拟的,所以是基于对象,不是面向对象。在ECMAScript中,无法实现接口继承,只支持实现继承,而实现继承主要是依靠原型链。每个函数都有call,apply方法,都有length,arguments,caller等属性。为什么每个函数都有?这肯定是"继承"的。函数由Function函数创建,因此继承Function.prototype中的方法。
ES5继承是先创建子类的实例对象this,再向this对象中添加父类的方法;
ES6继承是先创造父类的实例对象this,再用子类的构造函数修改this。
ECMAScript继承主要分两大类:引用对象继承,实例对象继承。
一、引用对象继承
子引用类型继承父引用类型,然后通过子引用类型生成的实例对象,具有父引用类型的特性。
1、原型链继承
【特点】
A、父类新增原型方法/原型属性,子类都能访问到;
B、简单,易于实现;
C、非常纯粹的继承关系,实例是子类的实例,也是父类的实例。
【缺点】
A、来自原型对象的引用属性是所有实例共享的;
B、创建子类实例时,无法向父类构造函数传参;
C、要想为子类新增属性和方法,必须要在new Parent()后执行;
D、无法实现多继承。
2、构造继承(伪造对象,经典继承,对象冒充继承)
【特点】
A、避免了引用类型的属性被所有实例共享;
B、可以在Child中向Parent传参;
C、可以实现多继承,call多个父类对象。
【缺点】
A、实例并不是父类的实例,只是子类的实例;
B、只能继承父类的实例属性和方法,不能继承原型属性/方法;
C、无法实现函数复用,每个子类都有父类实例函数的副本,影响性能。
3、组合继承,次优(类式继承)
【特点】
A、可以继承实例属性/方法,也可以继承原型属性/方法;
B、既是子类的实例,也是父类的实例;
C、可传参;
D、函数可复用。
【缺点】
A、调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)。
4、寄生继承
【特点】
A、给原生继承穿了个马甲,更像继承了;
B、createObj函数不是必须的,新对象是如何创建的并不重要,用createObj生的,new出来的,字面量现做的,都可以。
【缺点】
A、无法实现函数复用(没用到原型,当然不行)。
5、寄生组合继承,最优
【特点】
A、完美,在调用两次父类的构造的时候,不会初始化两次实例方法/属性;
B、对实例属性和原型属性分别进行了继承。
【缺点】
A、实现复杂。
二、实例对象继承
继承得到的对象都具有父实例对象的所有属性和方法,其实就是指对象的复制和克隆。
1、原型继承
ECMAScript5通过新增Object.create()方法规范化了原型式继承,这个方法接收两个参数:一个用作新对象原型的对象和一个作为新对象定义额外属性的对象。
【特点】
A、复制父实例对象;
【缺点】
A、原型引用属性会被所有实例共享;
B、无法实现代码复用(新对象是现取的,属性是现添的,都没用函数封装,怎么复用)。
2、拷贝继承
3、借用
有时可能恰好仅需要父对象其中的一两个方法,而不需要父对象的其他方法。这种情况下,可以使用call()和apply()来实现继承,区别就是传参的不同。
A、比如,借用Array中的slice方法
B、比如,借用对象的方法
4、绑定
ES5将bind()添加到Function.prototype,使得bind()像call()apply()一样易用。
三、ES6中的继承
使用class,extends,super关键字来实现类继承。
而在Javascript中,没有模子,月饼被换成了面团,你可以捏成自己想要的样子。对象属性,方法不合适,可以修改。没有的方法,可以自己创建,发挥的自由度很大。
继承是指一个对象直接使用另一对象的属性和方法。
许多面向对象语言都支持两种继承方式:接口继承和实现继承。
接口继承:只继承方法签名;
实现继承:继承实际的方法。
Javascript是弱类型语言,大小写敏感。它不是原生支持继承,而是通过prototype去模拟的,所以是基于对象,不是面向对象。在ECMAScript中,无法实现接口继承,只支持实现继承,而实现继承主要是依靠原型链。每个函数都有call,apply方法,都有length,arguments,caller等属性。为什么每个函数都有?这肯定是"继承"的。函数由Function函数创建,因此继承Function.prototype中的方法。
ES5继承是先创建子类的实例对象this,再向this对象中添加父类的方法;
ES6继承是先创造父类的实例对象this,再用子类的构造函数修改this。
ECMAScript继承主要分两大类:引用对象继承,实例对象继承。
一、引用对象继承
子引用类型继承父引用类型,然后通过子引用类型生成的实例对象,具有父引用类型的特性。
// 定义一个父类,公用 function Parent(name,age){ // 属性 this.name = name || 'Parent'; this.age = age || 18; // 实例方法 this.getAge = function(){ console.log('我的年龄是:'+ this.age); } } // 原型方法 Parent.prototype.getName = function() { console.log('我的名字是:'+ this.name); };
1、原型链继承
function Child(){ } Child.prototype = new Parent();//借助JavaScript中的委托机制,将父类的实例作为子类的原型。 Child.prototype.constructor = Child; Child.prototype.name = 'child1'; //test var child1 = new Child(); console.log(child1.name); console.log(child1.getAge()); console.log(child1.getName()); console.log(child1 instanceof Parent); //true console.log(child1 instanceof Child); //true
【特点】
A、父类新增原型方法/原型属性,子类都能访问到;
B、简单,易于实现;
C、非常纯粹的继承关系,实例是子类的实例,也是父类的实例。
【缺点】
A、来自原型对象的引用属性是所有实例共享的;
B、创建子类实例时,无法向父类构造函数传参;
C、要想为子类新增属性和方法,必须要在new Parent()后执行;
D、无法实现多继承。
2、构造继承(伪造对象,经典继承,对象冒充继承)
function Child(name){ Parent.call(this);//在子类内部调用父类,通过call,apply改变对象的this指向来继承。 //Parent.apply(this, arguments); this.name = name || 'Camille'; } //test var child1 = new Child(); console.log(child1.name); console.log(child1.getAge()); console.log(child1 instanceof Parent); // false console.log(child1 instanceof Child); // true
【特点】
A、避免了引用类型的属性被所有实例共享;
B、可以在Child中向Parent传参;
C、可以实现多继承,call多个父类对象。
【缺点】
A、实例并不是父类的实例,只是子类的实例;
B、只能继承父类的实例属性和方法,不能继承原型属性/方法;
C、无法实现函数复用,每个子类都有父类实例函数的副本,影响性能。
3、组合继承,次优(类式继承)
function Child(name,age){ Parent.call(this);//通过调用父类构造函数,继承父类的实例属性并保留传参的优点。 this.name = name || 'Camille'; this.age = age; } Child.prototype = new Parent();//通过将父类实例作为子类原型,实现函数复用及对原型属性和方法的继承。 Child.prototype.constructor = Child; //test var child1 = new Child('HouYi',20); console.log(child1.name); console.log(child1.getAge()); console.log(child1 instanceof Parent); // true console.log(child1 instanceof Child); // true
【特点】
A、可以继承实例属性/方法,也可以继承原型属性/方法;
B、既是子类的实例,也是父类的实例;
C、可传参;
D、函数可复用。
【缺点】
A、调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)。
4、寄生继承
function createObj(o){ var F = function(){}; F.prototype = o; return new F(); } function getObj(obj){ var cobj = createObj(obj);//创建新对象,通过寄生方式,砍掉父类的实例属性 cobj.setSex = function(){//增强对象 console.log('新对象的性别设置函数'); }; return cobj;//指定对象 } //test var psl = new Parent(); var child1 = getObj(psl); console.log(child1.name); console.log(child1.setSex()); console.log(child1 instanceof Parent); // true
【特点】
A、给原生继承穿了个马甲,更像继承了;
B、createObj函数不是必须的,新对象是如何创建的并不重要,用createObj生的,new出来的,字面量现做的,都可以。
【缺点】
A、无法实现函数复用(没用到原型,当然不行)。
5、寄生组合继承,最优
function createObj(o){ var F = function(){}; F.prototype = o; return new F(); } function Child(name,age){ Parent.call(this); this.name = name || 'Camille'; this.age = age; } var nobj = createObj(Parent.prototype);//通过寄生方式,砍掉父类的实例属性,创建对象 nobj.constructor = Child;//增强对象 Child.prototype = nobj;//指定对象 //test var child1 = new Child('HouYi',27); console.log(child1.name); console.log(child1.getAge()); console.log(child1 instanceof Parent); // true console.log(child1 instanceof Child); // true
【特点】
A、完美,在调用两次父类的构造的时候,不会初始化两次实例方法/属性;
B、对实例属性和原型属性分别进行了继承。
【缺点】
A、实现复杂。
二、实例对象继承
继承得到的对象都具有父实例对象的所有属性和方法,其实就是指对象的复制和克隆。
1、原型继承
// 定义一个父类,公用 function Parent(name,age){ // 属性 this.name = name || 'Parent'; this.age = age || 18; // 实例方法 this.getAge = function(){ console.log('我的年龄是:'+ this.age); } } // 原型方法 Parent.prototype.getName = function() { console.log('我的名字是:'+ this.name); };
/** * 创建一个继承父类原型的实例对象,这也是ES5中Object.create()的简单实现。 * 在object()函数内部, 先创建一个临时性的构造函数; * 然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。 */ function createObj(o){ var F = function(){}; F.prototype = o; return new F(); } //test var psl = new Parent(); var child1 = createObj(psl);//这是重点 child1.name = 'Camille'; console.log(child1.name); console.log(child1.getAge()); console.log(child1 instanceof Parent); // true
ECMAScript5通过新增Object.create()方法规范化了原型式继承,这个方法接收两个参数:一个用作新对象原型的对象和一个作为新对象定义额外属性的对象。
var child1 = Object.create(psl); console.log(child1.name); console.log(child1.getAge()); console.log(child1 instanceof Parent); // true
【特点】
A、复制父实例对象;
【缺点】
A、原型引用属性会被所有实例共享;
B、无法实现代码复用(新对象是现取的,属性是现添的,都没用函数封装,怎么复用)。
2、拷贝继承
3、借用
有时可能恰好仅需要父对象其中的一两个方法,而不需要父对象的其他方法。这种情况下,可以使用call()和apply()来实现继承,区别就是传参的不同。
A、比如,借用Array中的slice方法
Array.prototype.slice.call(); [].prototype.slice.call();
B、比如,借用对象的方法
//如果getAge的this指向childObj var parentObj = { name:'Parent', getAge:function(age){ console.log('我的年龄是:'+ age); } } var childObj = { name:'camille' } parentObj.getAge.call(childObj, '18'); //我的年龄是:18
//如果getAge的this指向全局对象 var parentObj = { name:'Parent', getAge:function(age){ console.log('我的年龄是:'+ age); } } var pgetage = parentObj.getAge; pgetage("27");//我的年龄是:27 var childObj = { name:'Camille' } function bind(o,m){ return function(){ return m.apply(o,arguments); }; } var cgetage = bind(childObj,parentObj.getAge); cgetage("30");//我的年龄是:30
4、绑定
ES5将bind()添加到Function.prototype,使得bind()像call()apply()一样易用。
// bind的内部实现 if(typeof Function.prototype.bind === 'undefined'){ Function.prototype.bind = function(thisArg){ var _this = this, slice = Array.prototype.slice, args = slice.call(arguments, 1); return function(){ return _this.apply(thisArg, args.concat(slice.call(arguments))); } } }
var parentObj = { name:'Parent', getAge:function(age){ console.log('我的年龄是:'+ age); } } var childObj = { name:'Camille' } var cgetage = parentObj.getAge.bind(childObj, '32'); cgetage();//我的年龄是:32
三、ES6中的继承
使用class,extends,super关键字来实现类继承。
//父类 Parentc class Parentc { // 父类的构造方法 constructor(name, age) { this.name = name; this.age = age; // 共享变量 this.LEGS_NUM = 2; } // 父类的getName方法 getName() { console.log(`我的名字是:${this.name}`); } // 父类的getAge方法 getAge() { console.log('我的年龄是:' + this.age); } }
//子类 Childc class Childc extends Parentc { constructor(name, age, city) { // 调用父类的构造方法 super(name, age); this.city = city; } // 覆盖父类的getName方法 getName() { console.log(`我的名字是:${this.name},来自${this.city}`); } } // 实例化一个Childc的实例 let aperson = new Childc('Camille', 18, '西安'); aperson.getName(); // 我的名字是:Camille,来自西安 aperson.getAge(); // 我的年龄是18 console.log(aperson.LEGS_NUM); // 2 console.log(aperson instanceof Childc); //true console.log(aperson instanceof Parentc); //true
相关文章推荐
- Javascript中对象继承的实现小例
- Java程序员从笨鸟到菜鸟之(二十九)javascript对象的创建和继承实现
- 使用apply方法实现javascript中的对象继承
- JavaScript使用原型和原型链实现对象继承的方法详解
- JavaScript2种构造函数创建对象的模式以及继承的实现
- Java程序员从笨鸟到菜鸟之(二十九)javascript对象的创建和继承实现
- JavaScript里面向对象的继承:不使用构造函数实现"继承"
- javascript使用call方式实现对象继承
- JavaScript2种构造函数创建对象的模式以及继承的实现
- 使用apply方法实现javascript中的对象继承
- JavaScript 利用原型和原型链实现对象继承
- JavaScript对象继承的实现
- Java程序员从笨鸟到菜鸟之(二十九)javascript对象的创建和继承实现
- 算法-javascript自实现继承extend(单次继承原型对象和单次继承构造函数)
- javascript 中面向对象实现 如何继承
- Javascript--对象冒充实现继承
- 使用apply方法实现javascript中的对象继承
- Javascript中JSON对象继承实现
- JavaScript采用对象冒充和原型链组合模式实现继承
- JavaScript 域名学习及对象的继承实现