理解JavaScript继承

对于JavaScript的继承和原型链,虽然以前本身看了书也听了session,但仍是一直以为云里雾里,不由感叹JavaScript真是一门神奇的语言。此次通过Sponsor的一对一辅导和本身回来后反复思考,总算以为把其中的精妙领悟一二了。session

 

1. JavaScript建立对象函数

在面向对象语言中,一般经过定义类而后再进行实例化来建立多个具备相同属性和方法的对象。可是在JavaScript中并无类的概念,不过ECMAScript中的构造函数能够用来建立特定类型的对象。所以,在JavaScript中能够建立自定义的构造函数,而且经过new操做符来建立对象。this

在JavaScript中并无“指定的”构造函数类型,构造函数实质上也是函数,它和通常函数的区别只在于调用方式不一样。只有当经过new操做符来调用的时候它才能够做为构造函数来建立对象实例,而且把构造函数的做用域赋给这个新对象(将this指向这个新对象)。若是没有使用new来调用构造函数,那就是普通的函数调用,这个时候this指向的是window对象,这样作会致使全部的属性和方法被添加到全局,所以必定要注意命名构造函数时首字母大写,而且永远使用new来调用它。spa

function Person(name, gender) {
    this.name = name;
    this.gender = gender;
   this.say = function() {     console.log("Hello");   } } var person1 = new Person("Mike", "male"); var person2 = new Person("Kate", "female");

这段代码就定义了一个构造函数Person, 而且给它添加了name和gender属性以及say方法。经过调用new操做符来建立了两个Person的实例person1和person2.能够经过代码来验证一下:prototype

person1 instanceof Person; //true;
person2 instanceof Person; //true;

而且person1和person2都分别具备了name,gender属性,而且都被附上了构造对象时传入的值。同时它们也都具备say方法。指针

不过经过比较能够看出来,虽然这时person1和person2都具备say方法,但它们其实并非同一个Function的实例,也就是说当使用new来建立构造函数的实例时,每一个方法都在实例上从新被建立了一遍:code

person1.say == person2.say; //false

这样的重复建立Function是没有必要的,甚至在实例变多的时候形成一种浪费。为此,咱们可使用构造函数的prototype属性来解决问题。prototype原型对象是用来寻访继承特征的地方,添加到prototype对象中的属性和方法都会被构造函数建立的实例继承,这时实例中的方法就都是指向原型对象中Function的引用了:对象

function Person(name, gender) {
    this.name = name;
    this.gender = gender;
}

Person.prototype.say = function() {
    console.log("Hello");
}

var person1 = new Person("Mike", "male");
var person2 = new Person("Kate", "female");

person1.say == person2.say //true

 

2. prototype, constructor, 和__proto__blog

构造函数,原型对象,实例的关系是:JavaScript中,每一个函数都有一个prototype属性,这是一个指针,指向了这个函数的原型对象。而这个原型对象有一个constructor属性,指向了该构造函数。每一个经过该构造函数建立的对象都包含一个指向原型对象的内部指针__proto__。继承

用代码表示它们的关系:

Person.prototype.constructor === Person;
person1.__proto__ === Person.prototype;
person2.__proto__ === Person.prototype;

 

3. 继承的实现

JavaScript中使用原型链做为实现继承的主要方法。因为对象实例拥有一个指向原型对象的指针,而当这个原型对象又等于另外一个类型的实例时,它也具备这个指向另外一类型原型对象的指针。所以经过指向原型对象的指针__proto__就能够层层递进的找到原型对象,这就是原型链。经过原型链来完成继承:

function Teacher(title) {
    this.title = title;
}
Teacher.prototype = new Person();

var teacher = new Teacher("professor");

这时,咱们经过将Teacher的prototype原型对象指向Person的实例来完成了Teacher对Person的继承。能够看到Teacher的实例teacher具备了Person的属性和方法。

可是若是只是将构造函数的prototype原型对象指向另外一对象实例,发生的事情其实能够概括为:

Teacher.prototype instanceof Person //true
Teacher.prototype.constructor == Person //true
Teacher.prototype.__proto__ === Person.prototype //true

问题出现了:这时Teacher的构造函数变成了Person。虽然咱们在使用建立的实例的属性和方法的时候constructor的类型并不会产生很大的影响,可是这依然是一个很不合理的地方。所以通常在使用原型链实现继承时,在将prototype指向另外一个构造函数的实例以后须要再将当前构造函数的constructor改回来:

Teacher.prototype = new Person();
Teacher.prototype.constructor = Teacher;

这样才是真正的实现了原型链继承而且不改变当前构造函数和原型对象的关系:

到这里,咱们就能够将这个继承过程封装成一个extend方法来专门完成继承的工做了:

var extend = function(Child, Parent) {
  Child.prototype = new Parent();
  Child.prototype.constructor = Child;
  return new Child();
};

如今这个方法接受两个参数:Child和Parent,而且在完成继承以后实例化一个Child对象并返回。咱们固然能够根据须要来继续丰富这个函数,包括实例化的时候须要传入的参数什么的。

相关文章
相关标签/搜索