首先申明,本文参考的是阮一峰老师的博客,因为写的真是太好了,为了加深记忆,理解,所以自己手写一份,链接是http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html,本文主要抽取了里面的构造函数部分。
构造函数模式
所谓的构造函数是什么呢?其实就是一个普通函数,函数名首字母一般大写,但是内部使用了this变量,对构造函数使用new
运算符,就能生成实例,并且this
变量会绑定在实例对象上。
比如,猫的原型对象现在可以这样写,
function Cat(name,color){
this.name=name;
this.color=color;
}
我们现在就可以生成实例对象了。
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.name); // 大毛
alert(cat1.color); // 黄色
这是cat1和cat2会自动含有一个constructor属性了,指向他们的构造函数。
alert(cat1.constructor == Cat); //true
alert(cat2.constructor == Cat); //true
存在的问题
为cat添加一个属性和一个方法:
function Cat(name,color){
this.name = name;
this.color = color;
this.type = "猫科动物";
this.eat = function(){alert("吃老鼠");};
}
生成实例:
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat ("二毛","黑色");
alert(cat1.type); // 猫科动物
cat1.eat(); // 吃老鼠
表面看起来没有什么问题,但是其实,每次生成实例,所有的属性和方法都会重新生成一份,这样就造成了浪费内存。
alert(cat1.eat == cat2.eat); //false
解决方案:
就是在prototype添加,因为每个构造函数都有prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "猫科动物";
Cat.prototype.eat = function(){alert("吃老鼠")};
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.type); // 猫科动物
cat1.eat(); // 吃老鼠
这时所有实例的type属性和eat()
方法,其实都是同一个内存地址,指向prototype对象,因此就提高了运行效率。
alert(cat1.eat == cat2.eat); //true
构造函数继承的五种方法
现有一个动物的构造函数
function Animal(){
this.species = "动物";
}
Animal.prototype.eat=function(){
console.log('我是动物')
}
还有一个猫的构造函数
function Cat(name,color){
this.name = name;
this.color = color;
}
构造函数绑定
使用call或apply方法,将父级构造绑定到子级构造上面。
function Cat(name,color){
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
cat1.eat();//报错
缺点就是,无法继承父级的原型方法。
prototype模式
如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
这里对第二行做出讲解,原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数。如果没有"Cat.prototype = new Animal();"这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal。
Cat.prototype.constructor == Animal//true
更重要的是每个实例也有一个constructor属性,默认调用prototype.constructor。如果没有第二行,那么:
cat1.constructor == Cat //false
这就导致了继承链错乱,明明cat1是cat的实例化对象,怎么变成Animal了呢,因此我们必须手动纠正,这就是第二行的意义所在。
这里问题是,如果属性存在引用类型,那将将是共享的。
直接继承prototype
第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。
现在,我们先将Animal对象改写:
function Animal(){ }
Animal.prototype.species = "动物";
然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
但是缺点是Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。
利用空对象作为中介
这个方法的存在就是为了解决“直接继承prototype”的缺点的
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
函数最后一行,意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。(uber是一个德语词,意思是"向上"、"上一层"。)这等于在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。
拷贝继承
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
非构造继承
Object
var china={
nation:'中国'
}
var object=function(o){
var fn=function(){}
fn.prototype=o;
return new fn();
}
var doctor=object(china);
console.log(doctor.nation)//中国
浅拷贝
function extendCopy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
return c;
}
缺点大家都知道吧,引用类型数据无法拷贝
深拷贝
var clone= function (obj) {
var a;
if (obj instanceof Array) {
a = [];
for (var i = 0; i < obj.length; i++) {
a.push(this.clone(obj[i]))
}
} else if (obj instanceof Function) {
return eval('(' + obj.toString() + ')')
} else if (obj instanceof Object) {
a = {};
for (var i in obj) {
a[i] = this.clone(obj[i])
}
} else {
return obj;
}
return a;
}