接上一张,下面是一些针对上述问题的解决方案
组合使用构造函数模式和原型模式
构造函数用于定义实例属性,原型模式用于定义方法和共享的属性;这样每个实例都会有自己的一份实例属性的副本,同事共享这对方法的引用,最大限度的节省了内存。
重写前面的例子
这是应用最广泛的一种创建自定义类型的方法
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["hello","world"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
console.log(this.name);
}
}
var p1 = new Person("sdf",12,"sdf");
var p2 = new Person("sdf",19,"sdsdff");
p1.friends.push("cab");
console.log(p1.friends);//["hello","world","cab"]
console.log(p2.friends);//["hello","world"]
p1.friends === p2.friends//false
p1.sayName === p2.sayName;//true
动态原型模式
使用动态原型模式时,不能使用对象字面量重写原型,会在已经创建的实例的情况下重写原型,会切断现有实例与新原型之间的联系
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
//方法
if(typeof this.sayName != "function"){
Person.prototype.sayName = function(){
console.log(this.name);
}
}
}
var p1 = new Person("sdf",12,"sdf");
寄生构造函数模式
与工厂模式基本一致,除了使用时是用new操作符;可以使用其他模式不要使用这种;因为反回的对象与构造函数或构造函数的原型属性之间没有关系,不能以来instanceof操作符来确定对象的类型
function Ceateperson(name,age,job){
var o = new Object();
o.name = name;
o.age= age;
o.job= job;
o.sayName = function (){
console.log(this.name)
}
return o;
}
var person = new Ceateperson('Nickho',29,'sdfsdfdsf');
可以用来创建具有额外方法的特殊数组等
function SpecialArray(){
var values = new Array();
values.push.apply(values,arguments)
values.toPipedString = function (){
return this.jion("|");
}
return values;
}
var color = new SpecialArray('Nickho','29','sdfsdfdsf');
console.log(color.toPipedString());//"Nickho|29|sdfsdfsf"
稳妥构造函数模式
function Ceateperson(name,age,job){
var o = new Object();
o.sayName = function (){
console.log(name)
}
return o;
}
var friend = Createperson('Nickho',29,'sdfsdfdsf');
friend.sayName()//"Nickho"
与寄生构造函数类似,但是有两点不同,创建对象的实例方法不引用this,不使用new操作符构造函数
这样name只能通过sayName方法访问
继承
- 原型链
基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法
构造函数、原型、实例的方法:
每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,实例都包含一个指向原型对象的内部指针。如果我们让原型对象等于另一个类型的实例,则此时的原型对象将会包含一个指向另一个原型的指针,相应的另一个原型中也包含这指向另一个构造函数的指针,假如另一个原型又是另一个类型的实例,那么层层推进,就构成了实例和原型的链条。
大致代码如下
function SuperType(){
this.property = true
}
SuperType.prototype.getSuperValue = function(){
return this.property
}
function SubType(){
this.subproperty = false
}
//继承了SuperType
SubType.prototype = new SuperType();//重写对象原型
SubType.prototype.getSubValue = fucntion(){
return this.subproperty
}
var instance = new SubType();
console.log(instance.getSuperValue);//true
原型链增加了搜索过程:实例 到 SubType.prototype 到 SuperType.prototype
当然还有默认的原型:Object,也就是说默认的原型都会包含一个内部指针,指向Object.prototype
确定原型和对象的关系
instanceof 实例与原型链中出现过的构造函数,就会返回true
isPrototypeof ,只要原型链中出现过的原型,都可以说是该原型链所派生的实例的原型
谨慎的定义方法
子类型有时候需要重写超类型(父级)的某个方法,或者需要添加超类型中某个不存在的方法。但不管怎么样,给原型添加方法的代码一定要放在替换原型的语句之后;还有通过原型链实现继承时,不能使用字面量创建原型方法,这回重写原型链
原型链的问题
1.对于引用类型的属性,会造成所有的实例共享问题(即所有实例引用类型属性相同)
2.创建子类型的实例时,不能向超类的构造函数中传递参数,即没有办法在不影响所有对象实例的情况下,给超类的构造函数传递参数。因此单独的原型链很少使用
function SuperType(){
this.colors = [1,2,3]
}
function SubType(){
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push(4);
console.log(instance1.colors);//[1,2,3,4]
var instance2 = new SubType();
console.log(instance2.colors);//[1,2,3,4]
借用 构造函数
在子类型构造函数的内部调用超类型的构造函数。(函数,特定环境中的执行代码的对象,因此可以使用apply和call方法可以在新创建的对象上执行构造函数)
function SuperType(){
this.colors = [1,2,3]
}
function SubType(){
SuperType.call(this)
}
var instance1 = new SubType();
instance1.colors.push(4);
console.log(instance1.colors);//[1,2,3,4]
var instance2 = new SubType();
console.log(instance2.colors);//[1,2,3]
传递参数
相对于原型链而言,就用构造函数有一个很大的又是,即可以在子类型构造函数中向超类型构造函数传递参数
借用构造函数的问题
也是构造函数的问题,复用性。因此借用构造函数的技术也很少单独使用
组合继承
伪经典继承:使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承;既实现了函数的复用,又保证了每个实例有自己的属性
function SuperType(name){
this.name = name;
this.colors = ['red',"blue","green"];
}
SuperType.prototype.sayName = function(){
console.log(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(){
console.log(this.age);
}
var i1 = new SubType("sdf",21);
i1.colors.push("sdf");
console.log(i1.colors);//['red',"blue","green","sdf"];
i1.sayName();//'sdf'
i1.sayAge();//21
var i2 = new SubType("sname",22);
console.log(i2.colors);//['red',"blue","green"];
i2.sayName();//'sname'
i2.sayAge();//22
这是最常用的继承模式
原型式继承
ECMAScript 5通过Object.create()方法规范了原型式继承:入参为一个用作新对象原型的对象和(可选)一个为新对象定义额外属性的对象;
寄生式继承
寄生组合式继承
组合继承最大问题,无论什么情况下,都会调用两次超类型的构造函数,一次是在创建子类型原型时,一次实在子类型构造函数内部;这会造成有两组属性,一组在SubType原型中,一组在实例上
解决方法,寄生组合式继承:通过构造函数来继承属性,通过原型链的混成方法来继承方法
function object(o){
function F(){}
F.prototype = o;
return new F();
}//本质上将,object 方法对传入其中的对象执行了一次浅复制;
function inheritroPrototype(subType,superType){
var prototype = object(superType.prototype)//创建对象
prototype.constructor = subType;//增强对象
subType.prototype = prototype;
}
function SuperType(name){
this.name = name;
this.colors = ['red',"blue","green"];
}
SuperType.prototype.sayName = function(){
console.log(this.name)
}
function SubType(name,age){
//继承属性
SuperType.call(this,name);
this.age = age;
}
//继承方法
inheritroPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
console.log(this.age);
}
var i1 = new SubType("sdf",21);
i1.colors.push("sdf");
console.log(i1.colors);//['red',"blue","green","sdf"];
i1.sayName();//'sdf'
i1.sayAge();//21
var i2 = new SubType("sname",22);
console.log(i2.colors);//['red',"blue","green"];
i2.sayName();//'sname'
i2.sayAge();//22