js面向对象
Posted 王小窝
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js面向对象相关的知识,希望对你有一定的参考价值。
一、封装
原文链接:http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html
1.1 原始模型
var Cat = {//原型 name : "",
color :"" }
var cat1 = {};//实例
cat1.name = "小花";
cat1.color = "花";
var cat2 = {};//实例
cat2.name = "小黑";
cat2.name = "黑";
这就是最简单的封装了,把两个属性封装在一个对象中,但是这样的封装有两个缺点
1、如果写多个实例会很麻烦
2、实例也原型之间没有任何关系
1.2 原始模型的改进
写一个函数解决代码重复问题
function Cat(name,color){
return {
name : name,
color:color
}
}
var cat1 = Cat("小花",“花”);
var cat2 = Cat("小黑" , "黑");
存在的问题:cat1和cat2之间没有任何内在关系,不能看出他们是同一原型的实例
1.3 构造函数模式
为了解决从原型对象生成实例问题,js提供一个构造函数模式。
所谓的构造函数就是普通的函数,内部使用this对象,对构造函数内部使用new运算符就可以生成实例,并且this变量会绑定在实例对象上
var Cat = function(name,color){
this.name = name;
this.color = color;
}
var cat1 = new Cat("小花",“花”);
var cat2 = new Cat("小黑",“黑”);
js提供了一个instanceof 运算符,验证原型对象和实例对象之间的关系
alert( cat1 instanceof Cat);//ture
alert( cat2 instanceof Cat);//true
存在的问题:如果存在不变的属性或方法时,比如var Cat = function(name,color){ this.name = name;
this.color = color;
this.style = "猫科"
this.eat = function(){
alert("吃老鼠");
}
}
var cat1 = new Cat("小黑",“黑”);
var cat2 = new Cat("小花",“花”) ;
alert(cat1.style);//猫科
cat1.eat();//吃老鼠
alert(cat2.style);//猫科
cat2.eat();//吃老鼠
对于每一个实例对象,type和eat都是一样的内容,每一次生成一个实例,都必须为重复内容,多占用一些内存,既不环保也缺乏效率
解决办法:prototype模式
每一个构造函数都有一个prototype属性,指向prototype对象,这个对象的所有属性和方法都被构造函数的实例继承
这意味着,我们可以把那些不变的属性和方法直接定义在prototype上
var Cat = function(name,color){
this.name = name;
this.color = colr;
}
Cat.prototype = {
constructor : Cat,
style : "猫科",
eat : function(){
alert("吃老鼠");
}
}
var cat1 = new Cat("小黑",“黑”);
cat1.eat();
alert(cat1.style);
var cat2 = new Cat("小花",“花”);
alert(cat2.style);
cat2.eat();
prototype验证方法
isPrototypeOf方法判断,某个prototype对象和实例之间的关系
alert( Cat.prototype isPrototypeOf (cat1) ) ;//true
alert(Cat.prototype isPrototypeOf( cat2 ) );//true
instanceof 判断实例和父类之间的关系
alert( cat1 instanceof Cat);//true
alert(cat2 instanceof Cat);//true
每一个实例对象都有hasOwnProperty()方法,判断属性是本地属性还是继承prototype的属性
alert( cat1 hasOwnProperty(name) );//true 为本地属性
alert( cat1 hasOwnProperty(eat) );//false 为继承属性
in 运算符用于判断实例是否有某个属性,无论是本地属性还是继承属性
alert( "name" in cat1 );//true
alert("age" in cat1);//false
in还可以变量对象中的所有属性
for( var pro in cat1){
alert("cat1的”+pro +"属性值为:"+cat1[pro]);
}
二、继承
方法一、构造函数的继承
function Animal (){
this.species = "动物";
}
function Cat(name,color){
this.name = name;
this.color = color;
}
怎样才能是猫继承动物呢?
方法一:构造函数绑定
function Animal(){
this.species = "动物";
}
function Cat(name,color){
Animal.apply(this,arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat("小黑",“黑”);
alert( cat1.species );//“动物”
方法二:原型模式
如果Cat的prototype成为Animal的实例,cat就可以继承Animal了
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat();
alert( cat1.species );//"动物"
任何一个构造函数的原型对象都有一个constructor属性,此属性都指向这个构造函数。
当没有Cat.prototype = new Animal();的时候,Cat.prototype.constructor指向的是Cat,但是继承了Animal后,Cat.prototype.constructor = Animal;
所以要进行更正,Cat.prototype.constructor = Cat;
更重要的是实例也有constructor属性,指向的是构造函数原型对象的constructor属性
alert( cat1.constructor == Cat.prototype.constructor);//true
因此当Cat.prototype = new Animal();后 cat1.constructor = Animal,cat1明明是使用构造函数Cat生成的,但此时确是Animal,导致了原型链的混乱,所以要手动更正constructor,
Cat.prototype.constructor = Cat
方法三:直接继承prototype
方法三是方法二的改进:由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。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属性也改掉了!
alert(Animal.prototype.constructor); // Cat
方法四:利用空对象作为中介
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F()
Cat.prototype.constructor = Cat;
F作为空对象,几乎不占内存,此时修改Cat的prototype对象,就不会修改Animal的prototype对象了
将上面方法封装成一个函数方便使用
function extend(Child,Parent){
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
extend(Cat,Animal);
var cat1 = new Cat();
alert( cat1.species);//动物
这个是YUI库实现继承的方法
另外,说明一点,函数体最后一行
Child.uber = Parent.prototype;
意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。(uber是一个德语词,意思是"向上"、"上一层"。)
这等于在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。
方法五:拷贝继承
将父对象的所有属性和方法都拷贝到子对象中
function Animal(){}
Anima.prototype.species = "动物";
function extend2( Child,Parent){
var p = Parent.prototype;
var c = Child,prototype;
for(var i in p){
c[i] = p[i];
}
Child.uber = p
}
extend2( Cat,Animal );
var cat1 = new Cat("小花",“花”);
alert(cat1.species);//动物
三、非构造函数的继承
var Chines = {
nation : "中国"
};
var Doctor = {
career:"医生";
}
让医生继承中国,成为中国医生。
方法一:object()方法
function object(parent){
var F = function(){};
F.prototype = parent;
return new F();
}
var Doctor = object(Chinese);
Doctor.career = "医生";
alert(Doctor.nation);//中国
方法二:浅拷贝
function extend3(p){
var c = {};
for(var i in p){
c[i] = p[i];
}
c.uber = p;
return c;
}
var Doctor = extend3(Chines);
Doctor.career = "医生";
alert(Doctor.nation);//中国
但是,这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。
请看,现在给Chinese添加一个"出生地"属性,它的值是一个数组。
Chinese.birthPlaces = [‘北京‘,‘上海‘,‘香港‘];
通过extendCopy()函数,Doctor继承了Chinese。
var Doctor = extendCopy(Chinese);
然后,我们为Doctor的"出生地"添加一个城市:
Doctor.birthPlaces.push(‘厦门‘);
发生了什么事?Chinese的"出生地"也被改掉了!
alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门
alert(Chinese.birthPlaces); //北京, 上海, 香港, 厦门
所以,extendCopy()只是拷贝基本类型的数据,我们把这种拷贝叫做"浅拷贝"。这是早期jQuery实现继承的方式。
方法三:深拷贝
function deepCopy(parent,c){
var c = c || {};
for( var i in parent){
if( typeof(parent[i] == "object") ){
c[i] = (parent[i].constructor == Array ) ? [] : {};
deepCopy(parent[i] , c[i]);
}else{
c[i] = parent[i];
}
}
return c;
}
var Doctor = deepCopy(Chinese);
Doctor.carrer = "医生";
Chinese.brithPlace = ["上海",“北京”,“南京”]
alert(Doctor.nation);//"中国"
Doctor.brithPlace.push ("内蒙古");
alert(Doctor.brithPlace);//["上海",“北京”,“南京”,“内蒙古”]
目前jquery库使用的是这种继承方法