Javascript中实现继承的方式
Posted wall-ee
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Javascript中实现继承的方式相关的知识,希望对你有一定的参考价值。
js中实现继承和传统的面向对象语言中有所不同:传统的面向对象语言的继承由类来实现,而在js中,是通过构造原型来实现的,原型与如下几个术语有关:
①构造函数:在构造函数内部拥有一个prototype属性,这个属性指向原型。在js中,构造函数和函数是属于一个概念范畴,都是引用类型,都可以实例化为对象。唯一不同的地方是使用上的不同,用new关键字来调用函数就能让这个函数变成一个构造函数,这一点很好理解,因为在像java、C#这中类C语言中构造函数就是和方法是同名的。而如果要实例化一个类,那么就用new来构造一个类的实例。prototype保存一个指针,指向这个构造函数的原型。实现继承后,原型中保存的引用类型(像Arrya、Function等)会被所有实例化后的对象所共享。原型是js中实现继承的重要方式,通过构造原型链,可以实现代码复用,后面会用代码来阐述这一点。
②原型:在原型内部保存一个constructor的属性,这个属性保存一个指针,指向构造函数。
③对象(实例):在实例内部保存一个[[prototype]]的指针,指向原型,通过这个[[prototype]],所有实例就可以访问原型中的所有属性和方法。
在继承上面需要注意的是原型中保存的基础类型的属性在继承后能被重写(由于js动态添加特性),这个是没有问题的(如string、number、bool等),问题出在引用类型的属性(和函数)上,如果在原型中定义了一个Array,那么在所有的实例中将共享这个属性,那么,在一个实例中对这个Array所做的任何改变都会体现到其他实例中,这就破坏了面向对象的程序设计中对封装的要求。所以,下面介绍一个如果去有效的建立继承,这里我记录两个最为理想的例子。
第一个:
1 function BaseClass(name,age) { 2 this.name = name; 3 this.age = age; 4 } 5 6 BaseClass.prototype = { 7 constructor: BaseClass, 8 sayName: function() { 9 console.log(this.name); 10 } 11 }; 12 function DerivedClass(name,age,job) { 13 this.job = job; 14 BaseClass.call(this,name,age,job); 15 } 16 17 DerivedClass.prototype = new BaseClass(); 18 DerivedClass.prototype.sayHi=function() { 19 console.log(this.job); 20 } 21 var derived1 = new DerivedClass("bob", 16, "SoftWare Engineer"); 22 console.log(derived1.name);//pang 23 derived1.sayName();//pang 24 derived1.sayHi();//SoftWare Engineer 25 var derived2 = new DerivedClass("loryn", 17, "doctor"); 26 console.log(derived2.name);//loryn 27 derived2.sayName();//lory 28 derived2.sayHi();//doctor
这个就是一种业内比较推荐的实现继承的一种方式,在这个例子中BaseClass被当作基类,而DerivedClass被当作子类。可以看到,js中实现继承的重要的一种方式就是通过构造原型链:
DerivedClass.prototype = new BaseClass();将DerivedClass构造函数的prototype属性指向了BaseClass.这段代码运行起来比较正常,目前还看不出一些bug。下面我们对这个代码段进行改造一下:
function BaseClass(name,age) { this.name = name; this.age = age; } BaseClass.prototype = { constructor: BaseClass, favoriteColor:["red","green"],//新添加的属性 sayName: function() { console.log(this.name); } }; function DerivedClass(name,age,job) { this.job = job; BaseClass.call(this,name,age,job); } DerivedClass.prototype = new BaseClass(); DerivedClass.prototype.sayHi=function() { console.log(this.job); }
var derived1 = new DerivedClass("bob", 16, "SoftWare Engineer");
derived1.favoriteColor.push("black");
console.log(derived1.favoriteColor);//["red","green","blacjk"]
var derived2 = new DerivedClass("loryn", 17, "doctor");
console.log(derived2.favoriteColor);//["red","green","blacjk"]怪异的地方在这里
这上面这段代码的基础上给BaseClass的原型中添加了一个名为favoriteColor的属性,这个属性是一个数组,是引用类型,现在,还是定义两个DerivedClass,像其中一个DerivedClass的favoriteColor中添加一个“black”的元素,然而,这个元素可以在刚实例化后的derived2中也可以共享到。这里展示的就是这种流行的js的继承方式的一个弊端。所以,在使用这种方式进行继承的时候,要注意几点:在继承的过程中要注意原型和构造函数的”功能"。在原型中只存放函数,因为函数本身是对象,由于js中this关键字的灵活性,在js的原型中存放函数可以让函数得到最大化的重用。在构造函数中放入属性,然而这对于引用类型的值来说还是没有什么卵用,因为DerivedClass.prototype = new BaseClass();这句,将子类的原型赋予了一个基类的构造函数,所以在子类的原型中也存放了关于基类的构造函数中的所有属性,包括引用类型的属性。所以,使用这种继承方式的话,根本的解决方式是不在基类中声明引用类型的属性。这种继承方式还有一个弊端就是多处使用构造函数:第一次是在DerivedClass.prototype = new BaseClass();,第二次是在BaseClass.call(this,name,age,job);性能上也会有一些问题。
下面介绍第二种方式:
首先定义两个函数:
function object(o) { function F() { } F.prototype = o.prototype; return new F(); } function inheritPrototype(subType, superType) { var prototype = object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; }
一个函数是object(),这个函数接受一个构造函数的原型,object内部先定义一个构造函数,然后将传入的原型赋给这个定义好的构造函数的原型。
另一个函数是inheritPrototype,从字面意思上来讲就是继承原型。这个函数接受两个构造函数作为参数,在函数内部,使用object()函数返回一个实例化后的构造函数,接着将这个构造函数当作一个原型,并将为这个构造函数添加一个constructor的属性(constructor是原型中的属性,因为object函数返回的是一个构造函数,并没有constructor这个属性,所以要手动设置一个),并将constructor属性的值手动设置为传入的第一个参数(可以看出传入的第一个参数是作为子类,另一个是基类)。然后将第一个参数的原型设置为这个构造函数。这个函数基本解决了上面第一种所遇到的各种不好的情况:
"use strict"; function object(o) { function F() { } F.prototype = o; return new F(); } function inheritPrototype(subType, superType) { var prototype = object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; } function BaseClass(name, age) { this.name = name; this.age = age; this.favoriteColor = ["red", "green"]; //新添加的属性 } BaseClass.prototype.sayName = function () { console.log(this.name); }; function DerivedClass(name, age, job) { this.job = job; BaseClass.call(this, name, age); } inheritPrototype(DerivedClass, BaseClass);//替换DerivedClass.prototype=new BaseClass(); DerivedClass.prototype.sayHi = function () { console.log(this.job); } var derived1 = new DerivedClass("bob", 16, "SoftWare Engineer"); derived1.favoriteColor.push("black"); console.log(derived1.favoriteColor);//["red","green","blacjk"] derived1.sayName();//bob var derived2 = new DerivedClass("loryn", 17, "doctor"); console.log(derived2.favoriteColor);//["red","green"]
首先解决的是构造函数的使用次数,这个优化后的只使用了一次构造函数:
BaseClass.call(this, name, age);
然后解决的是原型链的问题:
inheritPrototype(DerivedClass, BaseClass);//替换DerivedClass.prototype=new BaseClass();
在inheritPrototype函数中调用object函数,object函数直接将传入的原型赋给新创建的这个构造函数的原型。而在inheritPrototype函数中完成了两个构造函数之间的继承关系。
以上是关于Javascript中实现继承的方式的主要内容,如果未能解决你的问题,请参考以下文章