JS高级(二)--继承
一、JS中继承的概念:
- 通过【某种方式】让一个对象可以访问到另一个对象中的属性和方法,我们把这种方式称之为继承 `并不是所谓的xxx extends yyy`
二、为什么要使用继承?
- 有些对象会有方法(动作、行为),而这些方法都是函数,如果把这些方法和函数都放在构造函数中声明就会导致内存的浪费
1 function Person(){ 2 this.say=function(){ 3 console.log("你好") 4 } 5 } 6 var p1=new Person(); 7 var p2=new Person(); 8 console.log(p1.say === p2.say); //false
1 function Person(name,age){ 2 this.name=name; 3 this.age=age; 4 this.say=function(){} 5 } 6 var p1=new Person(); 7 var p2=new Person(); 8 9 //p1对象和p2对象的say方法是否是同一个方法:false 10 console.log(p1.say===p2.say); 11 12 //由于say方法可能功能相似,但是不是同一个方法(没有指向同一块内存,会造成内存浪费) 13 //解决方案:把say方法写在他们共同的(父对象)中 14 //其实他们共同的父对象,就可以通过:Person.prototype来获取 15 16 //-->只要把say方法写在Person.prototype中,那么say方法就是同一个方法 17 Person.prototype.run=function(){ 18 console.log('时速500KM'); 19 } 20 //此时p1和p2都可以访问到run方法 21 p1.run(); 22 p2.run(); 23 //验证p1.run和p2.run是否是同一个方法? 24 console.log(p1.run == p2.run); //指向同一个方法,这种方法避免了内存的浪费 25 26 console.log(p1.run == Person.prototype.run);//true 27 28 var p3=new Person(); 29 console.log(p3.run == p1.run); //true 30 console.log(p3.run === p1.run);//true 31 //结论:只要往某个构造函数的prototype对象中添加某个属性、方法,那么这样的属性、方法都可以被所有的构造函数的实例所共享 32 //==>这里的【构造函数的prototype对象】称之为原型对象 33 // Person.prototype是 p1 p2 p3 的原型对象 34 // Person.prototype是Person构造函数的【实例】的原型对象 35 36 //猜猜看? 37 // Person的原型对象是谁呢? 38 // -->首先要知道Person的构造函数:-->Function 39 // -->所以Person的原型对象是:Function.prototype 40 41 // p1的原型对象是谁呢? 42 // -->首先要知道p1是谁创建的? -->Person 43 // -->所以p1的原型对象时: Person.prototype
三、继承的第一种方式:原型链继承1
1 function Dog(){ 2 3 } 4 var d1=new Dog(); 5 //为了让d1有一个say的方法, 6 //错误:d1.say=function(){} 7 //正确: 8 Dog.prototype.say=function(){ 9 console.log('汪汪汪'); 10 }
- 缺点:添加1、2个方法无所谓,但是如果方法很多会导致过多的代码冗余
四、继承的第二种方式:原型链继承2
1 function Cat(name){ 2 this.name=name; 3 } 4 var tom=new Cat("汤姆"); 5 //目的:把say方法放在tom的原型对象中(Cat.prototype) 6 Cat.prototype.say=function(){} 7 8 //问题: 9 Cat.prototype.s1=function(){} 10 Cat.prototype.s2=function(){} 11 Cat.prototype.s3=function(){} 12 Cat.prototype.s4=function(){} 13 Cat.prototype.s5=function(){} 14 //通过上面的方式,给tom的原型对象添加了好多方法,也就是让tom拥有了好多方法,但是代码产生了不少的冗余(重复) 15 16 //-->为了减少这种重复,改良版: 17 Cat.prototype = { 18 a1:function(){}, 19 a2:function(){}, 20 a3:function(){}, 21 a4:function(){}, 22 a5:function(){} 23 } 24 console.log(tom.s1); //可以访问 25 console.log(tom.a1); //undefined 26 //原因:tom对象在创建的时候已经有了一个确定的原型对象,就是旧的Cat.prototype 27 //由于Cat.prototype后面被重新赋值,但是tom对象的原型对象却没有改变,所以tom对象并不能访问到新原型对象中的a1-a5方法 28 29 //如何解决这个问题? 30 //-->先改变原型、再创建对象
1 function Tiger(){ 2 3 } 4 Tiger.prototype={ 5 a:function(){ 6 7 }, 8 b:function(){ 9 10 } 11 } 12 //创建tiger实例,此时的tiger实例的原型对象是新原型,所以tiger可以访问到新原型中的属性和方法(a/b) 13 var tiger=new Tiger(); 14 console.log(tiger.a); 15 console.log(tiger.b);
- 注意点:
a、一般情况下,应该先改变原型对象,再创建对象
b、一般情况下,对于新原型,会添加一个constructor属性,从而不破坏原有的原型对象的结构
五、继承的第三种方式:拷贝继承(混入继承)
场景:有时候想使用某个对象中的属性,但是又不能直接修改它,于是就可以创建一个该对象的拷贝
- 实现1:
var source={name:"李白",age:15} var target={}; target.name=source.name target.age=source.age;
- 上面的方式很明显无法重用,实际代码编写过程中,很多时候都会使用拷贝继承的方式,所以为了重用,可以编写一个函数把他们封装起来:
function extend(target,source){ for(key in source){ target[key]=source[key]; } return target; } extend(target,source)
- 由于拷贝继承在实际开发中使用场景非常多,所以很多库都对此有了实现
– jquery:$.extend
- es6中有了对象扩展运算符仿佛就是专门为了拷贝继承而生:
var source={name:"李白",age:15} var target={ ...source }
六、继承的第四种方式:原型式继承
- 场景:
- 创建一个纯洁的对象
- 创建一个继承自某个父对象的子对象
- 使用方式:
空对象:Object.create(null)
var o1={ say:function(){} } var o2=Object.create(o1);
七、继承的第五种方式:借用构造函数实现继承
- 场景:适用于2种构造函数之间逻辑有相似的情况
function Animal(name){ this.name=name; } function Person(name,age){ this.name=name; this.age=age; }
- 以上代码用借用构造函数实现
1 function Animal(name,age){ 2 this.name=name; 3 this.age=age; 4 } 5 function Person(name,age,address){ 6 Animal.call(this,name); 7 //this.name=name; 8 //this.age=age; 9 this.address=address; 10 }