JavaScript之继承和原型

一、类的继承与对象的原型继承:

        JavsScript不存在“类”的概念,都是“对象”。Java中如果想拥有某个类的属性和方法,需要使用extends关键字继承这个类。但是JavaScript不同于Java中的类的继承,如果想拥有像某个对象的属性和方法,需要使用prototype指定对象的原型对象。例如:

var tom = {
    name: 'tom',
    play: function () {
        console.log(this.name + ' like playing...');
    }
};

       现在要创建另一个对象Jim,但是Jim除了像tom一样,有名字、喜欢玩之外,他还爱学习,最笨的办法可以这样定义:

var jim = {
    name: 'jim',
    play: function () {
        console.log(this.name + ' like playing...');
    },
    study: function () {
        console.log(this.name + ' like studying...');
    }
};

       但如果使用原型可以这样定义:

var jim = {
    name: 'jim',
    study: function () {
        console.log(this.name + ' like studying...');
    }
};
jim.__proto__ = tom;

jim.name      //jim
jim.play();   //jim like playing...
jim.study();  //jim like studying...

       在编写JavaScript代码时,不要轻易用__proto__去改变对象的原型,按照标准__proto__是不对外公开的,但是chrome的引擎却将他暴露了出来成为了一个公有属性,我们可以对其进行访问和赋值。但IE浏览器是不能访问这个属性的,所以不推荐大家直接操作这个属性,以免造成浏览器兼容问题。通常我们可以使用Object.create(obj),传入原型对象即可创建一个以指定对象为原型的新对象,比如:

var jim = Object.create(tom);
jim.name = 'jim';
jim.study = function(){
                console.log(this.name + ' like studying...');
            };
jim.name      //jim
jim.play();   //jim like playing...
jim.study();  //jim like studying...

二、原型:

        JavaScript对每个创建的对象都会设置一个原型__proto__,指向它的原型对象。当我们访问某个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype对象,最后,如果还没有找到,就只能返回undefined。prototype是函数的一个属性,只有Function类型的对象才具有prototype属性。例如创建一个数组对象:

var arr = [1, 2, 3];

其原型链为:arr ——> Array.prototype ——> Object.prototype ——> null,Array.prototype定义了indexOf()、shift()等方法,因此在所有的Array对象上都可以直接调用这些方法。再比如创建一个函数结象:

function foo() {
    return 0;
}

函数也是一个对象,它的原型链是:foo ——> Function.prototype ——> Object.prototype ——> null,由于Function.prototype定义了apply()等方法,因此所有函数都可以调用apply()方法。很容易想到,如果原型链很长,那么访问一个对象的属性就会因为花很多时间查找而变得更慢,因此要注意不要把原型链搞得太长。       

三、构造函数:

        可以把构造函数理解为一个普通的函数,但是这个普通的函数可以在调用时使用new关键字调用,并返回新创建的对象(不需要显式写return this),this则会自动指向这个新创建的对象。为了区分普通函数和构造函数,按照约定,构造函数首字母应当大写,而普通函数首字母应当小写。另外:新创建的对象的constructor属性始终指向创建该对象的构造函数本身。例如:

function Student(name) {
    this.name = name;
    this.study = function () {
        console.log(this.name + ' is studying ...');
    }
}
var tom = new Student('tom');
tom.name         //tom
tom.study()      //tom is studying ...
tom.constructor  //Student(name) {
                 //    this.name = name;
                 //    this.study = function () {
                 //        console.log(this.name + ' is studying ...');
                 //    }
                 //}

其原型链为:tom ——> Student.prototype ——> Object.prototype ——> null,用关键字new创建的对象还获得了一个constructor属性,该属性指向这个构造函数本身。关系如下图所示:

tom.constructor === Student.prototype.constructor;     //true
Student.prototype.constructor === Student;             //true
Object.getPrototypeOf(tom) === Student.prototype;      //true
tom instanceof Student;                                //true

如果修改Student的prototype的某个属性,Student创建的对象的constructor依然为Student

//修改Student原型的某个属性,而不是全部覆盖其原型
Student.prototype.study = function() {
    console.log(this.name + ' is not studying ...');
};
var b = new Student("Bob");
b.constructor===Student  //true

但如果覆盖掉Student的prototype,Student创建的对象的constructor则变为Object

//覆盖Student的原型
Student.prototype = {
    study: function(){
        console.log(this.name + ' is not studying ...');
    }
};  
var b = new Student("Bob");
b.constructor===Student  //false
b.constructor===Object   //true

        这是因为覆盖Student的prototype时实际做的操作是:

Student.prototype = new Object({
    study: function(){
        console.log(this.name + ' is not studying ...');
    }
});  

        所以对象的constructor指向的是Object而非Student,可以用以下方法修正这个错误:

Student.prototype.constructor=Student;

四、原型链的维护:

        方式1:

        基于上面的示例,如果创建SeniorStudent对象,例如:

function SeniorStudent(name) {
    Student.call(this, name);
    this.seniorStudy = function () {
        console.log(this.name + ' is seniorStudying ...');
    }
}

要使原型链变为:new SeniorStudent() ——> SeniorStudent.prototype ——> Student.prototype ——> Object.prototype ——> null,因为现在的SeniorStudent.prototype和Student.prototype的原型链都是到Object.prototype的,所以需要重新维护原型链:需要借助一个中间对象来实现正确的原型链,这个中间对象的prototype要指向Student.prototype,SeniorStudent.prototype要指向这个中间对象的对象,SeniorStudent.prototype的constructor属性要指向SeniorStudent本身,关系如下图所示:

 代码实现如下:

//空函数F:
function F() {
}
//把F.prototype指向Student.prototype
F.prototype = Student.prototype;
//把SeniorStudent.prototype指向中间对象F的对象
SeniorStudent.prototype = new F();
//把SeniorStudent.prototype的constructor修复为SeniorStudent
SeniorStudent.prototype.constructor = SeniorStudent;

可以验证该原型链已修改为期望的顺序:

//创建lilei对象
var lilei = new SeniorStudent('LiLei');

lilei.name;            //LiLei
lilei.study();         //LiLei is studying ...
lilei.seniorStudy();   //LiLei is seniorStudying ...

//验证原型
lilei.__proto__ === SeniorStudent.prototype;     //true
lilei.__proto__.__proto__ === Student.prototype; //true

//验证继承关系
lilei instanceof SeniorStudent;  //true
lilei instanceof Student;        //true

如果把修改原型链这个动作用一个inherits()函数封装起来,还可以隐藏F的定义,并简化代码如下:

function inherits(Child, Parent) {
    var F = function () {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}

当然,如果不想用中间对象这么麻烦,只需要最核心的一行代码也可以实现,只是没有像原生的原型链那么完美。

SeniorStudent.prototype = new Student();

        方式2:

        使用新关键字class,从ES6开始正式被引入到JavaScript中。首先用关键字class创建Student类。

class Student {
    constructor(name) {
        this.name = name;
    }

    study() {
        console.log(this.name + ' is studying ...');
    }
}
var tom = new Student('tom');
tom.name      //tom
tom.study()   //tom is studying ...

如果要继承Student类创建一个新类,可以这样写:

class SeniorStudent extends Student {
    constructor(name) {
        super(name); //记得用super调用父类的构造方法!
    }

    seniorStudy () {
        console.log(this.name + ' is seniorStudying ...');
    }
}

ES6引入的class关键字和原有的JavaScript原型继承有什么区别呢?实际上它们没有任何区别,class的作用就是让JavaScript引擎去实现原来需要我们自己编写的原型链代码。简而言之,用class的好处就是极大地简化了原型链代码,继承的方式更接近Java类的继承方式!

 

参考:Douglas Crockford《JavaScript语言精粹》

           http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html

https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434499763408e24c210985d34edcabbca944b4239e20000

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值