JavaScript是一门面向对象的语言。在JavaScript中有一句很经典的话,万物皆对象。既然是面向对象的,那就有面向对象的三大特征:封装、继承、多态。这里讲的是JavaScript的继承,其余两个容后再讲。javascript
JavaScript的继承和C++的继承不大同样,C++的继承是基于类的,而JavaScript的继承是基于原型的。java
如今问题来了。浏览器
function Animal(name) { this.name = name; } Animal.prototype.setName = function(name) { this.name = name; } var animal = new Animal("wangwang");
function Animal(name) { this.name = name; } var animal = new Animal("wangwang");
Animal.prototype.setName = function(name) { this.name = name; }这时animal也会有setName方法。
var animal = Animal("wangwang");
function Animal(name) { this.name = name; return this; }猜猜如今animal是什么?
function Animal(name) { if(!(this instanceof Animal)) { return new Animal(name); } this.name = name; }这样就万无一失了。
console.log(Animal.prototype.constructor === Animal); // true咱们能够换种思惟:prototype在函数初始时根本是无值的,实现上多是下面的逻辑
// 设定__proto__是函数内置的成员,get_prototyoe()是它的方法 var __proto__ = null; function get_prototype() { if(!__proto__) { __proto__ = new Object(); __proto__.constructor = this; } return __proto__; }这样的好处是避免了每声明一个函数都建立一个对象实例,节省了开销。
function Animal(name) { this.name = name; } function Dog(age) { this.age = age; } var dog = new Dog(2);
Dog.prototype = new Animal("wangwang");这时,dog就将有两个属性,name和age。而若是对dog使用instanceof操做符
console.log(dog instanceof Animal); // true console.log(dog instanceof Dog); // false这样就实现了继承,可是有个小问题
console.log(Dog.prototype.constructor === Animal); // true console.log(Dog.prototype.constructor === Dog); // false能够看到构造器指向的对象更改了,这样就不符合咱们的目的了,咱们没法判断咱们new出来的实例属于谁。所以,咱们能够加一句话:
Dog.prototype.constructor = Dog;再来看一下:
console.log(dog instanceof Animal); // false console.log(dog instanceof Dog); // truedone。这种方法是属于原型链的维护中的一环,下文将详细阐述。
<pre name="code" class="javascript">function Animal(name) { this.name = name; } Animal.prototype.setName = function(name) { this.name = name; } function Dog(age) { this.age = age; } Dog.prototype = Animal.prototype;这样就实现了prototype的拷贝。
function Animal(name) { this.name = name; } function Dog(age) { this.age = age; } var animal = new Animal("wangwang"); Dog.prototype = animal; var dog = new Dog(2);
咱们能够看到,子对象的prototype指向父对象的实例,构成了构造器原型链。子实例的内部proto对象也是指向父对象的实例,构成了内部原型链。当咱们须要寻找某个属性的时候,代码相似于安全
function getAttrFromObj(attr, obj) { if(typeof(obj) === "object") { var proto = obj; while(proto) { if(proto.hasOwnProperty(attr)) { return proto[attr]; } proto = proto.__proto__; } } return undefined; }
在这个例子中,咱们若是在dog中查找name属性,它将在dog中的成员列表中寻找,固然,会找不到,由于如今dog的成员列表只有age这一项。接着它会顺着原型链,即.proto指向的实例继续寻找,即animal中,找到了name属性,并将之返回。假如寻找的是一个不存在的属性,在animal中寻找不到时,它会继续顺着.proto寻找,找到了空的对象,找不到以后继续顺着.proto寻找,而空的对象的.proto指向null,寻找退出。
ide
(new obj()).prototype.constructor === obj;而后,当咱们重写了原型属性以后,子对象产生的实例的constructor不是指向自己!这样就和构造器的初衷背道而驰了。
Dog.prototype = new Animal("wangwang"); Dog.prototype.constructor = Dog;看起来没有什么问题了。但实际上,这又带来了一个新的问题,由于咱们会发现,咱们无法回溯原型链了,由于咱们无法寻找到父对象,而内部原型链的.proto属性是没法访问的。
function Dog(age) { this.constructor = arguments.callee; this.age = age; } Dog.prototype = new Animal("wangwang");这样,全部子对象的实例的constructor都正确的指向该对象,而原型的constructor则指向父对象。虽然这种方法的效率比较低,由于每次构造实例都要重写constructor属性,但毫无疑问这种方法能有效解决以前的矛盾。
function getAttrFromObj(attr, obj) { if(typeof(obj) === "object") { do { var proto = Object.getPrototypeOf(dog); if(proto[attr]) { return proto[attr]; } } while(proto); } return undefined; }固然,这种方法只能在支持ES5的浏览器中使用。为了向后兼容,咱们仍是须要考虑上一种方法的。更合适的方法是将这两种方法整合封装起来,这个相信读者们都很是擅长,这里就不献丑了。