javascript的构造函数,原型和以其实现的经典继承

用javascript实现一个对象,一般最常用的方法是建立一个原型,单单依靠构造函数建立是不行的,比如:

function Person(name,age,college){
        this.name = name;
        this.age = age;
        this.college = college;
        this.sayName = function () {
            alert(this.name);
        }
    }
    var person1 = new Person("xuZhiWei",22,"Nanjing University of Science and Technology");

    var person2 = new Person("zhaochao",23,"Nanjing University");
    person1.sayName();//输出结果是xuZhiWei;
    person2.sayName();//zhaochao;
    alert(person1.age);//22;
    alert(person2.age);//23;

这里用构造函数的方法创建了两个对象person1和person2,发现用构造函数它们各自都可以创建独立的属性,但是有一个小的缺点,每new一个Person,函数都要实例化一次(函数也是对象),比如判断person1.sayName==person2.sayName,结果是false,所以这证明了确实新建了一个实例。这样会增加内存的开销。

而另一种方法是利用prototype原型,创建一个构造函数,其就创建了一个原型对象,构造函数的prototype属性指向原型对象,同时原型对象的construction属性也指向了Person。示例如下:

function Person(){}
    Person.prototype.name="xuZhiWei";
    Person.prototype.age=22;
    Person.prototype.sayName=function(){
        return this.name;
    };
    alert(typeof Person.prototype.constructor =="function");//true

我们先声明了构造函数,然后给它的prototype赋值,最后判断construction的时候它的类型是function,这是因为它指向了构造函数,因为构造函数有prototype属性,所以实例应该也有prototype属性,但是不能直接访问,js提供了isPrototype方法来判断,如下例子可以证明所有实例都是指向这个原型:

function Person(){}
    Person.prototype.name="xuZhiWei";
    Person.prototype.age=22;
    Person.prototype.sayName=function(){
        return this.name;
    };
    var person1 = new Person();
    var person2 = new Person();
    alert(Person.prototype.isPrototypeOf(person1));//true;
    alert(Person.prototype.isPrototypeOf(person2));//true;
    alert(typeof Person.prototype.constructor =="function");//true

这说明了所有实例都是指向了构造函数的原型 ,那么它们的引用应该是一样的,包括函数,这时候执行person1.sayName==person2.sayname返回结果是true,说明引用相同。如果这时候想重写某些属性,只能给实例赋值,如下:

function Person(){}
    Person.prototype.name="xuZhiWei";
    Person.prototype.age=22;
    Person.prototype.sayName=function(){
        return this.name;
    };
    var person1 = new Person();
    var person2 = new Person();
    person1.name = "zhaochao";
    alert(person1.name);//zhaochao
    alert(person2.name);//xuZhiWei;

第一个显示的是zhaochao,第二个显示的是xuZhiWei,这说明给实例赋值,它会“屏蔽”原型的值,而不是修改原型的值,这说明了取值的查找顺序,先看看实例中有没有赋值,然后如果实例中没有找到再从原型中去找值。

使用原型函数等就可以共享了,但是对于引用类型的属性,它会存在一个问题,就是当一个引用类型的属性修改,所有的都会修改,比如数组,如下所示:

 function Person(){}
    Person.prototype.name="xuZhiWei";
    Person.prototype.age=22;
    Person.prototype.hobbies=["弹吉他","弹钢琴","看动漫"];
    Person.prototype.sayName=function(){
        return this.name;
    };
    var person1 = new Person();
    var person2 = new Person();
    person1.hobbies.push("看书");
    alert(person1.hobbies);//弹钢琴,弹吉他,看动漫,看书;
    alert(person2.hobbies);//弹钢琴,弹吉他,看动漫,看书;

发现这两个对象结果都返回了相同的值,这说明对于引用类型(类似还有日期,正则)每次赋值都是从原型上修改了结果。所以原型还是有一些局限。

联系构造函数,如果在构造函数中声明数组,自然不会出现这个问题。所以可以把这两个方法结合起来,如下所示:

function Person(name,age){
        this.name = name;
        this.age = age;
        this.hobbies=["弹吉他","弹钢琴","看动漫"];
    }
    Person.prototype.sayName=function(){
        return this.name;
    };
    var person1 = new Person();
    var person2 = new Person();
    person1.hobbies.push("看书");
    alert(person1.hobbies);//弹钢琴,弹吉他,看动漫,看书;
    alert(person2.hobbies);//弹钢琴,弹吉他,看动漫;

在这里可以看出,用构造函数创建引用每次实例化都会产生一个新数组,就不会产生之前的问题了,另外要提一点的是:prototype如果要用字面量定义,那么它的constructor就会指向Object而非构造函数了。所以最好不要用这个构造方法。

在用prototype实现原型链的继承的时候,可以这样做:

function Personchild(name){
    this.name = name;
}
Personchild.prototype = new Person();
这样Personchild的prototype对象也是指向Person的prototype的(Person的实例本来就指向Person.prototype),这样不仅能获得在构造函数里的属性,同时也能获得在Person的prototype的属性,这些都是Personchild的原型属性了。然后可以调用父类的属性方法了。如下所示:

function Person(name,age){
        this.name = name;
        this.age = age;
        this.hobbies=["弹吉他","弹钢琴","看动漫"];
    }
    Person.prototype.sayName=function(){
        return this.name;
    };
    function Personchild(name){
        this.name = name;
    }
    Personchild.prototype = new Person();//这样Personchild的prototype就指向了Person的prototype.
    var child = new Personchild("许志伟");
    alert(child.hobbies);//弹钢琴,弹吉他,看动漫;

可以看到子类也成功调用了父类的属性。但是这又存在一个问题,如果先声明一个Person对象,然后修改hobbies的值,因为数组是引用类型,所以这个结果也会影响到子类,如下所示:

function Person(){
        this.hobbies=["弹吉他","弹钢琴","看动漫"];
    }
    function Personchild(){}
    Personchild.prototype = new Person();//这样Personchild的prototype就指向了Person的prototype.
    var child1 = new Personchild();
    child1.hobbies.push("看书");
    var child2 = new Personchild();
    alert(child2.hobbies);//弹钢琴,弹吉他,看动漫,看书;

因为每一个实例的指向的原型,都有继承来的hobbies属性,这个和构造函数的hobbies不一样,它是实例化后的新数组,但是在子类的prototype中,这样每个实例都共享了这个数组,当一个值改变,所有值都会发生改变。

为了解决这个问题,我们可以使用借用构造函数的技术,这个方法很简单,实例代码如下:

function Person(){
        this.hobbies=["弹吉他","弹钢琴","看动漫"];
    }
    function Personchild(){
        Person.call(this);//调用父类的构造方法;
    }
    var child1 = new Personchild();
    child1.hobbies.push("看书");
    alert(child1.hobbies);//弹钢琴,弹吉他,看动漫,看书;
    var child2 = new Personchild();
    alert(child2.hobbies);//弹钢琴,弹吉他,看动漫;

我们通过调用call()方法或者apply()方法,可以实现和Java的super()一样类似的效果,这样每个子类的实例都会有自己的hobbies副本。

如果要传递参数,可以在this后面加上参数即可,比如call(this,“zhaochao”)。

但是构造函数如果创建方法,仍然存在之前谈到的问题,所以一般还是要把借用构造函数和原型链结合在一起用,构成组合继承。

最后组合的代码如下:

function Person(name){
        this.name = name;
        this.hobbies=["弹吉他","弹钢琴","看动漫"];
    }
    Person.prototype.sayName = function () {
        alert(this.name);
    };
    Person.prototype.sayHobbies = function () {
        alert(this.hobbies);
    };
    function Personchild(name,age){
        Person.call(this,name);//调用父类的构造方法;
        this.age = age;
    }
    Personchild.prototype = new Person();
    Personchild.prototype.sayAge = function () {
        alert(this.age);
    };
    var instance1 = new Personchild("许志伟",22);
    instance1.hobbies.push("看书");
    instance1.sayName();//许志伟;
    instance1.sayAge();//22;
    instance1.sayHobbies();//"弹吉他","弹钢琴","看动漫","看书";
    var instance2 = new Personchild("赵超",55);
    instance2.sayName();//赵超
    instance2.sayAge();//55
    instance2.sayHobbies();//"弹吉他","弹钢琴","看动漫";
在这个例子中,构造函数里的属性是属于各个实例的,prototype原型里的方法是公用的,这样就结合了构造函数和原型链的优点。写的有点多,希望以后看到能瞬间秒懂 难过

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值