JS继承的实现方式
既然要实现继承,那么首先我们得有一个父类,代码如下:
function Person(name,age){ this.name=name; this.age=age; this.favor=["run","sing","write"]; this.sleep = function(){ console.log(this.name + '正在睡觉!'); } } Person.prototype.eat=function(food){ console.log(this.name+"喜欢吃:"+ food); }
1、原型链继承
核心: 让子类的原型等于父类的实例
function Student(){ } Student.prototype=new Person(); Student.prototype.name="lilei";
测试代码
var std1=new Student(); std1.favor.push("painting"); console.log(std1.favor); //["run", "sing", "write", "painting"] console.log(std1.name); // lilei console.log(std1.age); //undefined console.log(std1.sleep()); //lilei正在睡觉 console.log(std1.eat("dumpling")); //lilei喜欢吃:dumpling console.log(std1 instanceof Person); //true console.log(std1 instanceof Student);//true var std2=new Student(); console.log(std2.favor); //["run", "sing", "write", "painting"] console.log(std2.name); //同实例1 console.log(std2.age); //同实例1 console.log(std2.sleep()); //同实例1 console.log(std2.eat("rice")); //lilei喜欢吃:rice
特点:
非常纯粹的继承关系,实例是子类的实例,也是父类的实例;
父类新增原型方法/原型属性,子类都能访问到。
缺点:
要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中;
无法实现多继承;
来自原型对象的引用属性是所有实例共享的 (eg. favor);
创建子类实例时,无法向父类构造函数传参。
2、构造继承
核心:在子类型构造函数的内部调用父类型的构造函数,等于是复制父类的实例属性给子类(没用到原型)
提示:函数只不过是在特定环境中执行代码的对象,可以通过call()或apply()方法在新创建的对象上执行构造函数
function Student(name,age,classid){ Person.call(this,name,age); //name和age是父类的形参 this.classid=classid; }
测试代码
var std1=new Student("zhangsan",19,"#1"); std1.favor.push("painting"); console.log(std1.favor); //["run", "sing", "write", "painting"] console.log(std1.name); //zs console.log(std1.age); //19 console.log(std1.sleep()); //zs正在睡觉! // console.log(std1.eat("dumpling")); Uncaught TypeError: std1.eat is not a function console.log(std1 instanceof Person); //false console.log(std1 instanceof Student); //true var std2=new Student("lisi",20,"#1"); console.log(std2.favor); //["run", "sing", "write"] console.log(std2.name); //lisi console.log(std2.age); //20 console.log(std2.sleep()); //zs正在睡觉! //console.log(std2.eat("rice")); Uncaught TypeError: std2.eat is not a function
特点:
解决了原型链中,子类实例共享父类引用属性的问题; ( favor)
创建子类实例时,可以向父类传递参数;
可以实现多继承(call多个父类对象)。
缺点:
实例并不是父类的实例,只是子类的实例;
只能继承父类的实例属性和方法,不能继承原型属性/方法; 如,eat()
无法实现函数复用,每个子类都有父类实例函数的副本,影响性能。
5、组合继承
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后让子类的原型等于父类的实例,实现函数复用。
function Student(name,age,classid){ Person.call(this,name,age); //实现对实例属性和方法的继承 第二次调用Person() this.classid=classid; } Student.prototype=new Person(); //实现对原型属性和方法的继承 第一次调用Person() Student.prototype.sayclass=function(){ console.log(this.classid); }
测试代码
var std1=new Student("zhangsan",19,"#1001"); std1.favor.push("painting"); console.log(std1.favor); //["run", "sing", "write", "painting"] console.log(std1.name); //zhangsan console.log(std1.age); //19 console.log(std1.sleep()); //zhangsan正在睡觉! console.log(std1.eat("dumpling")); //zhangsan喜欢吃:dumpling console.log(std1.sayclass()); //#1001 console.log(std1 instanceof Person); //true console.log(std1 instanceof Student); //true var std2=new Student("lisi",20,"#1002"); console.log(std2.favor); // ["run", "sing", "write"] console.log(std2.name); //lisi console.log(std2.age); //20 console.log(std2.sleep()); //lisi正在睡觉! console.log(std2.eat("rice")); //lisi喜欢吃:rice
特点:
弥补了构造函数继承的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
既是子类的实例,也是父类的实例
不存在引用属性共享问题
可传参
函数可复用
缺点:
调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
4、拷贝继承
方法1核心思想:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
var person={ age:18, favor:["run","sing","write"] } function object(o){ //o表示父类型的对象 function F(){}; F.prototype=o; return new F(); } //直接返回临时类型(子类型)的对象 var std1=object(person); std1.name="sssss"; std1.age=20; var std2=object(person);
测试代码:
std1.favor.push("black"); console.log(std1.favor); //["run", "sing", "write", "black"] console.log(std1.name); //sssss console.log(std1.age); //20
console.log(std2.favor); //["run", "sing", "write", "black"] console.log(std2.name); //undefined console.log(std1.age); //20
特点:
从本质上讲,object()对传入其中的对象进行了一次浅拷贝。
在没有必要创建构造函数,而只是想让一个对象继承另一个对象时,用该方法即可。
缺点:
不支持多继承
来自原型对象的引用属性是所有实例共享的 (eg. favor);
方法2核心思想:把父对象的属性,全部拷贝给子对象
function extendCopy(o) { var copy = {}; for (var i in o) { copy[i] = o[i]; } copy.name="sssss" return copy; } var std1=extendCopy(person); std1.age=20; var std2=extendCopy(person);
测试代码:
std1.favor.push("black"); console.log(std1.favor); //["run", "sing", "write", "black"] console.log(std1.name); //sssss console.log(std1.age); //20 console.log(std2.favor); //["run", "sing", "write", "black"] console.log(std2.name); //sssss console.log(std1.age); //20
特点:
支持多继承
缺点:
效率较低,内存占用高(因为要拷贝父类的属性)
无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝。
5、寄生组合继承
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
function Student(name,age,classid){ Person.call(this,name,age); //实现对实例属性和方法的继承 this.classid=classid; } (function(){ // 创建一个没有实例方法的类 var Super = function(){}; Super.prototype = Person.prototype; //将实例作为子类的原型 Student.prototype = new Super(); })(); Student.prototype.sayclass=function(){ console.log(this.classid); }
测试代码:
var std1=new Student("zhangsan",19,"#1001"); std1.favor.push("painting"); console.log(std1.favor); //["run", "sing", "write", "painting"] console.log(std1.name); //zhangsan console.log(std1.age); //19 console.log(std1.sleep()); //zhangsan正在睡觉! console.log(std1.eat("dumpling")); //zhangsan喜欢吃:dumpling console.log(std1.sayclass()); //#1001 console.log(std1 instanceof Person); //true console.log(std1 instanceof Student); //true var std2=new Student("lisi",20,"#1002"); console.log(std2.favor); // ["run", "sing", "write"] console.log(std2.name); //lisi console.log(std2.age); //20 console.log(std2.sleep()); //lisi正在睡觉! console.log(std2.eat("rice")); //lisi喜欢吃:rice
特点:
堪称完美
缺点:
实现较为复杂