本文实例讲述了JavaScript使用原型和原型链实现对象继承的方法。分享给大家供大家参考,具体如下:
实际上JavaScript并不是一门面向对象的语言,不过JavaScript基于原型链的继承方式、函数式语法,使得编程相当灵活,所以可以利用原型链来实现面向对象的编程。
之前对JavaScript一直都是一知半解,这两天看了一下原型链这一块知识,综合练习了一下JavaScript的对象继承方式。
以下就是原型链和原型的关系,引用网上的一张图
在Javascript中,每个函数都有一个原型属性prototype指向自身的原型,而由这个函数创建的对象也有一个proto属性指向这个原型,而函数的原型是一个对象,所以这个对象也会有一个proto指向自己的原型,这样逐层深入直到Object对象的原型,这样就形成了原型链。
- 基本继承模式
function FatherClass() { this.type = 'father'; } FatherClass.prototype.getTyep = function() { console.log(this.type); } FatherClass.prototype.obj = {age: 35}; function ChildClass() { this.type = 'child'; } ChildClass.prototype = FatherClass(); ChildClass.prototype.getType = function() { console.log(this.type); } var father = new FatherClass(); var child = new ChildClass(); father.getTyep(); child.getType();
此方法有优点也有缺点,继承的实现很简单,代码简单容易理解,但是子类继承父类的成员变量需要自己重新初始化,相当于父类有多少个成员变量,在子类中还需要重新定义及初始化
function FatherClass(type) { this.type = type || 'father'; } function ChildClass(type) { this.type = type || 'child'; } ChildClass.prototype = FatherClass(); ChildClass.prototype.getType = function() { console.log(this.type); } var father = new FatherClass('fatClass'); var child = new ChildClass('chilClass');
上面这种情况还只是需要初始化name属性,如果初始化工作不断增加,这种方式是很不方便的。因此就有了下面一种改进的方式。
- 借用构造函数
var Parent = function(name){ this.name = name || 'parent' ; } ; Parent.prototype.getName = function(){ return this.name ; } ; Parent.prototype.obj = {a : 1} ; var Child = function(name){ Parent.apply(this,arguments) ; } ; Child.prototype = Parent.prototype ; var parent = new Parent('myParent') ; var child = new Child('myChild') ; console.log(parent.getName()) ; //myParent console.log(child.getName()) ; //myChild
这样我们就只需要在子类构造函数中执行一次父类的构造函数,同时又可以继承父类原型中的属性,这也比较符合原型的初衷,就是把需要复用的内容放在原型中,我们也只是继承了原型中可复用的内容。
- 临时构造函数模式(圣杯模式)
上面借用构造函数模式最后改进的版本还是存在问题,它把父类的原型直接赋值给子类的原型,这就会造成一个问题,就是如果对子类的原型做了修改,那么这个修改同时也会影响到父类的原型,进而影响父类对象,这个肯定不是大家所希望看到的。为了解决这个问题就有了临时构造函数模式。
var Parent = function(name){ this.name = name || 'parent' ; } ; Parent.prototype.getName = function(){ return this.name ; } ; Parent.prototype.obj = {a : 1} ; var Child = function(name){ Parent.apply(this,arguments) ; } ; var F = new Function(){} ; F.prototype = Parent.prototype ; Child.prototype = new F() ; var parent = new Parent('myParent') ; var child = new Child('myChild') ; console.log(parent.getName()) ; //myParent console.log(child.getName()) ; //myChild
个人综合模式
《Javascript模式》中到圣杯模式就结束了,可是不管上面哪一种方法都有一个不容易被发现的问题。大家可以看到我在'Parent'的prototype属性中加入了一个obj对象字面量属性,但是一直都没有用。我们在圣杯模式的基础上来看看下面这种情况:
var Parent = function(name){ this.name = name || 'parent' ; } ; Parent.prototype.getName = function(){ return this.name ; } ; Parent.prototype.obj = {a : 1} ; var Child = function(name){ Parent.apply(this,arguments) ; } ; var F = new Function(){} ; F.prototype = Parent.prototype ; Child.prototype = new F() ; var parent = new Parent('myParent') ; var child = new Child('myChild') ; console.log(child.obj.a) ; //1 console.log(parent.obj.a) ; //1 child.obj.a = 2 ; console.log(child.obj.a) ; //2 console.log(parent.obj.a) ; //2
在上面这种情况中,当我修改child对象obj.a的时候,同时父类的原型中的obj.a也会被修改,这就发生了和共享原型同样的问题。出现这个情况是因为当访问child.obj.a的时候,我们会沿着原型链一直找到父类的prototype中,然后找到了obj属性,然后对obj.a进行修改。再看看下面这种情况:
var Parent = function(name){ this.name = name || 'parent' ; } ; Parent.prototype.getName = function(){ return this.name ; } ; Parent.prototype.obj = {a : 1} ; var Child = function(name){ Parent.apply(this,arguments) ; } ; var F = new Function(){} ; F.prototype = Parent.prototype ; Child.prototype = new F() ; var parent = new Parent('myParent') ; var child = new Child('myChild') ; console.log(child.obj.a) ; //1 console.log(parent.obj.a) ; //1 child.obj.a = 2 ; console.log(child.obj.a) ; //2 console.log(parent.obj.a) ; //2
这里有一个关键的问题,当对象访问原型中的属性时,原型中的属性对于对象来说是只读的,也就是说child对象可以读取obj对象,但是无法修改原型中obj对象引用,所以当child修改obj的时候并不会对原型中的obj产生影响,它只是在自身对象添加了一个obj属性,覆盖了父类原型中的obj属性。而当child对象修改obj.a时,它先读取了原型中obj的引用,这时候child.obj和Parent.prototype.obj是指向同一个对象的,所以child对obj.a的修改会影响到Parent.prototype.obj.a的值,进而影响父类的对象。AngularJS中关于$scope嵌套的继承方式就是模范Javasript中的原型继承来实现的。
根据上面的描述,只要子类对象中访问到的原型跟父类原型是同一个对象,那么就会出现上面这种情况,所以我们可以对父类原型进行拷贝然后再赋值给子类原型,这样当子类修改原型中的属性时就只是修改父类原型的一个拷贝,并不会影响到父类原型。具体实现如下:
var deepClone = function(source,target){ source = source || {} ; target = target || {}; var toStr = Object.prototype.toString , arrStr = '[object array]' ; for(var i in source){ if(source.hasOwnProperty(i)){ var item = source[i] ; if(typeof item === 'object'){ target[i] = (toStr.apply(item).toLowerCase() === arrStr) ? [] : {} ; deepClone(item,target[i]) ; }else{ target[i] = item; } } } return target ; } ; var Parent = function(name){ this.name = name || 'parent' ; } ; Parent.prototype.getName = function(){ return this.name ; } ; Parent.prototype.obj = {a : '1'} ; var Child = function(name){ Parent.apply(this,arguments) ; } ; Child.prototype = deepClone(Parent.prototype) ; var child = new Child('child') ; var parent = new Parent('parent') ; console.log(child.obj.a) ; //1 console.log(parent.obj.a) ; //1 child.obj.a = '2' ; console.log(child.obj.a) ; //2 console.log(parent.obj.a) ; //1
综合上面所有的考虑,Javascript继承的具体实现如下,这里只考虑了Child和Parent都是函数的情况下:
var deepClone = function(source,target){ source = source || {} ; target = target || {}; var toStr = Object.prototype.toString , arrStr = '[object array]' ; for(var i in source){ if(source.hasOwnProperty(i)){ var item = source[i] ; if(typeof item === 'object'){ target[i] = (toStr.apply(item).toLowerCase() === arrStr) ? [] : {} ; deepClone(item,target[i]) ; }else{ target[i] = item; } } } return target ; } ; var extend = function(Parent,Child){ Child = Child || function(){} ; if(Parent === undefined) return Child ; //借用父类构造函数 Child = function(){ Parent.apply(this,argument) ; } ; //通过深拷贝继承父类原型 Child.prototype = deepClone(Parent.prototype) ; //重置constructor属性 Child.prototype.constructor = Child ; } ;
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。
本文向大家介绍JavaScript原型继承和原型链原理详解,包括了JavaScript原型继承和原型链原理详解的使用技巧和注意事项,需要的朋友参考一下 这篇文章主要介绍了JavaScript原型继承和原型链原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在讨论原型继承之前,先回顾一下关于创建自定义类型的方式,这里推荐将构造函数和原型模式组
本文向大家介绍js对象继承之原型链继承实例,包括了js对象继承之原型链继承实例的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了js对象继承之原型链继承的用法。分享给大家供大家参考。具体分析如下: 其它说明:kitty也是有构造方法的,即new Object()。Object默认也有一些方法和属性,见javascript手册中的 "object对象"。同时,它也有原型,只是为空而已 { }。
本文向大家介绍JavaScript的原型继承详解,包括了JavaScript的原型继承详解的使用技巧和注意事项,需要的朋友参考一下 JavaScript是一门面向对象的语言。在JavaScript中有一句很经典的话,万物皆对象。既然是面向对象的,那就有面向对象的三大特征:封装、继承、多态。这里讲的是JavaScript的继承,其他两个容后再讲。 JavaScript的继承和C++的继承不大一样,C
本文向大家介绍javascript原型链继承用法实例分析,包括了javascript原型链继承用法实例分析的使用技巧和注意事项,需要的朋友参考一下 本文实例分析了javascript原型链继承的用法。分享给大家供大家参考。具体分析如下: 当我们对对象的prototype属性进行完全重写时,有时候会对对象constructor属性产生一定的负面影响。 所以,在我们完成相关的继承关系设定后,对这些
本文向大家介绍图文详解JavaScript的原型对象及原型链,包括了图文详解JavaScript的原型对象及原型链的使用技巧和注意事项,需要的朋友参考一下 对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__proto__混淆,二来它们之间的各种指向实在有些复杂,其实市面上已经有非常多的文章在尝试说清楚,有一张所谓很经典的图,上面画了各种线条,一会连接这
本文向大家介绍JavaScript中的原型和继承详解(图文),包括了JavaScript中的原型和继承详解(图文)的使用技巧和注意事项,需要的朋友参考一下 请在此暂时忘记之前学到的面向对象的一切知识。这里只需要考虑赛车的情况。是的,就是赛车。 最近我正在观看 24 Hours of Le Mans ,这是法国流行的一项赛事。最快的车被称为 Le Mans 原型车。这些车虽然是由“奥迪”或“