js中继承是一个相对复杂的问题,这里我自己做了一些总结,便于日后复习。
什么是继承?
js中的继承可以从对象的角度去说,简单来讲有一个Child对象通过继承Parent对象,能够直接拥有Parent对象的所有属性和方法,但是不会修改Parent对象所掌管的变量。
通过复用可以实现共享。
继承的几种方法:
1.原型链继承
null处于原型链的最顶端
原型链继承的思路:主要利用原型让一个引用类型继承另一个引用类型的属性和方法。
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针(constructor),而实例对象都包含一个指向原型对象的内部指针(proto)。如果让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针(proto),另一个原型也包含着一个指向另一个构造函数的指针(constructor)。如果另一个原型又是另一个类型的实例……这就构成了实例与原型的链条。
image.png1.原型对象是普通对象,有constructor属性;(Function.prototype是特殊的函数对象)。
2.通过new Function()创建的是函数对象。
function parent(){};
console.log(typeof parent.prototype) //Object
console.log(typeof Object.prototype) // Object
console.log(typeof Function.prototype) // 特殊 Function
console.log(typeof Function.prototype.prototype) //undefined
举个
function Parent(){
this.name="li";
this.arr = [1,2,3,4];
}
Parent.prototype.say = function(){
console.log("say hi");
}
function Child(){
this.age = "18";
}
Child.prototype = new Parent();
Child.say = function(){
console.log("say hi1");
}
var dongdong = new Child();
var dd= new Child();
dongdong.say();//say hi
console.log(dongdong.arr);//[1,2,3,4];
dd.arr.push(5);
console.log(dongdong.arr);//[1,2,3,4,5]
console.log(dd.arr);//[1,2,3,4,5]
image.png
(参考出处:https://blog.csdn.net/sinat_21274091/article/details/52741788 )
缺点:
1.不能给父构造函数传递参数;
2.父子构造函数的原型对象之间有共享问题。
2.借用构造函数继承
借用call/apply方法
如下:
function Parent(name,hobby){
this.name = name;
this.hobby = hobby;
}
Parent.prototype.say = function(){
console.log("say hi");
}
function Child(name,hobby,age){
//通过call/apply()方法改变Child的this指向,使子类的函数体内执行父级的构造函数从而实现继承
Parent.apply(this,[name,hobby]);
this.age = age;
}
var dd = new Child('li','eating',18);
console.log(dd.name);//li
console.log(dd.hobby);//eating
console.log(dd.age);//18
console.log(dd.say());//Uncaught TypeError: dd.say is not a function
由上面的例子可以看出来,构造函数继承,继承不了父函数原型上的属性,每次创建实例都会创建一遍方法。
3.组合继承(原型链+借用构造函数继承)
初级版:
function Parent(name,age){
this.name = name;
this.age = age;
this.arr = [1,2,3,4];
}
Parent.prototype.sayName = function(){
console.log(this.name);
}
function Child(name,age,hobby){
Parent.call(this,name,age);//借用构造函数
this.hobby = hobby;
}
Child.prototype = new Parent();//借用原型链继承
//如果没有上面这句,Child.prototype.constructor指向的是函数本身Child,而写了上面这句 指向变成了Parent,所以需要写上下面这句手动把指向修改回来。这样就形成了继承,而且实现了父类和子类原型对象的隔离
Child.prototype.constructor = Child;
var dd = new Child("li","18","eating");
var dd1 = new Parent("zhang","17","basketball");
dd.arr.push(5);
console.log(dd.arr);//[1,2,3,4,5]
console.log(dd1.arr);//[1,2,3,4]
dd.sayName();//li
dd1.sayName();//zhang
console.log(dd.constructor);//Child
console.log(dd1.constructor);//Parent
由上面例子可以看出,综合构造函数继承和原型链继承,成功的将两个优点结合在了一起,避免了原型链继承和构造函数继承的缺点。
不过也有他的缺点,就是调用两次超类型构造函数,一次是在创建子类型原型时,另一次是在子类型的构造函数内部,造成了不必要的性能浪费。
废话不多说,直接上终极版
借助原型可以基于已有的对象来创建对象,var B = Object.create(A)以A对象为原型,生成了B对象。B继承了A的所有属性和方法。
function Parent(name,age){
this.name = name;
this.age = age;
this.arr = [1,2,3,4];
}
Parent.prototype.sayName = function(){
console.log(this.name);
}
function Child(name,age,hobby){
Parent.call(this,name,age);//借用构造函数
this.hobby = hobby;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
var dd = new Child("li","18","eating");
var dd1 = new Parent("zhang","17","basketball");
dd.arr.push(5);
console.log(dd.arr);//[1,2,3,4,5]
console.log(dd1.arr);//[1,2,3,4]
dd.sayName();//li
dd1.sayName();//zhang
console.log(dd.constructor);//Child
console.log(dd1.constructor);//Parent
4.原型式继承
function create(o){
function F(){};
F.prototype=o;
return new F();
}
var Person = {
name :'li',
age:[1,2,3,4]
}
var p1 = create(Person);
var p2 = create(Person);
console.log(p1.name);//li
console.log(p2.name);//li
p1.age.push(5);
console.log(p1.age);//[1,2,3,4,5]
console.log(p2.age);//[1,2,3,4,5]
缺点:共享所有属性
5.寄生式继承
function create(o){
var f = Object.create(o);
f.sayhi =function(){
console.log("hi");
}
return f;
}
var person = {
name :'li',
age:[1,2,3,4]
}
var a = create(person);
a.sayhi();//hi
function Parent(name){
this.name=name;
this.say1=function(){
console.log("h1");
}
}
Parent.prototype.say = function(){
console.log(this.name);
}
function Child(name){
Parent.call(this,name);
}
function inherit(parentObj,childObj){
var f = Object.create(parentObj.prototype);//复制父类parentObj的原型对象
f.constructor = childObj;//constructor指向子类构造函数
childObj.prototype = f;//再把这个对象给子类的原型对象
}
inherit(Parent,Child);
var p1 = new Child("li");
var p2 = new Parent("ZHANG");
p1.say();//li
p2.say();//zhang
p2.say1 =function(){
console.log("1");
}
p1.say1();//h1
p2.say1();//1
console.log(p1.constructor);//Child
console.log(p2.constructor);//Parent
6.es6 class继承
class 是es6里的,可以看成一个语法糖,可以更面向对象一些,让对象原型更清晰一些。
class parent{
constructor(name){//constructor是构造方法
this.name = name;
this.arr = [12,2,3,4];
}
say(){//原型上的方法
console.log(this.name);
}
static say2(){//静态方法
console.log("我是静态方法,不会被继承")
}
}
Object.assign(parent.prototype,{//给原型添加方法
say1(){console.log(2)}
})
var p1 = new parent("li");
p1.say();//li
p1.say1();//2
p1.arr.push(5);
parent.say2(); //我是静态方法,不会被继承
p1.say2();//Uncaught TypeError: p1.say2 is not a function
console.log(p1.arr);//[12,2,3,4]
console.log(p2.arr);//[12,2,3,4,5]
console.log(parent.name)//parent
父类的静态方法可以被子类继承,class xx extends xx
class person{
static fn(){
console.log("我可以被子类继承");
}
}
class xiaohong extends person{}
xiaohong.fn();//我可以被子类继承
class方法只有静态方法没有静态属性
class Foo{
}
Foo.prop=1;
console.log(Foo.prop);//1
Class的静态属性只要在实例属性写法前面加上 static 关键字就可以了。
class fn1{
static pop = 1;
}
console.log(Foo.prop);//1
下面说继承
看:
class Parent{
constructor(name){
this.name = name;
}
say(){
console.log(this.name);
}
static sayParent(){
return "我不被调用";
}
}
class Child extends Parent{
constructor(name,age){
super(name);
this.age = age;
}
sayHi(){
console.log("hi");
}
static sayChild(){
return super.sayParent()+"too";
}
}
var p1 = new Child("li","18");
console.log(p1.name);//li
console.log(p1.age);//18
p1.say();//li
p1.sayHi();hi
console.log(Parent.sayParent());//我不被调用
console.log(Child.sayChild());//我不被调用too
解释一下super
1.super(name);//相当于Parent.call(this,name);
2.super必须写在this前面,不然报错;
3.super()只能用在子类的constructor方法之中,用在其他地方会报错;
4.如果写在静态方法里,super对象可以调用父级的静态方法。
网友评论