您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息
免费发信息

JavaScript继承的实现方法有哪些

2024/4/24 19:06:37发布5次查看
javascript继承的实现方法:1、利用构造原型模式;2、使用动态原型,根据面向对象的设计原则,类型的所有成员应该都被封装在类结构体内;3、使用工厂模式;4、使用类继承,即在子类中调用父类构造函数。
本教程操作环境:windows7系统、javascript1.8.5版、dell g3电脑。
javascript 是以对象为基础,以函数为模型,以原型为继承的面向对象开发模式。本节将详细介绍定义 javascript 类型的方法,以及实现继承的常用模式。
构造原型
直接使用 prototype 原型设计类的继承存在两个问题。
由于构造函数事先声明,而原型属性在类结构声明之后才被定义,因此无法通过构造函数向原型动态传递参数。这样实例化对象都是一个模样,没有个性。要改变原型属性值,则所有实例都会受到干扰。
当原型属性的值为引用类型数据时,如果在一个对象实例中修改该属性值,将会影响所有的实例。
示例1
简单定义 book 类型,然后实例化。
function book () {}; //声明构造函数book.prototype.o = {x : 1, y : 2}; //构造函数的原型属性o是一个对象var book1 = new book (); //实例化对象book1var book2 = new book (); //实例化对象book2console.log(book1.o.x); //返回1console.log(book2.o.x); //返回1book2.o.x = 3; //修改实例化对象book2中的属性x的值console.log(book1.o.x); //返回3console.log(book2.o.x); //返回3
由于原型属性 o 为一个引用型的值,所以所有实例的属性 o 的值都是同一个对象的引用,一旦 o 的值发生变化,将会影响所有实例。
构造原型正是为了解决原型模式而诞生的一种混合设计模式,它把构造函数模式与原型模式混合使用,从而避免了上述问题的发生。
实现方法:对于可能会相互影响的原型属性,并且希望动态传递参数的属性,可以把它们独立出来使用构造函数模式进行设计。对于不需要个性设计、具有共性的方法或属性,则可以使用原型模式来设计。
示例2
遵循上述设计原则,把其中两个属性设计为构造函数模式,设计方法为原型模式。
function book (title, pages) { //构造函数模式设计 this.title = title; this.pages = pages;}book.prototype.what = function () { //原型模式设计 console.log(this.title + this.pages);};var book1 = new book("javascript 程序设计", 160);var book2 = new book("c语言程序设计", 240);console.log(book1.title);console.log(book2.title);
构造原型模式是 ecmascript 定义类的推荐标准。一般建议使用构造函数模式定义所有属性,使用原型模式定义所有方法。这样所有方法都只创建一次,而每个实例都能够根据需要设置属性值。这也是使用最广的一种设计模式。
动态原型
根据面向对象的设计原则,类型的所有成员应该都被封装在类结构体内。例如:
function book (title, pages) { //构造函数模式设计 this.title = title; this.pages = pages; book.prototype.what = function () { //原型模式设计,位于类的内部 console.log(this.title + this.pages); };}
但当每次实例化时,类 book 中包含的原型方法就会被重复创建,生成大量的原型方法,浪费系统资源。可以使用 if 判断原型方法是否存在,如果存在就不再创建该方法,否则就创建方法。
function book (title, pages) { this.title = title; this.pages = pages; if (typeof book.islock == "undefined") { //创建原型方法的锁,如果不存在则创建 book.prototype.what = function () { console.log(this.title + this.pages); }; book.islock = true; //创建原型方法后,把锁锁上,避免重复创建 }}var book1 = new book("javascript 程序设计", 160);var book2 = new book("c语言程序设计", 240);console.log(book1.title);console.log(book2.title);
typeof book.islock 表达式能够检测该属性值的类型,如果返回为 undefined 字符串,则不存在该属性值,说明没有创建原型方法,并允许创建原型方法,设置该属性的值为 true,这样就不用重复创建原型方法。这里使用类名 book,而没有使用 this,这是因为原型是属于类本身的,而不是对象实例的。
动态原型模式与构造原型模式在性能上是等价的,用户可以自由选择,不过构造原型模式应用比较广泛。
工厂模式
工厂模式是定义类型的最基本方法,也是 javascript 最常用的一种开发模式。它把对象实例化简单封装在一个函数中,然后通过调用函数,实现快速、批量生产实例对象。
示例1
下面示例设计一个 car 类型:包含汽车颜色、驱动轮数、百公里油耗 3 个属性,同时定义一个方法,用来显示汽车颜色。
function car (color, drive, oil) { //汽车类 var _car = new object(); //临时对象 _car.color = color; //初始化颜色 _car.drive = drive; //初始化驱动轮数 _car.oil = oil; //初始化百公里油耗 _car.showcolor = function () { //方法,提示汽车颜色 console.log(this.color); }; return _car; //返回实例}var car1 = car("red", 4, 8);var car2 = car("blue", 2, 6);car1.showcolor(); //输出“red”car2.showcolor(); //输出“blue”
上面代码是一个简单的工厂模式类型,使用 car 类可以快速创建多个汽车实例,它们的结构相同,但是属性不同,可以初始化不同的颜色、驱动轮数和百公里油耗。
示例2
在类型中,方法就是一种行为或操作,它能够根据初始化参数完成特定任务,具有共性。因此,可以考虑把方法置于 car() 函数外面,避免每次实例化时都要创建一次函数,让每个实例共享同一个函数。
function showcolor () { //公共方法,提示汽车颜色 console.log(this.color);};function car (color, drive, oil) { //汽车类 var _car = new object(); //临时对象 _car.color = color; //初始化颜色 _car.drive = drive; //初始化驱动轮数 _car.oil = oil; //初始化百公里油耗 _car.showcolor = showcolor; //引用外部函数 return _car; //返回实例}
在上面这段重写的代码中,在函数 car() 之前定义了函数 showcolor()。在 car() 内部,通过引用外部 showcolor() 函数,避免了每次实例化时都要创建一个新的函数。从功能上讲,这样解决了重复创建函数的问题;但是从语义上讲,该函数不太像是对象的方法。
类继承
类继承的设计方法:在子类中调用父类构造函数。
在 javascript 中实现类继承,需要注意以下 3 个技术问题。
在子类中,使用 apply 调用父类,把子类构造函数的参数传递给父类父类构造函数。让子类继承父类的私有属性,即 parent.apply(this, arguments); 代码行。
在父类和子类之间建立原型链,即 sub.prototype = new parent(); 代码行。通过这种方式保证父类和子类是原型链上的上下级关系,即子类的 prototype 指向父类的一个实例。
恢复子类的原型对象的构造函数,即 sub.prototype.constructor=sub;语句行。当改动 prototype 原型时,就会破坏原来的 constructor 指针,所以必须重置 constructor。
示例1
下面示例演示了一个三重继承的案例,包括基类、父类和子类,它们逐级继承。
//基类basefunction base (x) { //构造函数base this.get = function () { //私有方法,获取参数值 return x; }}base.prototype.has = function () { //原型方法,判断get()方法返回值是否为0 return ! (this.get() == 0);}//父类parentfunction parent () { //构造函数parent var a = []; //私有数组a a = array.apply(a, arguments); //把参数转换为数组 base.call(this, a.length); //调用base类,并把参数数组长度传递给它 this.add = function () { //私有方法,把参数数组补加到数组a中并返回 return a.push.apply(a, arguments) } this.geta = function () { //私有方法,返回数组a return a; }}parent.prototype = new base(); //设置parent原型为base的实例,建立原型链parent.prototype.constructor = parent; //恢复parent类原型对象的构造器parent.prototype.str = function (){ //原型方法,把数组转换为字符串并返回 return this.geta().tostring();}//子类subfunction sub () { //构造函数 parent.apply(this, arguments); //调用parent类,并把参数数组长度传递给它 this.sort = function () { //私有方法,以字符顺序对数组进行排序 var a = this.geta(); //获取数组的值 a.sort.apply(a, arguments); //调用数组排序方法 sort()对数组进行排序 }}sub.prototype = new parent(); //设置sub原型为parent实例,建立原型链sub.prototype.constructor = sub; //恢复sub类原型对象的构造器//父类parent的实例继承类base的成员var parent = new parent (1, 2, 3, 4); //实例化parent类console.log(parent.get()); //返回4,调用base类的方法get()console.log(parent.has()); //返回true,调用base类的方法has()//子类sub的实例继承类parent和类base的成员var sub = new sub (30, 10, 20, 40); //实例化sub类sub.add(6, 5); //调用parent类方法add(),补加数组console.log(sub.geta()); //返回数组30,10,20,40,6,5sub.sort(); //排序数组console.log(sub.geta()); //返回数组10,20,30,40,5,6console.log(sub.get()); //返回4,调用base类的方法get()console.log(sub.has()); //返回true,调用base类的方法has()console.log(sub.str()); //返回10,20,30,40,5,6
【设计思路】
设计子类 sub 继承父类 parent,而父类 parent 又继承基类 base。base、parent、sub 三个类之间的继承关系是通过在子类中调用的构造函数来维护的。
例如,在 sub 类中,parent.apply(this, arguments); 能够在子类中调用父类,并把子类的参数传递给父类,从而使子类拥有父类的所有属性。
同理,在父类中,base.call(this, a.length); 把父类的参数长度作为值传递给基类,并进行调用,从而实现父类拥有基类的所有成员。
从继承关系上看,父类继承了基类的私有方法 get(),为了确保能够继承基类的原型方法,还需要为它们建立原型链,从而实现原型对象的继承关系,方法是添加语句行 parent.prototype=new base();。
同理,在子类中添加语句 sub.prototype=new parent();,这样通过原型链就可以把基类、父类和子类串连在一起,从而实现子类能够继承父类属性,还可以继承基类的属性。
示例2
下面尝试把类继承模式封装起来,以便规范代码应用。
function extend (sub, sup) { //类继承封装函数 var f = function () {}; //定义一个空函数 f.prototype = sup.prototype; //设置空函数的原型为父类的原型 sub.prototype = new f (); //实例化空函数,并把父类原型引用传给给子类 sub.prototype.constructor = sub; //恢复子类原型的构造器为子类自身 sub.sup = sup.prototype; //在子类定义一个私有属性存储父类原型 //检测父类原型构造器是否为自身 if (sup.prototype.constructor == object.prototype.constructor) { sup.prototype.constructor = sup; //类继承封装函数 }}
【操作步骤】
1) 定义一个封装函数。设计入口为子类和父类对象,函数功能是子类能够继承父类的所有原型成员,不涉及出口。
function extend (sub, sup) { //类继承封装函数 //其中参数sub表示子类,sup表示父类}
2) 在函数体内,首先定义一个空函数 f,用来实现功能中转。设计它的原型为父类的原型,然后把空函数的实例传递给子类的原型,这样就避免了直接实例化父类可能带来的系统负荷。因为在实际开发中,父类的规模可能会很大,如果实例化,会占用大量内存。
3) 恢复子类原型的构造器为子类自己。同时,检测父类原型构造器是否与 object 的原型构造器发生耦合。如果是,则恢复它的构造器为父类自身。
下面定义两个类,尝试把它们绑定为继承关系。
function a (x) { //构造函数a this.x = x; //私有属性x this.get = function () { //私有方法get() return this.x; }}a.prototype.add = function () { //原型方法add() return this.x + this.x;}a.prototype.mul = function () { //原型方法mul() return this.x * this.x;}function b (x) { //构造函数b a.call (this.x); //在函数体内调用构造函数a,实现内部数据绑定}extend (b, a); //调用封装函数,把a和b的原型捆绑在一起var f = new b (5); //实例化类bconsole.log(f.get()); //继承类a的方法get(),返回5console.log(f.add()); //继承类a的方法add(),返回10console.log(f.mul()); //继承类a的方法mul(),返回25
在函数类封装函数中,有这么一句 sub.sup=sup.prototype;,在上面代码中没有被利用,那么它有什么作用呢?为了解答这个问题,先看下面的代码。
extend (b, a);b.prototype.add = function () { //为b类定义一个原型方法 return this.x + "" + this.x;}
上面的代码是在调用封装函数之后,再为 b 类定义了一个原型方法,该方法名与基类中原型方法 add() 同名,但是功能不同。如果此时测试程序,会发现子类 b 定义的原型方法 add() 将会覆盖父类 a 的原型方法 add()。
console.log(f.add()); //返回字符串55,而不是数值10
如果在 b 类的原型方法 add() 中调用父类的原型方法 add(),避免代码耦合现象发生。
b.prototype.add = function () { //定义子类b的原型方法add() return b.sup.add.call(this); //在函数内部调用父类方法add()}
【相关推荐:javascript学习教程】
以上就是javascript继承的实现方法有哪些的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录