【JavaScript】Pseudo-classical模式

在pseudo-classical模式中,对象是通过构造函数创建的,并且它的方法是直接被放到prototype中的。

pseudo-classical模式也应用在一些框架中,例如Google Closure Library, Native JavaScript objects等。

Pseudo-class 声明

"pseudo-class"这个词其实不是很准确,因为与PHP, Java,C++等语言相比,javaScript事实上没有类,但是pseudo-class模式在某种程度上是与它们相近的。

这篇文章假设你已经对原型继承的机制很了解了,如果不了解请查看原型继承这篇文章。

一个pseudo-class由构造函数和方法组成。例如下面的 Animal pseudo-class,有一个sit方法和两个属性。

function Animal(name){

    this.name = name;
}

Animal.prototype = {

    canWalk: true,

    sit: function(){

        this.canWalk = false;
        alert(this.name + ' sits down.');

    }

}


var animal = new Animal('Pet');

alert(animal.canWalk);   //true

animal.sit();    //pet sits down

alert(animal.canWalk);    //false
1.   new Animal(name) 被调用时,生成新对象的__proto__指向Animal.prototype,请看下面图片的左边部分。

2. animal.sit方法改变了实例中的animal.canWalk,因此该animal对象不能走,而其他对象仍可以走。


总结pseudo-class:

(1)方法和默认的属性是在原型中的。

(2)在prototype中的方法使用的this, 指的是当前对象,因为this的值仅仅依赖调用的上下文,因此animal.sit()将this设为animal。


在以上的两点中隐藏着一些使用的危险,请看下面的例子。

你是仓鼠农场的老大,让你下面的程序员接受一个任务去创建Hamster构造函数和原型。

Hamster应该有一个food数组来存储和found方法来操作food数组,这两个方法都被添加到了原型中。

你的程序员给你带来了以下的解决方案。代码看起来还挺好的,当你创建两个Hamster时,本来饲料是属于其中一个的,但现在两个都有了。

那该怎么解决这种问题呢?

function Hamster(){};

Hamster.prototype = {

    food: [],

    found: function(something){

        this.food.push(something);

    }

}

//创建一个懒惰的hamster,和一个勤快的hamster,勤快的有饲料


speedy = new Hamster();
lazy = new Hamster();

speedy.found("apple");
speedy.found("orange");

alert(speedy.food.length);    //2
alert(lazy.food.length);    //2
解决方法:

让我们仔细分析下,speedy.found("apple")执行时发生了什么事?

(1)解释器查找在speedy中查找found,但是speedy是一个空对象,因此会失败。

(2)解释器到speedy.__proto__(==Hamster.prototype)查找,然后找到了found方法后执行。

(3)在先前的执行阶段,由于调用speedy.__proto__,this是被设置成speedy对象。

(4)this.food在speedy中没有被找到,但是可以在speedy.__proto__中被找到。

(5)”apple"被加到了speedy.__proto__.food中。

在__proto__中的food被改变时,却在两个hamster对象中共享了,必须要解决这个问题。

解决这个问题

为了解决这个问题,我们要确定每个hamster都有他们自己的食物,可以通过在构造函数中赋值做到。

function Hamster(){
 
    this.food = [],
};

Hamster.prototype = {

    found: function(something){

        this.food.push(something);

    }

}


speedy = new Hamster();
lazy = new Hamster();

speedy.found("apple");
speedy.found("orange");

alert(speedy.food.length);    //2
alert(lazy.food.length);    //0


继承

让我们创建一个新的从animal继承的一个Rabbit类。

function Rabbit(name){

    this.name = name;

}

Rabbit.prototype.jump = function(){

    this.canWalk = true;

    alert(this.name + ' jumps!');
}


var rabbit = new Rabbit('John');

正如你所看到的,结构与Animal很相似。

为了从Animal中继承,我们需要Rabbit.prototype.__proto__ == Animal.prototype. 这是一个自然的想法,因为如果一个方法不能在Rabbit.prototype中找到,我们可以在Animal.prototype中去寻找。具体看下图说明。


了实现这条链,我们需要创建一个从Animal.prototype继承来的Rabbit.prototype空对象,然后再向其中添加方法。

function Rabbit(name){

    this.name = name;

}
Rabbit.prototype = inherit(Animal.prototype);

Rabbit.prototype.jump = function(){...}

上面的代码中,inherit是一个通过给定的__proto__属性来创建空对象的方法。

function inherit(proto){

    function F(){};

    F.prototype = proto;

    return new F;

}

最后两个对象完整的代码如下所示。

function Animal(name){

    this.name = name;
}

Animal.prototype = {

    canWalk: true,

    sit: function(){

        this.canWalk = false;
        alert(this.name + ' sits down.');

    }

}



function Rabbit(name){

    this.name = name;

}

//继承
Rabbit.prototype = inherit(Animal.prototype);

//Rabbit方法
Rabbit.prototype.jump = function(){
    
    this.canWalk = true;

    alert(this.name + ' jumps!');

}

var rabbit = new Rabbit('Sniffer');

rabbit.sit();    //Sniffer sits
rabbit.jump()    //sniffer jumps!




function inherit(proto){

    function F(){};

    F.prototype = proto;

    return new F;

}

不要通过new Animal()来继承。这种方法应用得非常多,但是通过Rabbit.prototype = new Animal()是一种错误的继承方式。
因为new Animal()这种方式并没有传递参数name,构造函数也许严格限制需要传递参数才行,所以这种情况下使用这种继承方式是不行的。

事实上,我们只想从Animal中继承,而不是去创建一个Animal实例吧?这才符合继承的实质。所以Rabbit.prototype = inherit(Animal.prototype)是更好点。

调用父类的构造函数

”superclass"构造函数不会被自动调用,我们可以通过apply来实现。

function Rabbit(name){

    Animal.apply(this,arguments);
}
在当前对象的上下文执行Animal构造函数,从而达到改变name值的目的。


重写方法(多态)

为了重写父类方法,在子类的原型中代替它。

Rabbit.prototype.sit = function(){

    alert(this.name + ' sits in a rabbity way.');
}

调用rabbit.sit()时,搜索链为:rabbit -> Rabbit.prototype -> Animal.prototype,如果在Rabbit.prototype中找到,就会停止去Animal.prototype寻找。

当然我们也可以直接在对象中重写方法。


rabbit.sit = function(){
    
    alert('a special sit of this very rabbit '+ this.name);

}

重写后再调用父类的方法

当一个方法被重写时,我们还想调用之前老的方法,如果可能我们可以直接请求父类的prototype来实现。

Rabbit.prototype.sit = function() {
    alert('calling superclass sit:')
    Animal.prototype.sit.apply(this, arguments)
}

有父类方法通过apply/call方法传递当前对象this来调用,Animal.prototype.sit()调用将 Animal.prototype作为this。

去除对父类的直接引用

在上面的例子当中,我们可以通过Animal.apply...或者Animal.prototype.sit.apply....直接调用父类。

正常情况下,我们不应该那样做。以后重构代码也许会改变父类的名字,或者在层级中引入新的类。

一般来说,程序语言允许调用父类方法通过使用特别的关键字,如parent.method()或者 super()

javaScript没有这样的特性,但我们可以模拟它。

下面的extend方法可以实现继承,却不需要直接指向父类的引用。

function extend(child, parent) {

    child.prototype = inherit(parent.prototype);

    child.prototype.constructor = child;

    child.parent = parent.prototype;

}

用法:

function Rabbit(name){

    Rabbit.parent.constructor.apply(this.arguments);    //调用父类构造器
}

extend(Rabbit,Animal);

Rabbit.prototype.run = function(){

    Rabbit.parent.run.apply(this,arguments); //调用父类方法

}

事实上现在我们可以重命名Animal或者创建一个中间类GrassEatingAnimal,但是实际情况的改变只会涉及到Animal和extend(...)。

private或protected方法(封装)

可保护的方法和属性通过命名的习惯来支持。因此,一个以下划线开始的方法不应该被外面直接调用。



private方法经常不支持。






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值