js继承详解

来源:互联网 发布:手机看最新电影软件 编辑:程序博客网 时间:2024/05/29 08:49

this

this表示当前对象,如果在全局作用范围内使用this,则指代当前页面对象window; 如果在函数中使用this,则this指代什么是根据运行时此函数在什么对象上被调用。 我们还可以使用apply和call两个全局方法来改变函数中this的具体指向。

prototype原型对象

所有函数都有一个prototype属性,当一个函数被定义的时候,prototype属性会被自动创建和初始化。prototype属性的初始值是一个对象,而这个对象只带一个属性,而这个属性名为constructor,即构造函数。在prototype对象中,还可以为其添加其他的属性。给这个原型对象添加的任何属性,都会成为被构造函数所初始化的对象的属性。

看一个很有趣的现象

我们知道每个函数都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数。

function Person(name) {    this.name = name;};Person.prototype.getName = function() {    return this.name;};var p = new Person("jay");console.log(p.constructor === Person);  // trueconsole.log(Person.prototype.constructor === Person); // true

当时当我们重新定义函数的prototype时(注意:和上例的区别,这里不是修改而是覆盖), constructor的行为就有点奇怪了,如下示例:

function Person(name) {   this.name = name;};Person.prototype = {    getName: function() {    return this.name;}};var p = new Person("jay");console.log(p.constructor === Person);  // falseconsole.log(Person.prototype.constructor === Person); // falseconsole.log(p.constructor.prototype.constructor === Person); // false

为什么呢?
原来是因为覆盖Person.prototype时,等价于进行如下代码操作:

    Person.prototype = new Object({        getName: function() {            return this.name;        }    });

而constructor始终指向创建自身的构造函数,所以此时Person.prototype.constructor === Object,即是:

function Person(name) {    this.name = name;};Person.prototype = {getName: function() {    return this.name;}};var p = new Person("ZhangSan");console.log(p.constructor === Object);  // trueconsole.log(Person.prototype.constructor === Object); // trueconsole.log(p.constructor.prototype.constructor === Object); // true

怎么修正这种问题呢?方法也很简单,重新覆盖Person.prototype.constructor即可:

function Person(name) {    this.name = name;};Person.prototype = {    getName: function() {    return this.name;}};Person.prototype.constructor = Person;var p = new Person("ZhangSan");console.log(p.constructor === Person);  // trueconsole.log(Person.prototype.constructor === Person); // trueconsole.log(p.constructor.prototype.constructor === Person); // true

首先我们先利用组合模式创建一个 Person对象。

// 构造函数function Person(name) {    this.name = name;    this.hobby = ["sing","dance"];}// 定义Person的原型,原型中的属性可以被自定义对象引用Person.prototype = {    contructor: Person,    getName: function() {        return this.name;    },    getHobby: function() {        return this.hobby;    }};

原型链

下面的例子创建了一个学生类Student,它从Person继承了原型prototype中的所有属性。

function Student(name, school) {    this.name = name;    this.school = school;}Student.prototype = new Person();Student.prototype.getSchool = function() {    return this.school;};var student1 = new Student("Taylor","Berklee College of Music");console.log("姓名:"+student1.getName()+"学校:"+student1.getSchool());student1.hobby.pop();console.log(student1.getHobby());var student2 = new Student("Jane","Harvard University");console.log(student2.getHobby());//实例修改了对象里的变量

以上原型链继承很粗糙,存在的问题:

1.引用值类型的问题。父亲Person的实例属性变成了儿子Student的原型属性。所以所有的儿子对象的实例都共享这个hobby属性,即使指针的关系。所以通过儿子Student实例修改父亲Person的引用型变量,会直接因想到父亲对象Person的变量值。

2.Student的构造函数没法调用父类Person的构造函数。

构造函数继承方式

function Person(name) {    this.name = name;    this.hobby = ["sing","dance"];}function Student(name){    Person.call(this,name);}var student1 = new Student("Taylor");console.log(student1.hobby);//["sing", "dance"]student1.hobby.pop();console.log(student1.hobby);//["sing"]var student2 = new Student("Jane");console.log(student2.hobby);//["sing", "dance"]

原理:在儿子Student实例的环境下调用了父亲Person构造函数,这样每个儿子实例都会有一个自己的hobby副本。利用构造函数继承的方法,可以在儿子构造函数中像父亲构造函数传递参数。

缺点:对象的方法都只能在构造函数中定义,因此不能进行函数的复用。而且父亲对象的原型对象中定义的方法,对儿子来说是不可见的。所以该继承方法很少单独使用。

组合继承

思路:采用原型链实现对原型属性和方法的继承,借用构造函数来实现对实例属性的继承。即:通过在原型上定义方法实现函数的复用,并且保证每个实例都有自己的属性

// 构造函数function Person(name) {    this.name = name;    this.hobby = ["sing","dance"];}// 定义Person的原型,原型中的属性可以被自定义对象引用Person.prototype = {    contructor: Person,    getName: function() {        return this.name;    },    getHobby: function() {        return this.hobby;    }};function Student(name,number){    Person.call(this,name);//第二次调用父对象的构造函数    this.number = number;}Student.prototype = new Person();Student.prototype.constructor = Student;Student.prototype.geNumber = function(){    return this.number;};var student1 = new Student("Taylor","1");//第一次调用父对象的构造函数student1.hobby.pop();console.log(student1.getHobby());//["sing"]console.log(student1.getName());console.log(student1.geNumber());var student2 = new Student("Swift","2");student2.hobby.push("swim");console.log(student2.getHobby());//["sing", "dance", "swim"]console.log(student2.getName());console.log(student2.geNumber());

组合继承优点:避免了原型链和构造函数继承的缺陷,融合了各自的优点,是JS中最常用的继承模式。

缺点

无论什么情况下,都会调用两次父对象的构造函数。第一次是在创建儿子对象实例的时候,第二次是在儿子构造函数的内部。

第一次:是在创建儿子对象实例的时候,此时儿子对象原型Student.prototype会得到两个属性name和hobby,他们都是父亲Person的实例属性。

第二次:是在儿子Student的构造函数内部。这次又在新对象上创建了name属性和hobby属性,并覆盖第一次的两个同名属性。

所以下面,我们采用寄生式组合继承的方式,解决上面出现的问题。

寄生式组合继承

本质:借用构造函数来继承属性,通过原型链的混成形式来继承方法。

基本思路:其实上我们想要的就是父亲Person的原型对象的一个副本而已。所以,本质上,我们采用寄生式继承来继承父亲Person的原型对象,然后再把结果赋予子对象的原型。

基本模式

function inheritPrototype(Parent,child){    var prototype = Object(Parent.prototype);//创建父亲原型对象副本    prototype.constructor = child;//为副本增加constructor属性    child.prototype = prototype;//赋予子类型原型}

下面采用寄生式组合继承:

// 定义Person的原型,原型中的属性可以被自定义对象引用Person.prototype = {    contructor:Person,    getName: function() {        return this.name;    },    getHobby: function() {        return this.hobby;    }};function Student(name,number){    Person.call(this,name);    this.number = number;}inheritPrototype(Person,Student);Student.prototype.geNumber = function(){    return this.number;};var student1 = new Student("Taylor","1");student1.hobby.pop();console.log(student1.getHobby());//["sing"]console.log(student1.getName());console.log(student1.geNumber());var student2 = new Student("Swift","2");student2.hobby.push("swim");console.log(student2.getHobby());//["sing", "dance", "swim"]console.log(student2.getName());console.log(student2.geNumber());

寄生式组合继承是基于类型继承最有效的方式。

0 0