在看文章之前,先写几句重要的话。
所有的函数都是Function的实例对象
Function是函数,也是Function的实例对象—(注意大小写)
原型对象prototype的本质是对象----({ } 或者 new Object( ))
1.原型链继承原型链是由实例对象和原型对象形成的链式结构(节点之间通过__proto__属性连接)
JavaScript中通过修改对象原型指向的对象来实现继承,即是将一个对象的原型指向要继承的对象实例,从而实现继承对象的属性及方法。
var obj = new Object();
// obj -> Object.prototype -> null
var arr = new Array();
// arr -> Array.prototype -> Object.prototype -> null
var foo = new Foo();
// foo -> Foo.prototype -> Object.prototype -> null
function Foo1(){
this.info1 = 123;
}
Foo2.prototype = new Foo1();
Foo2.prototype.constructor = Foo2;
function Foo2(){
this.info2 = 456;
}
Foo3.prototype = new Foo2();
Foo3.prototype.constructor = Foo3;
function Foo3(){
this.info3 = 789;
}
var f3 = new Foo3();
console.dir(f3);
f3 -> new Foo2() -> new Foo1() -> Foo1.prototype -> Object.prototype -> null
// 原型继承:修改子级构造函数的prototype指向父级构造函数的实例对象
// 原型继承的缺点:
1、不能给父级构造函数传递参数;
2、父级构造函数中的引用类型的数据会被子级构造函数实例共享
function Animal(name){
this.name = name;
this.favour = ['sleeping','eating','swimming'];
}
Animal.prototype.showName = function(){
console.log(this.name);
}
Cat.prototype = new Animal('Tom');
function Cat(color){
this.color = color;
}
var cat = new Cat('blue');
cat.favour.push('dancing');
var cat1 = new Cat('pink');
console.log(cat.color);
console.log(cat.name);
cat.showName();
console.log(cat1.favour);原型链继承的不足
但即使是这样,原型链继承依然有两点问题:
1.原型中的实例引用类型属性会在所有对象实例中共享.
2.无法想像Java 的继承一样向父类的构造函数中传递参数。
其他继承方式
由于原型链继承存在一些不足,为了解决这些不足,JavaScript 中还有其他的几种继承的方式。(重点来了:)
借用构造函数
因为原型链无法传递参数到父类的构造函数中,因此出现了这种叫做借用构造函数的技术。顾名思义,即是借用父类的构造函数在子类中进行调用。借用构造函数的继承方式解决了原型继承的两个缺点,但是又产生了一个新的问题:无法继承父级构造函数原型中的成员
function Animal(name){
this.name = name;
this.favour = ['sleeping','eating','swimming'];
}
Animal.prototype.showName = function(){
console.log(this.name);
}
function Cat(color,name){
// 借用构造函数的目的就是把父级所有的属性继承过来
Animal.call(this,name);
this.color = color;
}
var cat1 = new Cat('blue','tom');
cat1.favour.push('coding');
var cat2 = new Cat('pink','kitty');
// cat1.showName();
console.dir(cat1);
console.dir(cat2);
借用构造函数虽然解决了构造函数传参的问题,但是当父类拥有方法时每个子类的实例都会拥有独立的方法,这个问题与单独使用构造函数模式定义类型的时候相同。
组合继承(原型继承和借用构造函数的组合)
类比使用构造函数模式定义类型时的解决方法(组合构造函数模式与原型模式),继承时的解决方法也类似。即组合原型链继承和借用构造函数,属性由借用构造函数的方式继承,方法由原型链继承。
实际上也就是在原型链继承的代码中添加在子类的构造函数中调用父类构造函数。
寄生组合式继承
组合继承是常用的继承方式,但是同样的也是有不足之处:调用了两次父类的构造函数,一次在子类构造函数中调用父类构造函数,一次在实例父类对象赋值给子类的原型。简单理解为:父级构造函数中的属性会有冗余(浪费内存)
寄生组合式继承在指定子类的原型的时候不必调用父类的构造函数,而是直接使用 Object.create() 创建父类原型的副本。
function Animal(name){
this.name = name;
this.favour = ['sleeping','eating','swimming'];
}
Animal.prototype.showName = function(){
console.log(this.name);
}
function Cat(color,name){
Animal.call(this,name);
this.color = color;
}
Cat.prototype = new Animal();
var cat1 = new Cat('blue','tom');
var cat2 = new Cat('pink','kitty');
cat1.showName();
console.log(cat1.name);
console.log(cat1.color);
================ 补 充 ===== ============
Object.create() // ES5新特性
作用:创建对象
如果参数为null表示创建一个纯粹的空对象;
如果参数是一个实例对象,那么创建出来的对象的__proto__属性指向参数(本质上可以理解为继承了参数
var obj = new Object();
var obj = {};
console.dir(obj);
// Object.create(null) 创建的对象中什么都没有(没有__proto__)
var o = {
username : 'lisi',
age : 12
}
// var obj1 = Object.create(null);
var obj2 = Object.create(o);
console.dir(obj2.username);
-------------------------------------------
//属性复制
var obj1 = {
uname : 'lisi',
age : 12
}
var obj2 = {
uname : 'zhangsan',
gender : 'male'
}
for(var key in obj2){
obj1[key] = obj2[key];
}
console.dir(obj1);
ES6 中的继承:
使用 extends 关键字来继承。虽然能够使用 extends 实现继承,但实际上内部还是基于原型。(原生js中没有extend方法,extand属于jQuery中的方法)
var obj = {
info : 'hello',
extend : function(obj){
for(var key in obj){
this[key] = obj[key];
}
}
}
obj.extend({
info : 'hi',
message : 'nihao'
});
console.dir(obj);
===================
$.fn.extend = $.extend = function(obj){
for(var key in obj){
this[key] = obj[key];
}
}
$.extend({
hello : function(){
console.log('hello');
}
});
$.fn.extend({
hi : function(){
console.log('hi');
}
});
=====后续补充---更好的理解继承====
function Father(){ //被继承的函数叫做超类型(父类,基类)
this.name = "Jack";
}
function Son(){ //继承的函数叫做子类型(子类,派生类)
this.age = 10;
}
//通过原型链继承,赋值给子类型的原型属性
//new Father()会将father构造里的信息和原型里的信息都交给Son
Son.prototype = new Father();//Son继承了Father,通过原型,形成链条
var son = new Son();
alert(son.name);//弹出 Jack
再举个栗子:
function Person(name){
this.name = name; //设置对象属性
};
Person.prototype.company = "Microsoft"; //设置原型的属性
Person.prototype.SayHello = function(){ //原型的方法
alert("Hello,I'm "+ this.name+ " of " + this.company);
};
var BillGates = new Person("BillGates"); //创建person对象
BillGates.SayHello(); //继承了原型的内容,输出"Hello,I'm BillGates of Microsoft"
var Jobs = new Person("Jobs");
Jobs.company = "Apple";//设置自己的company属性,掩盖了原型的company属性
Jobs.SayHello = function(){
alert("Hi,"+this.name + " like " + this.company);
};
Jobs.SayHello(); //自己覆盖的属性和方法,输出"Hi,Jobs like Apple"
BillGates.SayHello();//Jobs的覆盖没有影响原型,BillGates还是照样输出