JavaScript中的面向对象(二)

javascript基于原型的面向对象编程

上一篇面向对象编程(一)讲了很多关于面向对象的概念、原型、原型链的内容,都是为最后的面向对象实现做铺垫,
如果不明白原型链的实现机制,基于原型的对象继承将会很难理解。

封装

先使用构造函数声明一个类,在构造函数中给this添加本地属性,
并实例化一个对象,这种方式可以为对象声明一个公共的本地属性:

function Animal(name) {
    this.name = name;
    this.sleep = function() {
        console.log(this.name + ' sleep');
    };
}
var a1 = new Animal('不高兴');
a1.sleep();

类名为Animal,使用大写字母开头,是编程的一种命名约定。

使用prototype也可以实现:

function Animal(name) {
    this.name = name;
}
Animal.prototype.sleep = function() {
    console.log(this.name + ' sleep');
};
var a1 = new Animal('不高兴');
a1.sleep();

但是,两种声明公共属性/方法的方式是有区别的,使用hasOwnProperty()方法可以用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。

继承

function Animal() {
    console.log('Animal init');
}
Animal.prototype.sleep = function() {
    console.log('Animal sleep');
};
var a1 = new Animal(); //  Animal init
a1.sleep(); //  Animal sleep

上面这段代码声明了一个Animal类,我现在想声明一个Cat类并继承它。

function Cat() {
    console.log('Cat init')
}
Cat.prototype = Animal.prototype;
Cat.prototype.sleep = function() {
    console.log('Cat sleep')
}
var c2 = new Cat();
c2.sleep(); // Cat sleep
a1.sleep(); // Cat sleep
上面这么写很显然是有问题的,这么写会使Cat.prototype与Animal.prototype引用相同对象。
修改Cat.prototype的属性值会影响到Animal.prototype

修改上面的代码

//声明一个Animal类
function Animal() {
    console.log('Animal init');
}
Animal.prototype.sleep = function() {
    console.log('Animal sleep');
};
//声明一个Cat类
function Cat() {
    console.log('Cat init')
}
Cat.prototype = new Animal();
var c2 = new Cat(); //打印了Animal init   Cat init
c2.sleep(); // Cat sleep
似乎也有问题,在给Cat.prototype属性赋值时,会实例化Animal。
我们只是希望继承Animal.prototype,并不希望执行Animal的构造函数。

继续修改上面的代码

//声明一个Animal类
function Animal() {
    console.log('Animal init');
}
Animal.prototype.sleep = function() {
    console.log('Animal sleep');
};
var a1 = new Animal();
//声明一个Cat类
function Cat() {
    console.log('Cat init')
}
利用一个空对象作为中介,实现对Animal的原型继承
var Empty = function () {};
Empty.prototype = Animal.prototype;
Cat.prototype = new Empty();
Cat.prototype.constructor = Cat;
Cat.prototype.sleep = function() {
    console.log('Cat sleep');
};
var c3 = new Cat(); //  Cat init
c2.sleep(); // Cat sleep
a1.sleep(); // Animal sleep

终于达到了想要的效果,现在来来重新组织下Animal与Cat的原型链:
这里写图片描述
将继承的方法封装成一个公共函数:

var Empty = function() {};
function chain(object) {
    Empty.prototype = object;
    var result = new Empty();
    Empty.prototype = null;
    return result;
}

写一个小案例

var Empty = function() {};
function chain(object) {
    Empty.prototype = object;
    var result = new Empty();
    Empty.prototype = null;
    return result;
}

function Animal(name,color) {
    this.name = name;
    this.color = color;
}
Animal.prototype.sleep = function() {
    console.log(this.name + '在睡觉');
}
var a1 = new Animal('胖达','黑白');
a1.sleep();  // 胖达 在睡觉

function Cat() {
    //通过调用父类的构造函数实现初始化
    Animal.apply(this,arguments);
}
Cat.prototype = chain(Animal.prototype);
Cat.prototype.constructor = Cat;
Cat.prototype.run = function() {
    console.log(this.name + "在跑步")
};
Cat.prototype.kind = "猫科";
var c2 = new Cat('tom','黑色');
c2.run(); // tom 在跑步
c2.sleep(); // tom 在睡觉
console.log(c2.kind);  // 猫科
console.log(c2.name);  // 胖达

function PersinCat() {
    Cat.apply(this,arguments);
}
PersinCat.prototype = chain(Cat.prototype);
PersinCat.prototype.constructor = PersinCat;
PersinCat.prototype.name = 'persian cat'; // 在原型中声明name属性
var p3 = new PersinCat('肥仔','灰色');
console.log(p3.name); //肥仔
console.log(p3.__proto__.name);// persian cat,本地name属性赋值之后,并不会覆盖prototype中name属性的值

下面通过一个更详细的原型链图,来描述这个例子中本地属性与prototype属性之间的关系:
这里写图片描述
通过这个图,大家应该也看明白了,a1、c2、p3中的是本地属性,其他的都是prototype属性,从例子的运行结果可以知道,对本地属性赋值,并不会覆盖prototype属性。
在使用this访问对象的属性或方法时,是先从本地属性中查找,如果未到,那么它会向上遍历原型链,直到找到给定名称的属性为止,
当到达原型链的顶部(也就是Object.prototype)仍然没有找到指定的属性,就会返回undefined。
chain()函数也可以使用Object.create()函数替代,可以简单的理解成Object.create()完成的工作与chain()一样。
这样可以对上面例子的代码再优化,将类继承封装成一个独立函数:

var Empty = function() {};
function chain(object) {
    Empty.prototype = object;
    var result = new Empty();
    Empty.prototype = null;
    return result;
}

function extend(SubClass, SuperClass, overrides) {
    var subProto, name;
    SuperClass = SuperClass || Object;
    SubClass.prototype = chain(SuperClass.prototype);
    subProto = SubClass.prototype;
    subProto.constructor = SubClass;
    if (overrides) {
        for (name in overrides) {
            if (overrides.hasOwnProperty(name)) {
                subProto[name] = overrides[name];
            }
        }
    }
}

小案例代码重构

function Animal(name, color) {
    this.name = name;
    this.color = color;
}
extend(Animal, Object, {
    sleep: function() {
        console.log(this.name + ' 在睡觉');
    }
});
var a1 = new Animal('胖达','黑白');
a1.sleep();  // 胖达 在睡觉
function Cat() {
    Animal.apply(this, arguments);
}
extend(Cat, Animal, {
    kind: "猫科",
    run: function() {
       console.log(this.name + ' 在跑步');
    }
});
var c2 = new Cat('tom','黑色');
c2.run(); // tom 在跑步
c2.sleep(); // tom 在睡觉
console.log(c2.kind);  // 猫科
console.log(c2.name);  // 胖达

function PersianCat() {
    Cat.apply(this, arguments);
}
extend(PersianCat, Cat, {
    name: 'persian cat',
});
var p3 = new PersinCat('肥仔','灰色');
console.log(p3.name); //肥仔
console.log(p3.__proto__.name);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值