创建对象
工厂模式
在普通函数中创建一个新对象,为其添加属性和方法,然后返回。在调用的时候直接调用函数,不需要使用new操作符。
function factory(name){
var o = new Object();
o.name = o;
o.sayName = function(){
console.log(this.name);
}
return o;
}
var per = factory('hhh');
- 缺点:批量创建出属性和方法一致的对象,创建出的对象不能确定其类型,每次创建一个对象都需要创建一次方法,方法复用度不高。
//稍加改进型:
function factory(name){
var o = new Object();
o.name = o;
o.sayName = sayName;
return o;
}
function sayName(){
console.log(this.name);
}
var per = factory('hhh');
- 缺点:每个方法都需要在外部写,会添加很多全局变量。
构造函数
和工厂模式的区别,利用了new操作符,使用this关键字,在创建对象的时候要使用new操作符,可以确定对象的类型。
- new操作符实际上的操作是如下几步:
- 1.创建一个空 对象
var obj = new Object();
- 2.将对象的__proto__指向其构造函数的原型;
obj.__proto__ = constructor.prototype;
- 3.执行函数代码;
var ret = constructor.call(obj);
- 4.返回对象;
return obj instanceof Object ? ret : obj
构造函数大致如下:
function Person(name){
this.name = name;
this.sayName = function(){
console.log(this.name);
}
}
var per = new Person('hhh');
- 缺点: 函数复用度不高
原型模式
首先,每个对象都有一个constructor属性,指向其构造函数,__proto__属性,指向其原型对象(即其构造函数的原型);而每个函数都有一个prototype属性,指向其原型,而其原型对象都有一个constructor属性指向构造函数。
在访问对象的属性和方法时,如果在对象自身找不到,我们会沿着原型链一层一层向上找,也就是使用函数的prototype属性,利用作用域查询变量规则,在原型上访问对象变量和方法。所以,为了解决函数方法复用的问题,引入了原型模式。在函数的原型上添加属性和方法,在调用时使用new操作符。
function Person(){}
Person.prototype.sayName = function(){
console.log(this.name);
}
Person.prototype.name = ['hhh','xxx'];
var per = new Person();
从代码中,很明显可以看出,如果单纯只使用原型模式来创建对象的话,我们创建对象时无法传入参数,其次,创建的对象没有自己的属性,对于引用类型name来说,当修改name属性(指的是除了赋值之外的修改)时,就等同于修改原型上的name属性。其他的对象的name属性也会改变,因为所有对象都访问的是其原型对象的属性,等同于所有对象都在改变其原型对象的name属性;而好处是,我们的sayName方法们,只需要写在原型上,复用率大大提高。所以,我们通常使用原型模式来创建对象的方法。
组合模式
利用了构造函数来为对象添加属性,弥补了原型模式“属性共用”的问题;而使用原型模式来为对象添加方法,弥补了构造函数方法复用率不高的问题。
function Person(name){
this.name = name;
}
Person.prototype.sayName = function(){
console.log(this.name);
}
var per = new Person('hhh');
动态模式
在构造函数的基础上,通过添加if语句判断是否需要添加方法。
function Person(name){
this.name=name;
if(typeof this.sayName != "function"){
Person.prototype.sayName = function(){
console.log(this.name);
};
}
}
var per = new Person('hhh');
只有在sayName方法不存在的情况下,才会将它添加到原型中,方法那段代码只有在初次调用构造函数时才会执行,此后,原型已经初始化完成,不需要再做更改了。
寄生构造模式
可以理解为寄生-构造模式,挂构造函数的名字(使用时用new操作符),而内部实现代码和工厂模式一模一样,所创建出来的实例无法指向构造函数。
稳妥模式
不使用this和new,与寄生构造函数相似。
function Person(name){
var o = new Object();
o.sayName = function(){
console.log(name);
}
//注意,发现,name属性除了可以通过sayName方法访问到,其他都不行
return o;
}
var per = Person('hhh');
per.sayName();// 'hhh'
//注意,在此想要更改其name属性
per.name = 'eee';
per.sayName();// 'hhh'
console.log(per.name); // 'eee'
继承
原型链继承
利用函数的prototype属性,改变子类构造函数的prototype为父类的实例。
function Father(){
}
Father.prototype.sayName = function(){
console.log(this.name);
}
function Son(name){
this.name = name;
}
Son.prototype = new Father(); //Son的原型指向一个Father实例,假设叫a
//a.__proto__ = Father.prototype;
//所以son实例可以沿着原型链向上找到sayName方法
var son = new Son('hhhh');
son.sayName(); // 'hhhh'
- 缺点:和原型模式一样,引用类型的属性被所有实例共享,以及无法向父构造函数传参。
继承关系如下:
借助构造函数继承
在子类构造函数中调用父类构造函数,从而达到继承父类的目的。
function Father(){
this.name = ['111','222'];
}
function Son(){
Father.call(this);
}
var son = new Son();
son.name.push('222');
console.log(son.name); //['111','222','222'];
var son2 = new Son();
console.log(son2.name); //['111','222'];
首先为啥在子类中调用父类构造函数可以达到继承的目的?首先,这里的继承是指我创建的实例会有我父类的属性和方法,原因就来自于其内部的Father.call(this)。在使用new操作符时,首先会创建一个空的对象obj,并将其__proto__属性指向其构造函数的原型对象,即Son.prototype,然后就会执行构造函数内部的代码,可以理解为在子类中执行了父类的代码,所以obj就会有name属性,最后返回。
- 优点: 各个实例有属于自己的属性和方法,没有创建多余的父类的实例,可以向父类构造函数传参。
- 缺点:在原型链上不会体现和父类的关系,在父类构造函数内部创建的方法,在每次都要创建一次。
组合继承
融合原型链和借助构造函数两者,利用原型链来继承父类方法,利用构造函数来继承父类属性。
function Father(){
this.name = ['111','222'];
}
Father.prototype.sayName = function(){
console.log(this.name);
}
function Son(){
Father.call(this);
}
Son.prototype = new Father();
Son.prototype.constuctor = Son; //弥补重写Son原型对象的不足
var son = new Son();
缺点:调用了两次父类构造函数。(我觉得没啥其实)
原型式继承
将传入的对象当做创建对象的原型,Object.create的模拟实现
funtion create(o){
function F(){}
F.prototype = o;
return new F();
}
- 缺点:和原型链继承一个道理,创建的实例共享原型上的所有属性和方法。
- 优点:不用写多余的代码来创建一个子类和父类实例
寄生式继承
用于封装继承过程的函数,在内部以某种方式做增强对象,最后返回。
function create(o){
var c = Object.create(o);
c.sayName = function(){
console.log(this.name);
}
return clone;
}
- 缺点:每次创建对象都会创建一次方法。
寄生组合继承
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function prototype(child, parent) {
var prototype = object(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}
funtion Parent(name){
this.name = name
}
Parent.prototype.sayName = function(){
console.log(this.name);
}
function Child(name){
Parent.call(this);
}
// 当我们使用的时候:
prototype(Child, Parent);
- 优点:只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变