转载自 阮一峰
原文链接:http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html
封装数据和方法,以及如何从原型对象生成实例
//一,生成实例对象的原始模式
var Cat={
name:"",
color:""
}
var cat1={};
cat1.name="大毛";
cat.color="黄色";
var cat2={};
cat1.name="二毛";
cat.color="黑色";`
//实例与原型之间,没有任何办法看出联系
//二,
function Cat(name,color){
return {
name:name,
color:color
}
}
//然后生成实例对象,就等于在调用函数
var cat1=Cat("大毛","黄色")
var cat2=Cat("二毛","黑色")
//cat1和cat2之间没有内在的联系。不能反映出它们是同一个原型对象的实例
//三,构造函数模式
//其实就是普通函数,但是内部使用了 this 变量。对构造函数使用 new 运算符,就能生成实例,并且this变量会绑定在实例对象上。
function Cat(name,color){
this.name=name;
this.color=color;
}
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
//这时,cat1和cat2会自动含有一个 constructor 属性,指向他们的构造函数
alert(cat1.constructor == Cat);//true
alert(cat2.constructor == Cat);//true
//js还提供了一个instanceof运算符,验证 原型对象 与 实例对象之间的关系
alert(cat1 instanceof Cat);//true
alert(cat2 instanceof Cat);//true
//四。构造函数模式的问题
//浪费内存 OK 现在我们为Cat对象添加一个不变的属性 type 种类,再添加一个 eat方法。那么 如下
function Cat(name,color){
this.name = name;
this.color = color;
this.type = "猫科动物";
this.eat = function(){
alert("吃猫粮");
}
}
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.type);//猫科动物
cat1.eat()//吃猫粮
//表面上好像没问题,但是其实有一个很大的弊端。每一个实例对象,type属性 和 eat()方法都是一摸一样的内容,每一次生成一个实例,都是重复的内容,占内存
alert(cat1.eat == cat2.eat);//false
//能不能只在内存中生成一次,然后所有的实例指向那个内存地址呢?答案是肯定的
//五,Prototype模式
//js规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
//which means,我们可以把那些 不变的 属性和方法,直接定义在 prototype 对象上
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "猫科动物";
Cat.prototype.eat = function(){alert("吃猫粮");}
//然后,生成实例
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.type);//猫科动物
cat1.eat();//吃猫粮
//这是所有实例的 type属性 和 eat()方法 ,其实都是同一个内存地址,指向 prototype 对象
alert(cat1.eat == cat2.eat);//true
//六,prototype模式的验证方法
//为了配合prototype属性,js定义了一些辅助方法,帮助我们使用它
//1. isPrototypeOf()
//这个方法用来判断,某个prototype对象 和 某个实例 之间的关系
alert(Cat.prototype.isPrototypeOf(cat1));//true
alert(Cat.protptype.isPrototypeOf(cat2));//true
//2.hasOwnProperty()
//每一个实例对象都有一个hasOwnproperty()方法,用来判断某一个属性到底是 本地属性,还是 继承自prototype对象 的属性
alert(cat1.hasOwnProperty("name"));//true
alert(cat1.hasOwnProperty("type"));//false
//3.in运算符
//可用来判断,某个 实例 是否含有某个属性,不管是不是 本地属性。
alert("name" in cat1);//true
alert("type" in cat1);//true
//还可用来遍历 某个对象 的所有 属性
for(var prop in cat1){alert("cat1[prop]="+cat[prop])}
//对象之间的”继承”的五种方法
function Animal(){
this.species = "动物";
}
//还有一个"猫"对象的构造函数
function Cat(name,color){
this.name = name;
this.color = color;
}
//怎么才能使"猫"继承"动物"呢?
//一,构造函数 绑定
//最简单的方法,使用call或apply方法,
//将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:
function Cat(name,color){
Animal.apply(this,arguments);//apply用法:add.apply(sub,arguments) == add(arguments)
this.name = name;
this.color = color;
}
vat cat1 = new Cat("大毛","黄色");
alert(cat1.species);//动物
//二。prototype模式
//更常见的是,使用prototype属性
//如果"猫"的prototype对象,指向一个Animal的实例,那么所有的"猫"的实例,就能继承Animal了。
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;//这句为什么这么写呢,下面有解释。
var cat1 = new Cat("大毛","黄色");
alert(cat1.species);
//首先
Cat.prototype = new Animal(); //Cat的prototype对象指向一个Animal的实例。相当于完全删除了prototype对象原先的值,然后赋予了一个新值。
alert(Cat.prototype.constructor);//function Animal()... //obj.constructor构造器 指向它的构造函数. 指向了Animal()
var cat1 = new Cat("大毛","黄色");//每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性
alert(cat1.constructor);//function Animal()...
//这显然会导致继承链的紊乱(cat1明明是用构造函数Cat生成的),因此我们必须手动纠正,将Cat.prototype对象的constructor值改为Cat.
//这是很重要的一点,编程时务必要遵守。
//即使替换了prototype对象:o.prototype={};
//那么下一步必然是为新的prototype对象加上constructor属性,并将属性指回原来的构造函数
//o.prototype.constructor = o;
//三。直接继承prototype
//预告:错误的
//第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。
//所以,我们也可以让Cat()跳过Animal(),直接继承Animal.prototype.
//Animal对象改写:
function Animal(){}
Animal.prototype.species = "动物";
//然后,将Cat的prototype对象,指向Animal的prototype对象,完成了继承。
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species)//动物
//与前一种方法相比,这样做的优点是效率毕竟比较高(不用执行和Animal的实例了),比较省内存。
//缺点是:Cat.prototype和Animal.prototype现在指向了同一个对象,那么,任何对Cat.prototype的修改,都会反映到Animal.prototype.
//所以,上面的代码是有问题的,看第二行
Cat.prototype.constructor = cat;
//这一句实际上吧Animal.prototype对象的constructor属性也改掉了
//四。利用空对象作为中介
//直接继承prototype存在上述缺点 so 第四种
var F=function(){}
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
//F是空对象,几乎不占内存。这是修改Cat.prototype对象,就不会影响到Animal.prototype对象
alert(Animal.prototype.constructor)//Animal
//将上面的方法,封装成一个函数,便于使用。
function extend(Child,Parent){
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;//为子元素设置一个uber属性,这个属性直接只想父对象的prototype属性
//uber德语"向上,上一层"。这等于在子对象上打开一条通道。可以直接调用父对象的方法。这一行只是为了实现继承的完备性,纯属备用性质。
}
//五。拷贝继承
//上面采用的prototype对象,实现继承。我们也可以换一种思路。纯粹采用"拷贝"方法实现继承。
//简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?
function Animal(){}
Animal.prototype.species="动物";
//然后再写一个函数,实现属性拷贝的目的
function extend2(Child,Parent){
var p = Parent.prototype;
var c = parent.prototype;
for (var i in p){
c[i] = p[i];
}
c.uber = p;
}
//这个函数的作用,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype
//使用时 extend2(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species)//动物
//非构造函数的继承
//一,什么叫"非构造函数"的继承
var Chinese = {nation:'中国'};
var Doctor = {career:'医生'};
//两个普通对象,如何让医生继承中国人,成为中国医生。
//注意:不是构造函数,无法使用构造函数方法实现继承
//二,object()方法
function object(o){
function F() {}
F.prototype = o;
return new F()
}
//这个object函数,只做了一件事,就是把子对象的prototype属性,指向父对象,从而使得子对象与父对象连在一起。
//使用的时候,第一步现在父对象的基础上,生成子对象
var Doctor = object(Chinese);
//然后再加上子对象本身的属性
Doctor.career = '医生';
alert(Doctor.nation)//中国
//三,浅拷贝
//除了使用"prototype链"以外,还有一种思路,把父对象的属性,全部拷贝给子对象,也能实现继承。
function extendCopy(p) {
var c={};
for (var i in p){
c[i] = p[i];
}
v.uber = p;
return c;
}
//使用:
var Doctor = extendCopy(Chinese);
Doctor.career = '医生';
alert(Doctor.nation)//中国
//但是这样的拷贝有一个问题。如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因为存在父对象被篡改的可能。
//现在给Chinese添加一个"出生地"属性,它的值是一个数组。
chinese.birthPlaces = ['北京','上海','香港'];
//通过extendCopy()函数,Doctor继承了Chinese
var Doctor = extendCopy(Chinese);
Doctor.birthPlaces.push("厦门");//我们为Doctor的 birthPlaces 添加一个城市
alert(Doctor.birthPlaces)
alert(Chinese.birthPlaces)//what Chinese的"出生地"也被改掉了
//所以,extendCopy()只是拷贝基本类型的数据,我们把这中拷贝叫做'浅拷贝'。
//四。深拷贝
//所谓'深拷贝',就是能够实现真正意义上的数组和对象的拷贝。她的实现并不难,只要递归调用"浅拷贝"就行了
function deepCopy(p,c){
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object'){
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i],c[i]);
}else{
c[i] = p[i];
}
}
return c;
}
//使用 :
var Doctor = deepCopy(Chinese);
//现在,给父对象加一个属性,值为数组。然后,在子对象上修改这个属性,这时,父对象就不会受到影响了。
</script>
</body>
1605

被折叠的 条评论
为什么被折叠?



