对象的创建
1.工厂模式
function person(name) {
var obj = new Object();
obj.name = name;
obj.getName = function () {
return "my name is " + this.name;
}
return obj;
}
var p = person("world");
优点:
- 把一类对象的创建封装起来
缺点:
- 不能识别对象类型
- 还有若干其他方法的缺点,他也有
2.构造函数模式
function Person(name){
this.name = name;
this.getName = function(){
return "my name is " + this.name;
}
}
var p = new Person("world");
优点:
- 可以通过instanceof判断对象类型
p instanceof Person;//true
缺点:
- 原本应该属于共用部分,却各自占用一部分空间。如getName方法,理论上不同的Person对象都使用同一个getName方法;但实际上它们各有自己的getName方法,导致内存浪费。
var p1 = new Person("world");
var p2 = new Person("hello");
p1.getName === p2.getName;//false
解决方法:指向外部的方法(但没有更好的封装)
function Person(name){
this.name = name;
this.getName = getName;
}
function getName(){
return "my name is " + this.name;
}
3.原型模型
function Person(){}
Person.prototype.name = "world";
Person.prototype.getName = function(){
return "my name is " + this.name;
}
Person.prototype.friends = ["john","mary"];
var p1 = new Person();
var p2 = new Person();
优点:
- 可以使用instanceof判断对象类型
- 不同的对象共享同一份属性方法
缺点:
- 不同对象默认属性的值相同
- 所有的属性共享
对于基本类型的数据属性,修改时只是为对象添加该属性,查找属性时会先查找自身的属性再去找原型上的属性
p1.name = "new";
console.log(p1.name);//"new" 自身的属性
console.log(p2.name);//"world" 原型的属性
但对于引用类型的数据属性,修改该属性的内容时会影响其他对象
p1.friedns.push("ken");
console.log(p1.friedns);//["john","mary","ken"]
console.log(p2.friedns);//["john","mary","ken"]
4.组合使用构造函数模式和原型模式
function Person(name){
this.name = name;
this.friends = [];
}
Person.prototype.getName = function(){
return "my name is " + this.name;
}
var p1 = new Person("hello");
var p2 = new Person("world");
优点:
- 可以使用instanceof判断对象类型
- 不同的对象共享一份理论上应该共享的属性
- 不同的对象拥有属于自己的数据属性
缺点:
- 公共属性不能封装起来,而是定义在构造函数外部
5.动态原型模式
function Person(name){
this.name = name;
this.friends = [];
if(typeof this.getName!=="function"){
Person.prototype.getName = function(){
return "my name is " + this.name;
}
}
}
var p1 = new Person("hello");
var p2 = new Person("world");
优点:
- 可以使用instanceof判断对象类型
- 不同的对象共享一份理论上应该共享的属性
- 不同的对象拥有属于自己的数据属性
- 所有属性都封装在一起
6.寄生构造函数模式
function Person(name){
var obj = new Array();
obj.name = name;
obj.getName = function(){
return "my name is " + this.name;
}
return obj;
}
var p = new Person("world");
优缺点:和工厂模式差不多,不过可以为对象添加新的属性并通过函数封装起来(如为Array对象添加name属性)
7.稳妥构造函数模式
function Person(name){
var obj = new Array();
obj.getName = function(){
return name;
}
return obj;
}
var p = Person("hello");
优缺点:和工厂模式差不多,不过可以保证原始数据安全,即只能通过getName方法获取name,却无法修改name(闭包的原理)
对象的继承
原型链继承
function Sup(name){
this.name = name;
this.friends = [];
}
Sup.prototype.getName = function(){
return this.name;
}
function Sub(age){
this.age = age;
}
Sub.prototype = new Sup("world");
Sub.prototype.getAge = function(){
return this.age;
}
var sub = new Sub(21);
console.log(sub instanceof Sup);//true
console.log(sub.name);//"world"
console.log(sub.age);//21
优点:
- 可以用instanceof判断继承关系
- 继承父类的属性
缺点:
- 对于父类的引用类型属性,其中一个对象修改后会影响其他的对象
- 创建子类对象的时候,不能在不影响其他子类对象的情况下向父类构造函数传参(即创建子类对象时,继承自父类的属性的值一样且固定)
借用构造函数
function Sup(name){
this.name = name;
this.friends = [];
this.getFriends = function(){
return this.friends;
}
}
Sup.prototype.getName = function(){
return this.name;
}
function Sub(name,age){
Sup.call(this,name);
this.age = age;
}
var sub = new Sub("world",21);
console.log(sub instanceof Sup);//false
console.log(sub.getName);//undefined
console.log(sub.getFriends());//[]
优点:
- 可以向父类构造函数传参
- 每个子类实例都有自己的一份引用类型属性,修改不影响其他实例
缺点:
- 只能继承父类在构造函数使用this定义的属性
- 不能使用instanceof判断子类实例和父类的关系
组合继承
function Sup(name){
this.name = name;
this.friends = [];
}
Sup.prototype.getName = function(){
return this.name;
}
function Sub(name,age){
Sup.call(this,name);
this.age = age;
}
Sub.prototype = new Sup();
Sub.prototype.constructor = Sub;
Sub.prototype.getAge = function(){
return this.age;
}
var sub = new Sub("xin",21);
console.log(sub instanceof Sup);//true
console.log(sub.getName());//"xin"
优点:
- 可以继承父类原型上的属性
- 可以向父类构造函数传参
- 可以使用instanceof判断子类实例和父类的关系
- 子类实例独有一份继承于父类构造函数定义的属性
缺点:
- 调用了两次父类的构造函数
原型式继承
function Sup(name){
this.name = name;
this.friends = [];
}
Sup.prototype.addFriend = function(f){
this.friends.push(f);
}
function Sub(sup){
function f(){}
f.prototype = sup;
return new f();
}
var sup = new Sup("xin");
var sub = Sub(sup);
sub.age = 21;
//使用Object.create
function Sup(name){
this.name = name;
this.friends = [];
}
Sup.prototype.addFriend = function(f){
this.friends.push(f);
}
var sub = Object.create(new Sup("xin"),{
age:{
value:21
}
});
优点:
- 一个对象与另一个对象保持类似
- 可以使用instanceof判断实例和Sup的关系
缺点:
- 若使用同一个Sup对象,所有实例共享引用类型属性
- 没有子类类型
寄生式继承
function object(o){
function f(){}
f.prototype = o;
return new f();
}
function createObj(o){
var clone = object(o);
clone.say = function(){
return "hello";
};
return clone;
}
var obj = {
name:"xin",
friends:[]
};
var ano = createObj(obj);
优点:
- 不仅拥有了obj的属性,还添加属于自己的属性
缺点:
- 自定义属性不能复用
寄生组合式继承
function object(o){
function F(){}
F.prototype = o;
return new F();
}
function inheritPrototype(sub,sup){
var proto = object(sup.prototype);
proto.constructor = sub;
sub.prototype = proto;
}
function Sup(name){
this.name = name;
this.friends = [];
}
Sup.prototype.addFriend = function(f){
this.friends.push(f);
};
function Sub(name,age){
Sup.call(this,name);
this.age = age;
}
inheritPrototype(Sub,Sup);
优点:(修复了组合模式的缺点)
- 只调用了一次Sup的构造函数
- 避免了在Sub的原型上添加不必要的属性
组合继承是直接把子类的原型指向父类实例,从而多了父类的非原型属性
寄生组合式继承的原型链如下,子类的原型并非是父类的实例,故子类的原型不会有该实例的属性。由于使用call调用父类的构造函数,子类故实例拥有父类构造函数中定义的属性
- 可以使用instanceof判断父类和子类实例的关系
基础原型图