我的学习笔记是根据我的学习情况来定期更新的,预计 2-3 天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧!
上一次我们分享了用 JavaScript 创建对象封装一个类,这次我们来分享如何用 JavaScript 实现继承和多态。
我们首先声明一个父类
- function ParentClass() {
- this.perentValue = true;
- }
然后我们为父类添加公有方法
- ParentClass.prototype.getPerentValue = function() {
- return this.perentValue;
- };
接着我们声明一个子类
- function ChildClass() {
- this.childValue = false;
- }
然后我们继承之前我们声明的父类
- ChildClass.prototype = new ParentClass();
接着我们为子类添加公有方法
- ChildClass.prototype.getChlidValue = function() {
- return this.childValue;
- }
看上去这和我们之前分享的封装很像,只不过我们将第一个类的实例赋值给了第二个类的原型,但是我们为什么要这么做呢?
类的原型对象就是为类的原型添加公有属性和添加公有方法,但是类不能直接访问这些属性和方法,必须通过原型 prototype 来访问。当我们实例化一个父类的时候,新创建的对象复制了父类构造函数里的属性和方法,并且将原型_protp_指向了父类的原型对象。
- __proto__是一个对象拥有的内置属性(请注意:prototype是函数的内置属性,__proto__是对象的内置属性),是JS内部使用寻找原型链的属性。
- 用chrome和FF都可以访问到对象的__proto__属性,IE不可以。
那么我们总结一下,类式继承的原理:
- 我们将父类对象赋值给子类的原型,那么这个子类的原型就可以访问父类原型上的属性和方法同时也可以访问从父类构造函数中赋值的属性和方法
- 我们可以通过点语法直接去访问父类的属性和方法
- var inheritance=new ChildClass();
- console.log(inheritance.getPerentValue());//true
- console.log(inheritance.getChlidValue());//false
我们可以通过 instanceof 来检查某个对象是否是某个类的实例,来进一步确认继承关系
- console.log(inheritance instanceof ParentClass);//true
- console.log(inheritance instanceof ChildClass);//true
我们可以看到对于 inheritance 对象,ChildClass 和 ParentClass 都是他的实例,为什么会这样呢?
因为 instanceof 是通过对象的 prototype 链来确定这个对象是否是某个类的实例而不关心对象与类的自身结构。
那么 ChildClass 继承于 ParentClass 那么 ParentClass 会是 ChildClass 的实例吗,我们再来打印看一下。
- console.log(ChildClass instanceof ParentClass);//false
答案是 false,为什么会出现这种情况呢?ChildClass 确实继承于 ParentClass 呀。
因为我们在实现 ChildClass 继承于 ParentClass 时,是将 ParentClass 的实例赋值给 ChildClass 的原型 prototype,所以是 ChildClass 的原型 prototype 继承了 ParentClass。
- console.log(ChildClass.prototype instanceof ParentClass);//true
- function ParentClass(){
- this.parentValue=['C#','JAVA','PHP'];
- }
- function ChildClass(){};
- ChildClass.prototype=new ParentClass();
- var inheritance1=new ChildClass();
- var inheritance2=new ChildClass();
- console.log(inheritance1.parentValue);//['C#','JAVA','PHP']
我们会看到此时,会打印出父类里的属性值,那么如果我们现在往父类的属性值中追加一个值又会怎样呢?
- inheritance1.parentValue.push('C++');
- console.log(inheritance2.parentValue);//['C#','JAVA','PHP,C++'']
我们看到 inheritance2 的属性也发生了变化,这在我们的编程中往往很容易埋下隐患。
那我们如果去解决这些问题呢?我们接着往下看
同样的我们先声明一个父类
- function ParentClass(id) {
- //引用类型的公有属性
- this.parentValue = ['C#', 'JAVA', 'PHP'];
- //值类型公有属性
- this.id = id;
- }
接着我们声明父类的原型方法
- ParentClass.prototype.showParents = function() {
- console.log(this.parentValue);
- }
下面我们声明子类
- function ChildClass(id) {
- //继承父类
- ParentClass.call(this, id);
- }
创建第一个子类的实例
- var inheritance1 = new ChildClass(1);
创建第二个子类的实例
- var inheritance2 = new ChildClass(2);
接着我们跟之前类式之前一样,往第一个实例的父类里追加一个值,看看有什么变化。
- inheritance1.parentValue.push('C++');
- console.log(inheritance1.parentValue);//['C#','JAVA','PHP']
- console.log(inheritance1.id);//1
- console.log(inheritance2.parentValue);//['C#','JAVA','PHP,C++'']
- console.log(inheritance2.id);//2
我们看到之前的问题解决了,这是为什么呢?
因为在继承父类的时候我们使用了 call 这个方法,这个方法可以更改函数的作用环境,所以在子类中对 ParentClass 调用实际上就是将子类中的变量在父类中执行一遍,由于父类中是通过 this 绑定属性的所以子类也就继承了父类的公有属性,我们之前讲个通过 this 继承的对象每次创建的时候都会创建一个新的实例,所以父类的原型方法不会被子类继承。我们可以打印出来看一看。
- inheritance1.showParents();//Uncaught TypeError: inheritance1.showParents is not a function
我们可以看到他抛了一个类型错误的异常: inheritance1.showParents 不是一个方法。
那么我们如何去解决这个问题呢?
从名字我们不难看出组合继承,是一种组合结构,他结合了类式继承,和构造函数继承的优点,他通过在子类的构造器中执行父类的构造函数,在子类原型上实例化父类就是组合继承,这样就融合了类式继承和构造函数继承的优点,并且过滤掉了他们两的缺点。
那么具体怎么实现呢? 我们来看下面的例子。
首先我们还是先声明一个父类
- function ParentClass(id) {
- //引用类型的公有属性
- this.parentValue = ['C#', 'JAVA', 'PHP'];
- //值类型公有属性
- this.id = id;
- }
同样的我们给父类声明原型方法
- ParentClass.prototype.getParentValue = function() {
- console.log(this.id);
- }
接着我们声明子类
- function ChildClass(id, childValue) {
- //通过构造函数继承,继承父类的id属性
- ParentClass.call(this, id);
- //子类新增公用属性
- this.childValue = childValue;
- }
紧接着我们使用类式继承
- ChildClass.prototype = new ParentClass();
同样的添加子类的原型方法
- ChildClass.prototype.getChildValue = function() {
- console.log(this.childValue);
- }
这样我们的组合继承就写好了,我们看到组合继承即使用构造函数继承又使用了类式继承,接下来我们调用看一看。
创建第一个子类的实例
- var inheritance1 = new ChildClass(1, 'hello');
我们往 parentValue 追加一个值
- inheritance1.parentValue.push('C++');
- console.log(inheritance1.parentValue);//['C#','JAVA','PHP,C++'']
接着我们再看看调用方法会不会抛之前 TypeError 的异常
- inheritance1.getParentValue();//1
- inheritance1.getChildValue();//hello
创建第二个子类的实例
- var inheritance2 = new ChildClass(2, 'word');
最后我们看看组合继承还会出现类式继承或构造器继承出现的问题吗?
- console.log(inheritance2.parentValue);//['C#','JAVA','PHP']
- inheritance2.getParentValue();//2
- inheritance2.getChildValue();//word
现在我们看到,子类的实例化中更改了父类继承下来的引用属性 parentValue,但是根本不会影响到其他的实例,这就解决了类式继承的弊端,同时子类实例化过程中又能将参数 id 传递到父类的构造函数中,这就解决了构造继承的弊端。
那组合继承是不是最完美的继承呢,其实并不是,我们来看在组合继承中,我们用构造器执行了一遍父类的构造函数,在实现子类原型的类式继承又调用了一遍父类的构造函数,因此父类构造函数被调用两次所以还不是最完美的继承方式。
那么最完美的方式是什么呢?,javascript 中多态又如何体现呢?我们将在下一章中来一一解答。
也谢谢大家看到这里:)如果你觉得我的分享还可以请点击推荐,分享给你的朋友让我们一起进步~
好了以上就是本次分享的全部内容,本次示例参考自 JavaScript 设计模式一书,让我们一点点积累一点点成长,希望对大家有所帮助。
来源: http://www.cnblogs.com/chen-jie/p/7283076.html