继承
原型链
构造函数被
new
操作符实例化一个对象之后,对象中会有一个秘密链接。(非IE内核浏览器这个链接叫__proto__
),通过这个链接我们可以从实例对象中查找属性和方法。而原型对象中本身也包含了指向其原型的链接,由此形成的一个链条,既原型链。
比如像下面这样查询一个属性的过程,
function A() { this.hairColor = "blue"; } function B() { this.height = "fat"; } function C() { this.name = "Child"; } B.prototype = new A(); C.prototype = new B(); var d = new C(); d.hairColor //blue
首先查询d这个实例对象中的所有属性,没有找到,继而去查找d.__proto__
指向的对象,即new
出来的B实例对象。重复这个过程在A中找到了,立即打印在浏览器中。
继承模式
实现继承的模式,可以分为俩类
- 基于构造函数的继承
- 基于对象的继承
一、 构造函数继承
1. 原型链继承
ECMAScript 默认的继承方法。
function Cat() { this.color = "blue"; } Cat.prototype.getColor = function() { console.log (this.color); };
function Dog() { this.height = "150cm"; } //继承 Dog.prototype = new Cat(); Dog.prototype.consctructor = Dog; Dog.prototype.getHeight = function() { console.log (this.height); };
var animal = new Dog();
animal.getColor();
animal.getHeight();
给原型添加方法的代码一定要放在替换原先的语句之后。
缺点:
- 当原型对象中有引用类型的值时,属性会被所有实例所共享。修改其中一个实例对象,会在所有实例中反应出来。
- 给父类型构造函数传递参数会影响所有实例。
不推荐单独使用原型链继承模式。
2. 借用构造函数(伪造对象或经典继承)
利用函数的apply()
或者call
方法,在子类型构造函数的内部调用超类型构造函数。
function Cat() { this.color = ["red", "blue", "green"]; } function Dog() { Cat.call(this); }
var animal1 = new Dog();
animal1.color.push("yellow");
console.log(animal1.color); // ["red", "blue", "green", "yellow"]
var animal2 = new Dog();
console.log(animal2.color); // ["red", "blue", "green"]
这种方式通过call()
或apply()
在实例化对象的时候,调用父类构造函数Cat()
来初始化实例的属性和方法,这样每一个实例都能拥有自己的引用类型的副本了。而不在是实例共享一个引用对象。
function Cat(name) { this.name = name; }
function Dog() { Cat.call(this, "张三"); this.age = "29"; }
var animal3 = new Dog();
animal3.name; //"张三"
animal3.age //"29"
优点: 可以再子类型构造函数中向父类构造函数传递参数。
缺点: 方法都在构造函数中定义,函数无法复用。
不推荐单独使用原型链继承模式。
3. 组合继承(伪经典继承)
使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。
定义父类构造函数 function Man(name) { this.name = name; this.friends = ["mark", "david", "july"]; }
在父类原型对象中定义方法 Man.prototype.sayName = function() { console.log(this.name); };
function Woman(name, age) { Man.call(this, name); //继承属性 this.age = age; //定义自有属性 }
Woman.prototype = new Man(); //继承再原型中定义的方法 Woman.prototype.constructor = Woman; //让consctructor指向正确 Woman.prototype.sayAge = function() { //定义自己的原型方法 console.log(this.age); };
这样不同的实例拥有自己的属性, 又可以使用共享的方法
var person = new Woman("marry", 24); person.friends.push("Joe"); console.log(person.friends); // ["mark", "david", "july", "Joe"] person.sayName(); // marry person.sayAge(); // 24
var person1 = new Woman("Nip", 30); console.log(person1.friends); // ["mark", "david", "july"] person1.sayName(); // Nip person1.sayAge(); // 30
4. 只继承于原型
由于原型中的所有代码都是共享的,这意味这Woman.prototype = Man.prototype
继承于原型,比 Woman.prototype = new Man();
继承于构造函数的实例,更加效率,因为中间省去了到new Man()
实例对象查找这一步。.也不用实例化父类对象了。
function Man() {} Man.prototype = { constructor: Man, friends: ["mark", "david", "july"], };
function Woman(name, age) { Man.call(this, name); this.age = age; }
Woman.prototype = Man.prototype
;
Woman.prototype.getAge= function() { console.log(this.age); };
缺点: 由于引用的是一个同一个原型,对子对象的原型经行修改,父对象也会跟着改变,所有有用继承关系的都是如此。
var person1 = new Woman(“bob", 25); console.log(person.friends) // ["mark", "david", "july"]
var person2 = new Woman(“marry", 28); console.log(person.friends) // ["mark", "david", "july"]
并且对象的conctructor属性的值也是一样的。
Man.prototype.constructor === WoMan.prototype.constructor //true
5. 临时构造器 newF()
这种模式是对第三种模式的一种改进,既然所有属性都指向了一个相同的对象,父对象就会受到子对象属性的影响。要避免这种缺点,可以用一个临时的构造器来替代。
var F = function() {};
F.prototype = Man.prototype;
Woman.prototype = new F();
Woman.prototype.constructor = Woman;
通过这种方法,我们可以再保持原型链的基础上使父对象的属性摆脱对子对象的影响了;
我们可以把之前的这个临时构造器封装起来,方便使用:
function extend(Child, Parent) { var F = function () {}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; Child.uber = Parent.prototype; }
可以像下面这样使用:
extend(Woman, Man);
var person = new Woman("mark", 25);
console.log(person.friends) // ["mark", "david", "july"]
这也是Yui库的实现方法
Child.uber = Parent.prototype;
这句话的意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。可以直接调用父对象的方法。
6. 属性拷贝
在构建可重用的继承代码之前,我们可以见简单的将父对象的属性拷贝给子对象。根据之前的extend
函数可以创建一个函数,该函数接受2个构造器函数。将父类的原型属性全部拷贝 给子类原型。
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for(var i in p) { c[i] = p[i]; }
c.uber = p;
}
这个方法与之前的相比,效率较低,这里执行的是子对象原型的逐一拷贝,而非简单的查询。这种方式仅适用于简单类型值的对象。引用类型的值不可复制,只支持复制指针。指向的还是原来的对象。
确定原型和实例关系的方法
instanceof()
,用来确定实例是不是某个原型对象的实例。可以像这样使用
console.log (animal instanceof(Dog)); //true console.log (animal instanceof(Cat)); //true
isPrototypeOf()
在原型链中出现过的原型,都会返回true
。
``
console.log (Dog.prototype.isPrototypeOf(animal)); //true
console.log (Cat.prototype.isPrototypeOf(animal)); //true
console.log (Object.prototype.isPrototypeOf(animal)); //true
``