ECMAScript支持面向对象(OO)编程,但不使用类或者接口。
对象可以在代码执行过程中创建和增强,因此具有动态性而非严格定义的实体。、
在没有类的情况下,可以采用下列模式创建对象。
1)工厂模式,使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。
这个模式后来被构造函数模式所取代。
2)构造函数模式,可以创建自定义引用类型,可以像创建内置对象实例一样使用new操作符。
不过,构造函数模式也有缺点,即它的每个成员都无法得到复用,包括函数。
由于函数可以不局限于任何对象(即与对象具有松散耦合的特点),因此没有理由不在多个对象间共享函数。
3)原型模式,使用构造函数的prototype属性来指定那些应该共享的属性和方法。
组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法。
JavaScript主要通多原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。
这样,子类型就能够访问父类型的所有属性和方法,这一点与基于类的继承很相似。
原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜单独使用。
解决这个问题的技术是借用构造函数,即在子类型构造函数的内部调用父类型构造函数。
这样就可以做到每个实例都具有自己的属性,同时还能保证只使用构造函数模式来定义类型。
使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。
此外,还存在下列可供选择的继承模式。
1)原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。
而复制得到的副本还可以得到进一步改造。
2)寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,
最后返回对象。为了解决组合继承模式由于多次调用福类型构造函数而导致低效率问题,
可以将这个模式与组合继承一起使用。
3)寄生组合式继承,集寄生式继承和组合继承的有点于一身,是实现基于类型继承的最有效方式。
/*
* 面向对象的程序设计
*/
function cl(x){
console.log(x);
}
//6.1 理解对象
var person={ //使用对象字面量创建对象
name:"Jason",
age:26,
job:"Software Engineer",
sayName:function(){
cl(this.name);
}
};
//6.1.1 属性类型 defineProperty()
//6.1.1.1 数据属性
var person={};
Object.defineProperty(person,'name',{
writable:false, //表示能否修改属性的值
configurable:false, //表示能否通过delete删除属性从而重新定义属性
enumerable:false, //表示能否通过for-in循环返回属性
value:'Jason'
});
cl(person.name); //=>Jason
person.name="Greg";
cl(person.name); //=>Jason
delete person.name;
cl(person.name); //=>Jason
// 6.1.1.2 访问器属性
var book={
_year:2014, //下划线记号,用于表示只能通过对象方法访问的属性
edition:1
};
Object.defineProperty(book,"year",{
get:function(){
return this._year;
},
set:function(newValue){
if(newValue>2014){
this._year=newValue;
this.edition+=newValue-2014;
}
}
});
book.year=2015;
cl(book.edition); //=>2
//6.1.2 定义多个属性 defineProperties()
var book={};
Object.defineProperties(book,{
_year:{value:2014},
edition:{value:1},
year:{
get:function(){
return this._year;
}
},
set:function(newValue){
if(newValue>2014){
this._year=newValue;
this.edition+=newValue-2014;
}
}
});
cl(book.edition);//=>1
// 6.1.3 读取属性的特性
var descriptor=Object.getOwnPropertyDescriptor(book,"_year");//取得给定属性的描述符,_year为数据属性
cl(descriptor.value); //=>2014
cl(descriptor.configurable); //=>false
cl(typeof descriptor.get); //=>undefined
var descriptor=Object.getOwnPropertyDescriptor(book,"year");//取得给定属性的描述符,year为访问器属性
cl(descriptor.value); //=>undefined
cl(descriptor.enumerable); //=>false
cl(typeof descriptor.get); //=>function
//6.2 创建对象
//6.2.1 工厂模式创建对象
//优缺点:解决了创建多个相似对象的问题,却没有解决对象识别的问题
function createPerson(name,age,job){
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){
cl(this.name);
};
return o;
}
var person1=createPerson("Jason",26,"Software Engineer");
var person2=createPerson("Greg",27,"Doctor");
//6.2.2 构造函数模式创建对象
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
cl(this.name);
}
}
var person1=new Person("Jason",26,"Software Engineer");
var person2=new Person("Greg",27,"Doctor");
cl(person1.constructor==Person); //=>true 构造函数属性constructor可用来标识对象类型
cl(person2 instanceof Person); //=>true instanceof检测对象类型
// 6.2.2.1将构造函数当做函数
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
cl(this.name);
}
}
//当做构造函数使用
var person=new Person("Jason",26,"Software Engineer");
//person.sayName(); //=>"Jason"
//作为普通函数调用
Person("Greg",27,"Doctor");//添加到window
window.sayName(); //=>"Greg"
//在另一对象的作用域中调用
var o=new Object();
Person.call(o,"Kristen",25,"Nurse");
//o.sayName(); //=>"Kristen"
// 6.2.2.2构造函数的问题:每个方法都要在实例上重新创建一遍
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=sayName;
}
function sayName(){
cl(this.name);
}
//6.2.3 原型模式创建对象
//使用原型对象的好处是:可以让所有对象实例共享它所包含的属性和方法
function Person(){};
Person.prototype.name="Jason";
Person.prototype.age=26;
Person.prototype.job="Software Engineer";
Person.prototype.sayName=function(){
cl(this.name);
};
var person1=new Person();
person1.sayName(); //=>"Jason"
var person2=new Person();
person2.sayName(); //=>"Jason"
cl(person1.sayName==person2.sayName); //=>true
//6.2.3.1 理解原型对象 //6.2.3.2 原型与in操作符
cl(Person.prototype.isPrototypeOf(person1)); //=>true 测试实例是否指向原型对象
cl(Object.getPrototypeOf(person1)==Person.prototype); //=>true 确定返回的对象实际就是这个对象的原型
cl(Object.getPrototypeOf(person1).name); //=>"Jason" Object.getPrototypeOf()可以方便地取得一个对象的原型
var person1=new Person();
var person2=new Person();
cl(person1.hasOwnProperty("name")); //=>false
cl("name" in person1);//=>true
person1.name="Robin";
cl(person1.name); //=>"Robin"——来自实例
cl(person2.name); //=>"Jason"——来自原型
cl(person1.hasOwnProperty("name")); //=>true 属性在实例中返回true
cl("name" in person1); //=>true
delete person1.name; //删除实例属性
cl(person1.name); //=>"Jason"——来自原型
cl(person1.hasOwnProperty("name")); //=>false 属性存在于实例中才返回true
//判断属性是原型中的属性
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name) && (name in object);
}
cl(hasPrototypeProperty(person,"name"));//=>true
//Object.keys():接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组
var keys=Object.keys(Person.prototype);
cl(keys);//=>["name", "age", "job", "sayName"]
var p1=new Person();
p1.name="Bob";
p1.age=31;
var p1keys=Object.keys(p1);
cl(p1keys); //=> ["name", "age"]
var keys=Object.getOwnPropertyNames(Person.prototype);//=>获取所有实例属性
cl(keys);//=> ["constructor", "name", "age", "job", "sayName"]
//6.2.3.3 更简单的原型语法
function Person(){};
Person.prototype={
name:"Jason",
age:26,
job:"Software Engineer",
sayName:function(){
cl(this.name);
}
}
var friend=new Person();
cl(friend instanceof Person); //=>true
cl(friend.constructor==Person); //=>false constructor属性不再指向Person函数
//6.2.3.4 原型的动态性
var friend=new Person();
Person.prototype.sayHi=function(){
cl("hi");
}
friend.sayHi(); //=>"hi"
//调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针
//把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系
//请记住:实例中的指针仅指向原型,而不指向构造函数
//6.2.3.5 原生对象的原型
//通过原生对象的原型,不仅可以取得所有默认方法的引用,而且也可以定义新方法
String.prototype.startsWith=function(text){
return this.indexOf(text)==0;
}
var msg="Hello world!";
cl(msg.startsWith("Hello")); //=>true
//6.2.3.5 原型对象的问题
//原型模式创建对象的最大问题是其共享的本性所导致的
function Person(){}
Person.prototype={
constructor:Person,
name:"Jason",
age:16,
job:"Software Engineer",
friends:["Sandy","Alex"],
sayName:function(){
cl(this.name);
}
}
var person1=new Person();
var person2=new Person();
person1.friends.push("Van");
cl(person1.friends); //=>["Sandy", "Alex", "Van"]
cl(person2.friends); //=>["Sandy", "Alex", "Van"]
cl(person1.friends==person2.friends); //=>true
//6.2.4 组合使用构造函数模式和原型模式 (实践中常用)
//构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
function Person2(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["Sandy","Alex"];
}
Person.prototype={
constructor:Person2,
sayName:function(){
cl(this.name);
}
}
var person1=new Person2("Jason",26,"Software Engineer");
var person2=new Person2("Greg",27,"Doctor");
person1.friends.push("Van");
cl(person1.friends); //=> ["Sandy", "Alex", "Van"]
cl(person2.friends); //=> ["Sandy", "Alex"]
cl(person1.sayName==person2.sayName); //=>true
//6.2.5 动态原型模式
function Person3(name,age,job){
//属性
this.name=name;
this.age=age;
this.job=job;
//方法
if(typeof this.sayName!="function"){
Person3.prototype.sayName=function(){
cl(this.name);
};
}
}
var friend=new Person3("Jason",26,"Software Engineer");
friend.sayName();//=>Jason
//6.2.6 寄生构造函数模式 与工厂模式相似
function Person4(name,age,job){
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){
cl(this.name);
};
return o;
}
var friend=new Person4("Jason",26,"Software Engineer");
friend.sayName(); //=>Jason
cl(friend instanceof Person4); //=>false
//寄生构造函数模式可以在特殊情况下用来为对象创建构造函数
function SpecialArray(){
//创建数组
var values=new Array();
//添加值
values.push.apply(values,arguments);
//添加方法
values.toPipedString=function(){
return this.join("|");
};
return values;
}
var colors=new SpecialArray("red","blue","green");
cl(colors.toPipedString());//=>"red|blue|green"
//6.2.7 稳妥构造函数模式:适合在某些安全执行环境
function Person5(name,age,job){
//创建要返回的对象
var o=new Object();
//定义私有变量和函数
o.name=name;
o.age=age;
o.job=job;
//添加方法
o.sayName=function(){
cl(this.name);
};
//返回对象
return o;
}
var friend=Person5("Jason",26,"Software Engineer");
friend.sayName(); //Jason
//6.3 继承
//ECMAScript只支持实现继承,主要是依靠原型链来实现的
//6.3.1 原型链
function SuperType(){
this.prototype=true;
};
SuperType.prototype.getSuperValue=function(){
return this.prototype;
};
function SubType(){
this.subproperty=false;
}
//继承了 SuperType
SubType.prototype=new SuperType();
//添加新方法
SubType.prototype.getSubValue=function(){
return this.subproperty;
};
//重写父类型中的方法
SubType.prototype.getSuperValue=function(){
return false;
}
var instance=new SubType();
//instance指向SubType的原型,SubType的原型又指向SuperType的原型
cl(instance.getSuperValue()); //=>false
/**
* instance.getSuperValue()会经历三个搜索步骤:
* 1)搜索实例
* 2)搜索SubType.prototype;
* 3)搜索SuperType.prototype。
*/
//6.3.1.1 别忘记默认的原型:所有引用类型默认继承了Object
//6.3.1.2 确定原型和实例的关系
cl(instance instanceof Object);//=>true
cl(instance instanceof SuperType);//=>true
cl(instance instanceof SubType);//=>true
cl(Object.prototype.isPrototypeOf(instance));//=>true
cl(SuperType.prototype.isPrototypeOf(instance));//=>true
cl(SubType.prototype.isPrototypeOf(instance));//=>true
//6.3.1.3 谨慎地定义方法
//通过原型链实现继承时,不能使用对象字面量创建原型方法
//6.3.1.4 原型链的问题:最主要的问题来自包含引用类型值的原型;
function SuperType2(){
this.colors=['red','blue','green'];
};
function SubType2(){};
//继承了SuperType2
SubType2.prototype=new SuperType2();
var instance1=new SubType2();
instance1.colors.push('black');
cl(instance1.colors); //=> ["red", "blue", "green", "black"]
var instance2=new SubType2();
cl(instance2.colors); //=> ["red", "blue", "green", "black"]
//原型链的第二个问题是:创建子类型的实例时,不能向父类型的构造函数中传递参数
//6.3.2 借用构造函数(也叫伪造对象或经典继承):在子类型构造函数的内部调用父类型构造函数
function SuperType3(){
this.colors=['red','blue','green'];
}
function SubType3(){
//继承了SuperType3
SuperType3.call(this);
}
var instance3=new SubType3();
instance3.colors.push("black");
cl(instance3.colors); //=>["red", "blue", "green", "black"]
var instance4=new SubType3();
cl(instance4.colors); //=>["red", "blue", "green"]
//6.3.2.1 传递参数:即可以在子类型构造函数中向父类型构造函数传递参数
function SuperType5(name){
this.name=name;
}
function SubType5(){
//继承了SuperType5,同时还传递了参数
SuperType5.call(this,"Jason");
//实例属性
this.age=25;
}
var instance5=new SubType5();
cl(instance5.name); //=>"Jason"
cl(instance5.age); //=>25
// 6.3.2.2 借用构造函数的问题:方法都在构造函数中定义,因此函数复用就无从谈起。所有类型都只能使用构造函数模式。
//6.3.3 组合继承(也叫伪经典继承),实践中常用的继承模式
function SuperType6(name){
this.name=name;
this.colors=['red','blue','green'];
}
SuperType6.prototype.sayName=function(){
cl(this.name);
};
function SubType6(name,age){
//借用构造函数继承属性
SuperType6.call(this,name);
//定义私有属性
this.age=age;
}
//使用原型链继承方法
SubType6.prototype=new SuperType6();
//定义私有方法
SubType6.prototype.sayAge=function(){
cl(this.age);
};
var instance6=new SubType6('Jason',26);
instance6.colors.push('black');
cl(instance6.colors); //=>["red", "blue", "green", "black"]
instance6.sayName(); //=>Jason
instance6.sayAge(); //=>26
var instance7=new SubType6('Greg',27);
cl(instance7.colors); //=>["red", "blue", "green"]
instance7.sayName(); //=>"Greg"
instance7.sayAge(); //=>27
//6.3.4 原型式继承 Object.create()方法
//6.3.5 寄生式继承
//6.3.6 寄生组合式继承:只调用一次父类型构造函数,提高效率
function inheritPrototype(subType,superType){
var prototype=Object.create(superType.prototype); //创建对象
prototype.constructor=subType; //增强对象
subType.prototype=prototype; //指定对象
}
function SuperType7(name){
this.name=name;
this.colors=['red','blue','green'];
}
SuperType7.prototype.sayName=function(){
cl(this.name);
}
function SubType7(name,age){
//借用构造函数继承属性
SuperType7.call(this,name);
//定义私有属性
this.age=age;
}
//寄生式继承方法
inheritPrototype(SubType7,SuperType7);
//定义私有方法
SubType7.prototype.sayAge=function(){
cl(this.age);
}
var instance8=new SubType7('Jason',26);
instance8.colors.push('black');
cl(instance8.colors); //=>["red", "blue", "green", "black"]
instance8.sayName(); //=>Jason
instance8.sayAge(); //=>26