javascript学习之路七------面向对象的程序设计

OO语言就是面向对象语言的意思

JavaScript对象的定义是无序属性的集合,其属性可以包含基本值、对象、或者函数,可以把它想象为一个散列表,无非就是一组键值对,其中只可以是数据或者函数!!!
JavaScript对象自带的属性可以分为两个类型,数据属性访问其属性

数据属性:
[[Configurable]]、[[Enumerable]]、[[Writable]]、[[Value]]
[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修该属性的特性,或者能否把属性修改为访问其属性。
[[Enumerable]]:表示能否通过for-in循环返回属性。
[[Writable]]:表示能否修改属性的值。

var rect={
    width:100,
    }

知道为什么可以给这个对象赋值吗?因为[[configurable]]、[[Enumerable]]、[[Writable]]这三个属性特性开关都为true,也就是同意你这么做,所以[[Value]]特性才能设置为指定的值。

如果你想修改属性默认的特性,可以使用Object.defineProperty()方法;

Object.defineProperty(“属性所在的对象”,“属性的名字”,“描述符对象”)

其中,这个描述符对象,可以是多个特性组合:

var Person={};
Object.defineProperty(Person,"name",{writable:false,value:"simalinjia"});

writable:false意味着只读模式开启了,那么后面想给这个函数赋值,那也是徒劳的!!!
再举个例子:

var Person={};
Object.defineProperty(Person,"name",{configurable:false,value:"simalinjia"});

如果这么做的话,那么你删除这个name属性也是不可能的了,因为configurable:false的意思就是说不可以删除!!!而且一旦设置了false,那么在想变成true就不可能了,会报错。

如果你用Object.defineProperty()方法去创建一个新的属性,那么默认的情况下他的特性都是是false!

访问器属性:
[[Configurable]]、[[Enumerable]]、[[Get]]、[[Set]]
[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修该属性的特性,或者能否把属性修改为访问其属性。
[[Enumerable]]:表示能否通过for-in循环返回属性。
[[Get]]:在读取属性时调用的函数
[[Set]]:在写入属性时调用的函数

var book={
__year:2004,//__的意思就是说只能通过对象方法访问属性
edition:1
}
Object.defineProperty(book,"year",{
get:function(){
return this.__year
},
set:function(newValue){
if(newValue>2004){
this.__year=newValue;
this.edition +=newValue-2004;
}
}
})
book.year=2005;

访问其属性常见的方式就是设置一个属性的值会导致其他属性发生变化!!!
同时定义多个DIY属性的方法:

    var book={}
    Object.defineProperties(book,{
    __year:{//数据属性
    writable:true,
    value:2004
    },
    edition:{//数据属性
    writable:true,
    value:1
    }
    year:{//访问其属性
    get:function(){
    return this.__year
    },
    set:function(newValue){
    if(newValue>2004){
    this.__year=newValue;
    this.edition +=newValue-2004;
    }
    }
    }
    })
    book.year=2005;

到这里了有没有想过一个问题,那就是你只是通过Object.defineProperties()这个方法设定了属性的相关特性,但是如果你想获取到属性的相关特性呢?那么有这么一个方法:Object.getOwnPropertyDescriptor(“属性所在的对象”,“你要测得属性”);
还是引用多属性的例子:

var desc=Object.getOwnPropertyDescriptor(book,"__year");//desc是一个对象
desc.value;//拿到了属性值
desc.configurable//是否设置了可删除模式,默认都是false

如果你想创建一个对象,那么你可以这么做:

var rect=new Object();
rect.width=100;
rect.height=100;
rect.background=”red“;
rect.scaleXY=function(){
alert(”我是个矩形“);
}

或者是这么做:

var rect={
width:100,
height:100,
background:"red",
scaleXY:function(){
 alert(”我是个矩形“);
}
}

如果你想用JavaScript创建对象,你可以在下面选几个模式创建试一试:

1.工厂模式:

    function createRect(width, height, background, border) {
        var rect = new Object();
        rect.width = width;
        rect.height = height;
        rect.background = background;
        rect.scaleXY = function () {}
        return rect;
    }

使用:var rect = createRect(300, 300, “red”, 3);

2.构造函数模式:

function Rect(width, height, background, border) {

    this.width = width;
    this.height = height;
    this.background = background;
    this.scaleXY = function () {}

}

使用:var rectP = new Rect(100, 100, “green”, 4);

解释一下吧,如果这个时候用构造方法去定义两个对象,那么这两个对象有一个共同的constructor构造函数属性,而且是指向Person的。

区别是什么:函数名称大写了,不用定义新对象,返回对象了,属性是this指向对象!!!构造函数和普通函数的根本区别就在于是不是new出来的!!!其实本质上来讲他就是函数啊,如果你new一下他,那么就是构造函数,如果你不new他,那么他就是全局的方法而已用window去调用它就可以了!

3.原型模式:

    function RectA() {};
    RectA.prototype.width = 100;
    RectA.prototype.height = 100;
    RectA.prototype.background = "pink";
    RectA.prototype.getWidth = function () {
        alert(this.width);
    }
    var per = new RectA();
    per.getWidth();

这个模式就是所在这个全局内对于这个类函数来说都是共通的。无论你定义了几个对象,其实都是一个,当你创建这个函数的时候,他就有一个prototype原形属性,这个原形属性是一个指针,指向一个对象,也就是说如果你创建了实例,他们和构造函数是没有关系的,有关系的是原型对象[Person.prototype]!!!
如果你想知道这个对象所指的原型对象是什么就可以用这个方法:

Object.getPrototypeOf(要测的对象);

用法:

Object.getPrototypeOf(person1)==Person.prototype

区分一下实例属性和原形属性吧;

function Person(){};
Person.prototype.name="simalinjia";
var person1=new Person();
person1.name="lily";
var person2=new Person();

上面的方法在.name的时候,返回的情况是不一样的,运作机制其实是这样的,如果person1.name的时候,他先回去实例对象的实例属性中查找name,如果有的话就直接取值就好了,如果没有,他还会去原型对象的属性上面去找,以person1为例,它的值就是lily,因为在搜索实例对象的属性时就找到了name属性了,person2就要搜索两次,得到的是simalinjia!

重要的一点是,实例对象的属性,如果同名于原型对象的属性,那么就屏蔽原型对象的属性,而不可以修改原型对象的这个属性。

想要知道这个属性是在实例对象中还是在原型对象中,就要用这个方法:
hasOwnProperty(属性名);//返回的值如果来自实例对象就是true,如果是原型对象就是false.
用法:

person1.hasOwnProperty("name");

如果你想获取到这个对象的所有实例属性,可以用这个方法:

Object.keys(对象);

该方法,没有实例属性的时候拿的就是原型对象的属性,有的话就是实例属性,返回的是以字符串为元素的数组。其中原型对象的constructor属性是不可以枚举的,如果实在是想枚举,那么可以使用这个方法:
var keys=Object.getOwnPropertyNames(原型对象);keys中就拿到了constructor!!!
上面的可以简写成:

        function Person(){};
        Person.prototype=
        {
        name="simalinjia";
        age=29,
        }

这样的话contructor其实指向的就不是Person了,如果你想让他指向的是Person,那么就可以把constructor这样做!

 function Person(){};
            Person.prototype=
            {
            constructor:Person,
            name="simalinjia";
            age=29,
            }

这样做优缺点,原生的constructor是不可以被枚举的,以这种方式写的话现在可以被枚举出来了,我们必须要把它改成不可枚举,开头的时候有个方法可以帮我们实现:

Object.defineProperty(Person.prototype,”contructor,“{enumerable:false,value:Person});

以下friend访问sayName方法会报错,原因是下面的句子动态创建了重写了原型对象,之前的原型对象根本没有name属性和sayName方法:

function Person(){}
var firend=new Person();
Person.prototype={
name:"simalinjia",
sayName:function(){
alert(this.name);
}
}
friend.sayName();//会报错

以上都是我们创建的函数上去加原型对象属性或方法,原生对象其实也是可以这么做的,但是强烈不建议修改原生对象的原形属性!!!

最受欢迎的模式来了,那就是构造函数模式+原生模式的结合

4.组合模式:

function Person(name,age,job)
{
this.name=name;
this.age=age;
this.job=job;
this.friend=["lily","lulu"];
}
Person.prototype={
contructor:Person,
sayName:function(){
alert(this.name);
}
}

这种模式下,创建的实例,构造出来的属性不共享,共享的只有方法而已!!!

上面的方法缺乏灵活性,要想灵活,就要实现动态加载!!!
5.动态原型模式:

function Person(name,age,job)
    {
    this.name=name;
    this.age=age;
    this.job=job;
    this.friend=["lily","lulu"];
    if(typeOf this.sayName !="function"){
    Person.prototype.sayName=function(){
    alert(this.name);
    }
    }
    }

继承
JavaScript实现继承是依靠原型链来实现的。
原型链继承:

function SuperType(){
this.property=true;
}
Super.prototype.getSuperValue=function(){
return this.property;
}

function SubType(){
this.subproperty=false;
}
SubType.prototype=new SuperType();
Subtype.prototype.getSubValue=function(){
return this.subproperty;
}
var instance=new SubType();
instance.getSuperValue();

上面的方式,sub就继承了super的方法,instance存储的是SubType的指针,SubType的prototype里面存储的是SuperType的指针,SuperType的prototype里存储着Object的指针。
instanceof同样可以测出对象的原型链中出现的构造函数,也就是说用来确定原型和实例之间的关系。

instance instanceof object //因为object是在最顶层,所以返回的是true(爷爷)
instance instanceof SuperType //因为object是在第2层,所以返回的是true(爸爸)
instance instanceof SubType //因为object是在第3层,所以返回的是true(自己)

或者下面的方法也适用:

instance isPrototypeOf object //因为object是在最顶层,所以返回的是true(爷爷)
instance isPrototypeOf SuperType //因为object是在第2层,所以返回的是true(爸爸)
instance isPrototypeOf SubType //因为object是在第3层,所以返回的是true(自己)

方法的重载:

 function SuperType(){
    this.property=true;
    }
    Super.prototype.getSuperValue=function(){
    return this.property;
    }
    
    function SubType(){
    this.subproperty=false;
    }
    SubType.prototype=new SuperType();
    Subtype.prototype.getSubValue=function(){
    return this.subproperty;
    }
    Subtype.prototype.getSuperValue=function(){
    return false;
    }
    var instance=new SubType();
    instance.getSuperValue();

上面的写法虽然继承了SuperType,但是子类SubType的方法名和父类重名了,那么父类的方法就被屏蔽掉了,方法也就只能执行子类的了。

我想说的是实践的过程中很少会使用原型链,因为原型链一直是共享的。

那么如何实现不共享呢?
借用构造函数:

function SuperType(){
        this.colors=['red','blue','green'];
        }
 function SubType(){
        SuperType.call(this);
        }
  var instance1 =new SubType();
  instance1.colors.push("black");//red,blue,green,black
  var instance2 =new SubType();
  instance1.colors;//red,blue,green

这种情况下,它仅仅是调用了以下SuperType父类的方法而已!!!
这种方法也可以传递参数:

function SuperType(name){
            this.name=name;
            }
 function SubType(){
            SuperType.call(this,"simalinjia");
            age:29;
            }
var instance=new SubType();
instance.name;
instance.age;

这种方法也是基本上不怎么用啦!!!
组合继承:

function SuperType(name){
                this.name=name;
                this.colors=['red','blue','green'];
                }
 SuperType.prototype.sayName=function(){
 alert(this.name)
 }
     function SubType(name,age){
                SuperType.call(this,name);
                this.age:age;
                }
 SubType.prototype=new SuperType();
 SubType.prototype.constructor=SubType;
 SubType.prototype.sayAge=function(){
 alert(this.age)
 }
 var instance1 =new SubType("simalinjia",29);
 instance1.colors.push("black");
 alert(instance1.colors);
 instance1.sayName();
 instance1.sayAge();
  var instance2 =new SubType("malinjia",32);
 instance2.colors.push("yellow");
 alert(instance2.colors);
 instance2.sayName();
 instance2.sayAge();

较为常用的继承模式,即可不共用,又可以同时用方法!!!

寄生组合模式:

function SuperType(name){
                this.name=name;
                this.colors=['red','blue','green'];
                }
 SuperType.prototype.sayName=function(){
 alert(this.name)
 }
     function SubType(name,age){
                SuperType.call(this,name);
                this.age:age;
                }
 SubType.prototype=new SuperType();
 SubType.prototype.constructor=SubType;
 
 inheritPrototype(SubType,SuperType)//这句话很重要
 
 SubType.prototype.sayAge=function(){
 alert(this.age)
 }
 var instance1 =new SubType("simalinjia",29);
 instance1.colors.push("black");
 alert(instance1.colors);
 instance1.sayName();
 instance1.sayAge();
  var instance2 =new SubType("malinjia",32);
 instance2.colors.push("yellow");
 alert(instance2.colors);
 instance2.sayName();

这个组合才是目前最优的组合!!!
本章结束

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值