7.13 JS继承的实现方法
标签(空格分隔): JS继承的实现方法
JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。
js继承的实现方式
js继承的实现方式有6种
1.原型链继承
2.构造继承 3.实例继承 4.拷贝继承 5.组合继承 6.寄生组合继承
既然要实现继承,那么首先我们得有一个父类。代码如下:
//定义一个动物类 function Animal(name){ //属性 this.name = name || 'Animal'; //实例方法 this.sleep = function(){ console.log(this.name + '正在睡觉'); } } //原型方法 Animal.prototype.eat = function(food){ console.log(this.name + '正在吃:' +food); }
1、原型链继承
核心:将父类的实例作为子类的原型
function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; //Test Code var cat = new Cat(); console.log(cat.name); //cat console.log(cat.eat('fish')); //cat正在吃:fish console.log(cat.sleep()); //cat正在睡觉 console.log(cat instanceof Animal); //true console.log(cat instanceof Cat); //true
特点:
1.非常纯粹的继承关系,实例是子类的实例,也是父类的实例
2.父类新增的原型方法/原型属性,子类都能访问到
3.简单,易于实现
缺点:
1.要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
2.无法实现多继承
3.来自原型对象的引用属性是所有实例共享的
4.创建子类实例时,无法向父类构造函数传参
需要注意的两点:
1.别忘记默认的原型:
所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的
所有的函数的默认原型都是Object的实例,因此默认的原型都会包含一个内部指针,指向Object.prototype。这也正是所有的自定义类型都会继承toString(),valueOf()等默认方法的根本原因。2.确定原型和实例的关系
可以通过两种方式来确定原型和实例之间的关系。
- 使用instanceof操作符 只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true.(格式:alert(instance instanceof Object);//true)
instanceof 用于判断一个变量是否某个对象的实例
- 使用isPrototypeOf()方法 同样,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此isPrototypeOf()方法也会返回true.(格式:alert(Object.prototype.isPrototypeOf(instance));//true)
3.谨慎地定义方法
子类型有时候需要覆盖超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎么样,给原型添加方法的代码一定要放在替换原型的语句之后。例子如下:
function SuperType(){ this.prototype = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //继承了SuperType SubType.prototype = new SuperType(); //添加新方法 SubType.prototype.getSubValue = function(){ return this.subproperty; }; //重写超类型中的方法 SubType.prototype.getSuperValue = function(){ return false; }; var instance = new SubType(); alert(instance.getSuperValue());
不能使用字面量创建原型方法,因为这样就会重写原型链。例子如下:
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //继承了SuperType SubType.prototype = new SuperType(); //使用字面量添加新方法,会导致上一行代码无效 SubType.prototype = { getSubValue : function(){ return this.subproperty; }, someOtherMethod : function(){ return false; } }; var instance = new SubType(); alert(instance.getSuperValue());//error;
2、构造函数
核心:使用父类的构造函数来增强子类的实例,等于是复制父类的实例属性给子类(没用到原型)
function Cat(){ Animal.call(this);//继承了Animal this.name = name || 'Tom'; } //Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); console.log(cat instanceof Cat);
特点:
1.解决了1中,子类实例共享父类引用属性的实例
2.创建子类实例时,可以向父类传递参数
3.可以实现多继承(call多个父类对象)
举例:
apply举例:
倒数第二句:通过apply方法,改变了Parent的指向,此时Parent的新指向为Child的实例,并且调用child的getName()方法。
去掉最后一句,Parent.apply(child,[child.getName()]);调用的顺序为:Child(‘张’)、getName()、Parent(‘张’),由于没人任何console.log,所以不会打印任何信息、
apply实现原理:改变函数内部的函数上下文this,使它指向传入函数的具体对象
缺点:
1.实例并不是父类的实例,只是子类的实例
2.只能继承父类的实例属性和方法,不能继承原型属性方法
3.无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
注意:
传递参数:
相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数
例子:
function SuperType(name){ this.name = name; } function SubType(){ //继承了SuperType,同时还传递了参数 SuperType.call(this,"Nicholas"); //实例属性 this.age = 29; } var instance = new SubType(); alert(instance.name);//"Nicholas" alert(instance.age);//29
3.实例继承
核心:为父类实例添加新特性,作为子类实例返回。
function Cat(name){ var instance = new Animal(); instance.name = name || 'Tom'; return instance; } //Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal);//true console.log(cat instanceof Cat);//false
特点:
- 不限制调用方式,不管是new 子类()还是 子类(),返回的对象都具有相同的效果
- 实例是父类的实例,不是子类的实例
- 不支持多继承
4.拷贝继承
function Cat(){ var animal = new Animal(); for(var p in animal){ Cat.prototype[p] = animal[p]; } Cat.prototype.name = name || 'Tom'; } //Test Code var cat = new Cat(); console.log(cat.name); //Tom console.log(cat.sleep()); //Tom正在睡觉 console.log(cat instanceof Animal); //false console.log(cat instanceof Cat);//true
特点:
- 支持多继承
缺点:
- 效率较低,内存占用高(因为要拷贝父类的属性)
- 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
5.组合继承
核心:通过调用父类构造,继承父类的属性并保留传参的有点,然后通过将父类实例作为子类原型,实现函数复用。
function Animal(name){ //属性 this.name = name || 'Animal'; //实例方法 this.sleep = function(){ console.log(this.name + '正在睡觉'); } } function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal(); //Test Code var cat = new Cat(); console.log(cat.name);//Tom console.log(cat.sleep());//Tom正在睡觉 console.log(cat instanceof Animal);//true console.log(cat instanceof Cat);//true
特点:
- 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可复用
缺点:
1.调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
6.寄生组合式继承
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造函数的时候,就不会初始化两次实例方法/属性,避免组合继承的缺点
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } (function(){ //创建一个没有实例方法的类 var Super = function(){}; Super.prototype = Animal.prototype; //将实例作为子类的原型 Cat.prototype = new Super(); })(); //Test Code var cat = new Cat(); console.log(cat.name);//Tom console.log(cat.sleep());//Tom 正在睡觉 console.log(cat instanceof Animal);//true console.log(cat instanceof Cat);//true
特点:堪称完美
缺点:实现较为复杂
原型链
prototype和__proto__的概念
prototype是函数的一个属性(每个函数都有一个prototype属性),这个属性是一个指针,指向一个对象。它是显示修改对象的原型的属性。
__proto__是一个对象拥有的内置属性(请注意:prototype是函数的内置属性,__proto__是对象的内置属性),是JS内部使用寻找原型链的属性。
用chrome和FF都可以访问到对象的__proto__属性,IE不可以。
拓展
我们可以直接获取一个对象的[[prototype]]链。
在ES5中,标准的方法是:Object.getPrototypeOf(a);
可以验证一下,这个对象引用和我们想象的是不是一样的:
Object.getprototypeOf(a) === Foo.prototype;//true
绝大多数(不是所有!)浏览器也支持一种非标准的方法来访问内部[[prototype]]属性:
a._proto_ === Foo.prototype;//true
这里就要使用__proto__属性来链接到原型(也就是Person.prototype)进行查找。最终在原型上找到了age属性。
原型
JS中的对象有一种特殊的[[prototype]]内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时[[prototype]]属性都会被赋一个非空的值。
var myObject = { a : 2; }; myObject.a;//2
[[prototype]]引用的用处:
当你试图引用对象的属性时会触发[[Get]]操作,比如myObject.a 对于默认的[[Get]]操作来说,第一步是检查对象本身是否有这个属性,如果有的话就使用它。但是如果a不在myObject中,就需要使用对象的[[prototype]]链。
对于默认的[[Get]]操作来说,如果无法在对象本身找到需要的属性,就会继续访问对象的[[prototype]]链。var anotherObject = { a:2; }; //创建一个关联到anotherObject的对象 var myObject = Object.create(anotherObject); myObject.a;//2
现在myObject对象的[[prototype]]关联到了anotherObject.显然myObject.a并不存在,但是尽管如此,属性访问仍然成功的(在anotherObject中)找到了值2.
但是,如果anotherObject中也找不到a并且[[prototype]]链不为空的话,就会继续查找下去。