第六章:面向对象的程序设计

面向对象:OO(Object-Oriented)
对象:一组没有特定顺序的值,可以是数据或函数


6.1理解对象
最简单:
var person=new Object();
person.name="Tom";
person.age=29;


person.sayName=function(){
alert(this.name);
};


对象字面量创建
var person={
name:"Tom",
age:29,
sayName:function(){
alert(this.name);
}
};


6.1.1 属性类型
数据属性和访问器属性
1.数据属性:可以用Object.defineProperty()方法修改这个特性,参数:对象、属性名、描述符对象
var person={};
Object.defineProperty(person,"name",{
writable:false,
value:"tom"
});
alert(person.name);  //tom
person.name="ggg";  
alert(person.name);  //tom,因为不可写


2.访问器属性,有点像c#里面的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;
alert(book.edition); //2


6.1.2 定义多个属性
Object.defineProperties(),参数:对象,对象属性集合{}


6.1.3读取属性的特性
var book={};
Object.defineProperties(book,{
_year:{
value:2004
}
})
var descriptor=Object.getOwnPropertyDescriptor(book,"_year");
alert(descriptor.value); //2004


6.2创建对象
6.2.1 工厂模式:用工厂方法代替new Object()
function createPerson(name,age,job){
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){
alert(this.name)
};
return o;
}
var person1=createPerson("nic",29,"engineer");
var person2=createPerson("greg",27,"doctor");


6.2.2 构造函数模式:new 一个函数,函数就是对象
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
alert(this.name);
};
}
var person1=new Person("nic",29,"soft");
var person2=new Person("greg",27,"doctor");
构造函数模式比工厂模式好的原因是我知道new的是一个什么对象(Person)


1.将构造函数当作函数
Person("greg",27,"doctor");
window.SayName(); //greg


2.构造函数的问题
函数的方法需要在实例上重新创建一遍
可以这样解决:
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=sayName;
}


function sayName(){
alert(this.name);
}
这样会在全局创建了很多方法,但是这个方法实际只为某个函数服务,最后解决的办法是原型模式实例对象


6.2.3原型模式
为了把最基本的实例对象方法、工厂模式、构造函数模式产生的问题的一个实例对象的模式
通过prototype这个属性来达到共享这个对象的属性和方法,prototype是一个指针指向一个对象,这个对象包含了这个函数的共享的属性和方法
function Person(){
}
Person.prototype.name="tom";
Person.prototype.age="29";
Person.prototype.job="soft";
Person.prototype.sayName=function(){
alert(this.name);
};


var person1=new Person();
person1.sayName();  //tom


var person2=new Person();
person2.sayName(); //tom


1.理解原型对象
无论什么时候,只要创建了一个新函数,这个函数就会创建一个prototype属性,这个属性指向函数的原型对象。


isPrototypeOf():确定通过这个函数实例化出来的对象是不是属于这个函数
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true


Object.getPrototypeOf():返回这个对象的原型
alert(Object.getPrototypeOf(person1)==Person.prototype);  //true
alert(Object.getPrototypeOf(person1).name);  //tom


每当代码获取对象的某个属性时,都会执行两次搜索:
①对象本身的属性,当找到就结束,找不到则执行②
②在原型中查找该属性
如:
function Person(){
}
Person.prototype.name="tom";
Person.prototype.age="29";
Person.prototype.job="soft";
Person.prototype.sayName=function(){
alert(this.name);
};


var person1=new Person();
var person2=new Person();


person1.name="greg";
alert(person1.name);  //greg
alert(person2.name);  //tom


可以使用delete删除实例的属性,就可以返回原型的属性值‘
function Person(){
}
Person.prototype.name="tom";
Person.prototype.age="29";
Person.prototype.job="soft";
Person.prototype.sayName=function(){
alert(this.name);
};


var person1=new Person();
var person2=new Person();


person1.name="greg";
alert(person1.name);  //greg-实例
alert(person2.name);  //tom-原型


delete person1.name;
alert(person1.name);  //greg-原型


hasOwnProperty():检测该属性是否存在于实例中,还是存在于原型中
例子:
function Person(){
}
Person.prototype.name="tom";
Person.prototype.age="29";
Person.prototype.job="soft";
Person.prototype.sayName=function(){
alert(this.name);
};


var person1=new Person();
var person2=new Person();


alert(person1.hasOwnProperty("name"));  //false


person1.name="greg";
alert(person1.name);  //greg-实例
alert(person1.hasOwnProperty("name"));  //true


alert(person2.name);  //tom-原型
alert(person2.hasOwnProperty("name"));  //false


delete person1.name;
alert(person1.name);  //tom-原型
alert(person1.hasOwnProperty("name"));  //false


2.原型与in操作符
in有两种方法操作(for-in和单独使用),单独使用:通过对象能够访问给定属性时返回true


function Person(){
}
Person.prototype.name="tom";
Person.prototype.age="29";
Person.prototype.job="soft";
Person.prototype.sayName=function(){
alert(this.name);
};


var person1=new Person();
var person2=new Person();


alert(person1.hasOwnProperty("name"));  //false
alert("name" in person1);  //true


person1.name="greg";
alert(person1.name);  //greg-实例
alert(person1.hasOwnProperty("name"));  //true
alert("name" in person1);  //true


alert(person2.name);  //tom-原型
alert(person2.hasOwnProperty("name"));  //false
alert("name" in person2);  //true


delete person1.name;
alert(person1.name);  //tom-原型
alert(person1.hasOwnProperty("name"));  //false
alert("name" in person1);  //true


将in和hasOwnProperty()一起使用就可以判断一个属性是存在于对象还是原型中
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name)&&(name in object);   //注意object.hasOwnProperty(name)前面的!,当传入的属性时原型就会返回true
}


例子:
function Person(){
}
Person.prototype.name="tom";
Person.prototype.age="29";
Person.prototype.job="soft";
Person.prototype.sayName=function(){
alert(this.name);
};


var person=new Person();
alert(hasPrototypeProperty(person,"name"));  //true


person.name="greg";
alert(hasPrototypeProperty(person,"name"));  //false


for...in 语句用于遍历数组或者对象的属性(对数组或者对象的属性进行循环操作)。而这个属性是Enumerable才可以for in
其他浏览器自己定义的属性是可枚举的,但是在ie8及以前的浏览器,自己定义的属性是不可以枚举的
var o={
toString:function(){
return "my object";
}
};
for(var prop in o){
if(prop=="toString"){
alert("found toString"); //在ie8及以前的浏览器不会显示
}
}
可以使用Object.key()方法解决
function Person(){
}
Person.prototype.name="tom";
Person.prototype.age="29";
Person.prototype.job="soft";
Person.prototype.sayName=function(){
alert(this.name);
};


var keys=Object.keys(Person.prototype);
alert(keys); //name,age,job,sayName


var p1=new Person();
p1.name="rob";
p1.age=31;
var p1keys=Object.keys(p1);
alert(p1keys); //注意:Object.key()是根据传入的对象获取该对象的属性,不是像取属性值那样找不到然后找原型,如果new对象后没有设置属性就不会返回值的


Object.getOwnPropertyNames():获取所有的实例属性,无论可否枚举,如果new对象后没有设置属性就不会返回值的
var keys=Object.getOwnPropertyNames(Person.prototype);
alert(keys); //constructor,name,age,job,sayName


3.更简单的原型语法
用对象字面量来重写原型对象,这样导致了这个原型的constructor不在指向这个函数
function Person(){
}
Person.prototype={
name:"tom",
age:29,
job:"soft",
sayName:function(){
alert(this.name);
}
};
此时constructor无法确定对象的类型
var friend=new Person();
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor==Person); //false
alert(friend.constructor==Object); //true


如果constructor的值很重要,可以重设这个值,但是设置了之后Enumeraber就变成了true了,因为这相当于你自己定义的属性
function Person(){
}
Person.prototype={
constructor:Person,
name:"tom",
age:29,
job:"soft",
sayName:function(){
alert(this.name);
}
};


Object.defineProperty(),可以改变属性的一些设置,并非全部浏览器兼容
function Person(){
}
Person.prototype={
name:"tom",
age:29,
job:"soft",
sayName:function(){
alert(this.name);
}
};
//重设构造函数,并非全部浏览器兼容
Object.defineProperty(Person.prototype,"constructor",{
enumerable:false,
value:Person
});


4.原型动态性
当没有重写prototype对象前,就是没有用字面量的形式来创建原型时,读取属性的方法是搜索实例,找不到会找原型的属性,
但是如果重写prototype对象,就是为了偷懒好看采用字面量来创建原型时,假如在重写前实例化了这个函数,那么将存在两个原型


例子一:这没有重写prototype
var friend=new Person();
Person.prototype.sayHi=function(){
alert("hi");
};
friend.sayHi(); //hi


例子二:重写了prototype,然后在重写前又实例了一个对象
function Person(){
}
var person=new Person();
Person.prototype.name="jack";
Person.prototype={
name:"tom",
age:29,
job:"soft",
sayName:function(){
alert(this.name);
}
};
person.sayName();  //error


5.原生对象的原型
主要体现在可以增加一些类的方法,如:给基本包装类型String添加一个名为startsWith()的方法
String.prototype.startsWith=function(text){
return this.indexOf(text)==0;
};
var msg="hello world!";
alert(msg.startsWith("hello")); //true
最好不要修改原型的方法


6.原型对象的问题
所有实例都取得相同的属性值
function Person(){
}
Person.prototype={
constructor:Person,
name:"tom",
age:29,
job:"soft",
friends:["jack","lee"],
sayName:function(){
alert(this.name);
}
};
var person1=new Person();
var person2=new Person();


person1.friends.push("van");


alert(person1.friends); //"jack","lee","van"
alert(person2.friends); //"jack","lee","van"
alert(person1.friends===person2.friends) //true


6.2.4组合使用构造函数模式和原型模式
创建自定义类型的最常见的方法,组合使用构造函数模式和原型模式
构造函数:用于定义实例属性
原型模式:定义方法和共享的属性


function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["shelby","court"];
}


Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
}


var person1=new Person("nic",29,"soft");
var person2=new Person("greg",27,"doctor");


person1.friends.push("van");
alert(person1.friends); //"shelby","court","van"
alert(person2.friends); //"shelby","court"
alert(person1.friends===person2.friends); //false
alert(person1.sayName===person2.sayName); //true


6.2.6寄生构造函数
通过工厂模式的构造函数的方法,然后用构造函数模式来new 函数
function createPerson(name,age,job){
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){
alert(this.name)
};
return o;
}
var person1=new createPerson("nic",29,"engineer");
如果定义一个全新的类型时不建议,组合使用构造函数模式和原型模式,但是如果是想增加某一个基本类型的属性可以考虑一下,但是还是不建议


6.2.7 稳妥构造函数模式
就是工厂模式不用公共属性还有this
function createPerson(name,age,job){
var o=new Object();
o.sayName=function(){
alert(name)
};
return o;
}
var person=createPerson("nic",29,"engineer");
person.sayName();


6.3继承
继承:接口继承和实现继承
ECMAScrript只支持实现继承,主要依靠原型链来实现
思想就是把要继承的对象的原型等于new 父级对象
例子:
function SuperType(){
this.property=true;
}
SuperType.prototype.getSuperValue=function(){
return this.property;
};
function SubType(){
this.subproperty=false;
}
//继承了SuperTyper
SubType.prototype=new SuperType();


SubType.prototype.getSubValue=function(){
return this.subproperty;
};


var instance =new SubType();
alert(instance.getSuperValue()); //true
alert(instance.getSubValue()); //false


1.别忘记默认的原型
所有的引用类型默认都继承了Object,由于Object.prototype包含了toString(),valueOf()等方法,所以引用类型都可以继承了toString(),valueOf()方法。


2.确定原型和实例的关系
①instanceof
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true


②isPrototypeOf()
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true


3.谨慎地定义方法
给原型添加的方法的代码一定要放在替换原型的语句之后
function SuperType(){
this.property=true;
}
SuperType.prototype.getSuperValue=function(){
return this.property;
};
function SubType(){
this.subproperty=false;
}
//继承了SuperTyper
SubType.prototype=new SuperType();


//添加新的方法
SubType.prototype.getSubValue=function(){
return this.subproperty;
};


//重写超类型中的方法
SubType.prototype.getSuperValue=function(){
return false;
};


var instance =new SubType();
alert(instance.getSuperValue()); //false


ps:要玩继承,先把要继承的原型替换了,在写新方法,并且新加的原型方法不要用字面量写法,不然会覆盖
function SuperType(){
this.property=true;
}
SuperType.prototype.getSuperValue=function(){
return this.property;
};
function SubType(){
this.subproperty=false;
}
//继承了SuperTyper
SubType.prototype=new SuperType();


//使用字面量添加新的方法,会导致上一行代码无效
SubType.prototype={
getSubValue:function(){
return this.subproperty;
},
someOtherMethod:function(){
return false;
}
};


var instance =new SubType();
alert(instance.getSuperValue()); //error


4.原型链的问题:当改变父类的引用类型属性时,多个子类又公用这个父类原型,那么其中一个子类改变父类的引用类型属性,另外一个也会改变
function SuperType(){
this.colors=["red","blue","green"];
}
function SubType(){
}
//继承SuperType
SubType.prototype=new SuperType();


var instance1=new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red","blue","green","black"


var instance2=new SubType();
alert(instance2.colors); //"red","blue","green","black"


6.3.2借用构造函数
通过apply()和call()方法在新创建的对象执行构造函数
function SuperType(){
this.colors=["red","blue","green"];
}


function SubType(){
//继承了SuperTyper
SuperType.call(this);
}


var instance1=new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"


var instance2=new SubType();
alert(instance2.colors); //"red,blue,green


1.传递参数
function SuperType(name){
this.name=name;
}
function SubType(){
//继承了SuperTyper,同时还传递了参数
SuperType.call(this,"nico");

//实例属性
this.age=29;
}
var instance=new SubType();
alert(instance.name);
alert(instance.age);


6.3.3组合继承
简单理解:首先应该知道,无论继承和创建对象,属性都用构造函数,方法都用原型。
 你要继承对象,肯定要继承属性和方法吧;然后对于父类,属性用构造函数方法定义,方法用原型来定义;
 同样的,你定义子类时,属性用构造函数方法定义,方法用原型来定义,但是,当你要继承属性和方法要怎么做呢?
 继承属性就用call()(可以方便传参,并且在子类实例化时,修改父类的引用属性不会引起其他子类的改变),
 继承方法就是原型=new 父类()。
 
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("nic",29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"nic"
instance1.sayAge(); //29


var instance2=new SubType("greg",27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"greg"
instance2.sayAge(); //27


这种继承和新建对象是最常用的,如果不理解为什么要这样做,可以看看单独使用某一种方法是否能实现这种效果,估计尝试就知道了。


6.3.4原型式继承
先掌握基础再学习


6.3.4寄生式继承
先掌握基础再学习


6.3.4寄生组合式继承
先掌握基础再学习































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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值