面向对象
一、创建对象
1. 工厂模式
抽象了创建具体对象的过程
function Person(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.satName = function () {
alert(this.name);
};
return o;
}
var person = Person("LiMing",20);
工厂模式虽然解决了创建多个相似对象的问题,但没解决对象识别的问题(均为Object)
2. 构造函数模式
function Person(name,age){
//var o = new Object();
this.name = name;
this.age = age;
this.satName = function () {
alert(this.name);
};
}
var person = new Person("LiMing",20);
与工厂模式的区别:
- 没有显示创建对象
- 直接将属性和方法赋值给this
- 没有return语句
new创建对象过程:
- 创建新对象
- 将构造函数的作用域赋值给新对象(this就指向这个对象)
- 执行构造函数
- 返回新对象
创建自定义构造函数意味着可以将它的实例类型标识为一种特定的类型
2.1 将构造函数当作函数
//作为构造函数
var person = new Person("LiMing",20);
person.sayName();
//作为普通函数
Person("LiMing",20);//添加到window
window.sayName();
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o,"LiMing",20);
o.sayName();
2.2 构造函数的问题
每个方法都要在每个实例上重新创建一次
可以将函数定义转移到构造函数之外,但会失去封装性
3. 原型模式
function Person(){}
Person.prototype.name = "LiMing";
Person.prototype.age = "20";
Person.prototype.sayName = function(){
alert(this.name);
};
};
var person = new Person();
person.sayName();
将方法和属性直接添加到了Person的prototype属性中,构造函数成了空函数。
这些属性和方法都是由所有实例共享的。
3.1 理解原型对象
__proto__:连接存在于实例与构造函数的原型之间,而不是实例与构造函数之间,可以用isPrototypeOf()方法来确定对象之间是否存在这种关系
alert(Person.prototype.isPrototypeOf(person));//true
alert(Objet.getPrototypeOf(person) == Person.prototype);//true
原型最初只含constructor属性,该属性也是共享的,可以通过对象实例访问
使用hasOwnProperty()方法可以检测一个属性是存在于实例中还是存在于原型中。
3.2 原型与in操作符
in操作符会在通过对象能访问给定属性时返回true,无论该属性在实例中还是原型中
3.3 更简单的原型语法
对象字面量方式
function Person(){}
Person.prototype = {
name : "LiMing",
age : 29,
sayName : function () {
alert(this.name);
}
};
但constructor属性不再指向Person,而是Object
可以显式指定 constructor:Person
3.4 原型的动态性
将原型改为另一个对象就等于切断了构造函数与最初原型之间的联系
3.5 原生对象的原型
原生的应用类型都是采用这种模式创建的
通过原生对象的原型,不仅可以取得所有默认方法的引用,也可以定义新方法
3.6 原型对象的问题
省略了构造函数传递参数这一环节,使得所有实例在默认情况下都将取得相同的属性值,由其共享本质导致
4. 组合模式
常用于定义引用类型
function Person(name,age){}
this.prototype.name = name;
this.prototype.age = age;
};
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person = new Person("LiMing",20);
5. 动态原型模式
function Person(name,age){}
this.prototype.name = name;
this.prototype.age = age;
if (typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
};
}
};
只在sayName( )不存在的情况下,才会将他添加到原型中,即只有在初次调用构造函数是才会执行
if语句检查的可以说任一初始化之后应该存在的属性或方法
6. 寄生构造函数模式
基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回创建的对象
function Person(name,age){}
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert(this.name);
};
return o;
};
var person = new Person("LiMing",20);
除了使用new且把包装的函数叫做构造函数之外,与工厂模式其实相同
返回的对象与构造函数或者构造函数的原型属性之间没有关系,不能依赖instanceof操作符来确定对象类型
7. 稳妥构造函数模式
指没有公共属性,其方法也不引用this的对象
与寄生模式的区别
- 新创建的对象的实例方法不引用this
- 不使用new操作符调用构造函数
function Person(name,age){}
var o = new Object();
o.sayName = function(){
alert(this.name);
};
return o;
};
var person = Person("LiMing",20);
person.sayName();//LiMing
具有安全性,除了使用sayName( )方法外没有访问name值的方法
二、继承
1. 原型链
function Dad(){
this.property = true;
}
Dad.prototype.getDadValue = function(){
return this.property;
};
function Son(){
this.sonProperty = false;
}
Son.prototype = new Dad();//继承了Dad
Son.prototype.getSonValue = function(){
return this.sonProperty;
};
var instance = new Son();
alert(instance.getDadValue);//true
Son继承了Dad,继承是通过创建Dad的实例,并将该实例赋给Son.prototype实现的,本质是重写原型对象。
instance.constructor现在指向的是Dad,这是因为Son.prototype中的constructor被重写了。
1.1 别忘记默认的原型
所有引用类型默认都继承自Object
1.2 确定原型与实例的关系
- instanceof操作符
- isPrototypeOf( )方法
1.3 谨慎定义方法
给原型添加方法的代码一定要放在替换原型的语句之后。
不能使用对象字面量创建原型方法,会重写原型链。
1.4 原型链的问题
- 最主要的问题来自包含引用类型值的原型,会被所有实例共享,故应在构造函数中定义属性,而不是在原型对象中定义。
- 在创建子类型实例时,没有办法在不影响所有对象实例的情况下,向超类型的构造函数传递参数。
2. 借用构造函数
又叫伪造对象或经典继承
通过使用apply( )和call( )方法也可以在新创建的对象上执行构造函数
function Dad(){
this.colors = ['red','blue'];
}
function Son(){
Dad.call(this);//继承了Dad
}
var instance1 = new Son();
instance1.color.push('yellow');
alert(instance1.colors);//"red,blue,yellow"
var instance2 = new Son();
alert(instance2.colors);//"red,blue"
Son的每一个实例都会有自己的color属性的副本
2.1 传递参数
相对于原型链有一个巨大优势,即可以在子类的构造函数中向超类构造函数传递参数
function Dad(name ){
this.name = name;
}
function Son(){
Dad.call(this,"LiMing");//继承了Dad,同时传递了参数
this.age = 20;
}
var instance = new Son();
alert(instance.name);//"LiMing"
alert(instance.age);//20
2.2 借用构造函数的问题
方法都在构造函数中定义,函数复用无法实现
3. 组合继承
也叫伪经典继承
思路是使用原型链实现对原型属性和方法的继承,借用构造函数实现对实例属性的继承。
即通过在原型上定义实现了方法的复用,又保证每个实例都有自己的属性。
function Dad(name){
this.name = name;
this.colors = ['red','blue'];
}
Dad.prototype.sayName() = function(){
alert(this.name);
};
function Son(name,age){
Dad.call(this,name);//继承属性,第二次调用Dad()
this.age = age;
}
Son.prototype = new Dad();//继承方法,第一次调用Dad()
Son.prototype.sayAge = function(){
alert(this.age);
}
var instance = new Son("LiMing",20);
instance.colors.push('blue');
alert(instance.colors);//"red,blue,yellow"
isntance.sayName();//LiMing
instance.sayAge();//20
最常用,instanceof和isPrototypeOf( )均能识别
4. 原型式继承
思路是借助原型可以基于已有的对象创建新对象,同时不必创建自定义类型,本质上为一次浅复制
var Person = {
name : "LiMing",
fridens : ["LiHua"]
};
var anotherPerson = Object.create(person)
anotherPerson.name = "ZhaoSi";
anotherPerson.fridens .push("XiaoWang");
alert(person.friends);//"LiHua,XiaoWang"
包含应用类型值的属性始终会共享相应的值,就像使用原型模式
5. 寄生式继承
function createAnother(original){
var clone = object(original); //通过调用函数创建新对象
clone.sayHi = function(){ //以某种方式增强对象
alert("hi");
};
return clone; //返回这个对象
}
var person = {
name : "LiMing";
}
var anotherPerson = createAnother(person);
anotherPerson.satHi();//"hi"
使用寄生式继承来为对象添加函数,不能做到函数复用,与构造函数模式类似。
6. 寄生组合式继承
组合继承最大的问题是,会调用两次超类型构造函数,会导致创建两次同名属性,一组在原型中,一组在新对象上,且新对象的属性屏蔽了原型上的同名属性。
基本思路是,不必为了指定子类型的原型而调用构造函数,所需要的只是超类原型的一个副本。本质上,就是使用寄生式继承来继承超类的原型,再将结果指定给子类型的原型。
function inheritPrototype(son,dad){
var prototype = object(dad.prototype); //创建对象
prototype.constructor = son; //增强对象
son.prototype = protoype; //指定对象
}
function Dad(name){
this.name = name;
}
Dad.prototype.sayName = function(){
alert(this.name);
};
function Son(name,age){
Dad.call(this,name);
this.age = age;
}
inheritPrototype(Son,Dad);
Son.prototype.sayAge = function(){
alert(this.age);
}
只调用了一次Dad构造函数,避免了创建多余的属性,且原型链保持不变,可正常使用instanceof和isPrototypeOf( )。