前言
之前在知乎上看到别人分享一个面试题目是写一个函数,其中拥有:私有方法,私有变量,公有方法,公有变量。看到以后才发现,自己大红书第六章看的不仔细。。这里来总结一下吧~毕竟是基础中的基础。
JS对象构建
在JS中创建对象只需要很简单的
var person=new Object();
person.name = "johan";
person.age = "29";
person.height = "175";
person.sayName = function(){
alert(this.name);
};
当然这是最初的创建对象的写法,之后对象字面成为了主流的创建对象的方式:
var person = {
name : "johan",
age: 23,
height : 175;
sayName : function(){
alert(this.name);
}
};
而这样子创建对象存在问题:使用一个接口创建很多对象,会产生大量重复的代码。为了解决这个问题,人们开始使用工厂模式(设计模式中的一种)的一种变体来创建对象:
function createPerson(name,age,height){
var o = new Object();
o.name = "johan";
o.age = 23;
o.height = 175;
o.sayName = function(){
alert(this.name);
}
}
这样一来,创建对象只需要调用这个创建对象的函数
var person1 = createPerson("johan","23","175");
var person2 = createPerson("vicent","21","183");
当然我们可以用构造函数的方法去对这个函数进行改写:
function Person(name,age,height){
this.name = name;
this.age = age;
this.height = height;
this.sayName = function(){
alert(this.name);
};
};
var person1 = new Peron("johan",23,"175");
var person2 = new Person("vicent",27,“184”);
这样子写就清爽多了~我们没有显示的去var Object,而且直接把属性和方法直接赋给了this。而且还没有return语句。(如果这个函数用于创建对象,开头字母需要大写!)
我们用它创建实例的时候需要用new操作符:
new干的事情,之前的文章中也总结过(把它贴过来):
1.创建一个新的对象,它的对象类型是object.
2.设置这个新的对象的内部、可访问性、和[prototype]属性为构造函数(指prototype.constructor所指向的函数)中设置的;
3.执行构造函数,当this关键字被提及时,使用新创建对象的属性;
4.返回新创建对象的对象(除非构造方法中返回的是’无原型’)。
这跟书中总结差不多,简单来说,就是创建对象,绑定this,赋予作用域,protol。
那么这里的protol的再指定就创造出了原型链!!!每new一次,这条链就会变长一节。
TIP:如果不是用new的话,那么这个函数的this会指向window。
但是构造函数的方法也是存在问题的:
我们实例化的对象的方法是不同的,因为funciton也是对象(Function)。但是,同样的方法进行了重复,这根本没有必要,那么我们若是在构造函数外部去声明一个方法的函数,在构造函数内去调用呢?这样子的话,就需要在全局中定义很多很多的方法了。。而且全局中的这个方法只为了给一个对象调用。。全局就名不副实了。
那么原型模式就氤氲而生了!!
这也是原型链的起。结合我们之前new的protol的指向,我们可以知道,原型与原型链是为了继承以及解决对象的方法的不必要复写而诞生的,它是基于原型设计模式而设计的。
我创建的每一个函数都有一个原型属性,这个属性是一个指针指向函数的原型对象,我们为这个原型对象添加的属性与方法可以被所有对象的实例所公用。关于原型与原型链这里就不赘述了。我们继续谈关于继承的问题。
那么有了原型属性,创建对象时就不存在问题了吗?
不,原型的弊病就在于我们可以通过实例化的对象去对共享的属性进行覆盖。如果这个属性时基本值那还好(数字,字符创,布尔值),但是如果是引用类型的值就有问题了。我们在覆盖基本对象时,由于实例中的基本值是在栈内存中的,我们对原型中的基本值进行了复制。开辟了新的内存去保存基本值,但是引用类型就不是这样了。我们实例中的引用类型的值与原型中的引用类型值是放在堆内存中的同一个副本,我们对它进行覆盖或者复写时,会是所有实例中相应的引用类型值发生改变。这是我们所不希望发生的事情,因为我们需要各个实例各自拥有自己的实例属性。
这个问题也简单:
我们可以把方法函数单独的分离出来,使用this来写属性(基本类型和引用类型),会用原型来写方法。就像这样:(组合只用构造函数模式和原型模式)
function Person(name,age,height,friend){
this.name = name;
this.age = age;
this.height = height;
this.friends = ["me“,"me","me"];
}
Person.prototype = {
constructor : Person,
sayName = function(){
alert(this.name)
}
}
这样一来属性和方法就可以完全分开了!
但是方法却写在了构造函数外部,解决的方法是使用if语句:若函数原型中不存在该方法,则添加该方法
if(type of this.sayName != function){
Person.prototype.sayName = function(){
alert(this.name)
}
}
这样子去添加方法时,只有在初次调用构造函数时,才会执行。伺候原型已经完成了初始化,不需要做多余的修改了。
至于寄生式构造函数模式,和稳妥构造函数模式都是在特定的情况下才会使用到的~我们这里只简单的提一下:
前者出了使用了new以外与工厂模式完全一样,他需要返回的对象与构造函数与构造函数的原型属性之间没有关系。也就是说,构造函数返回的对象与构造函数外部创建的对象没有什么不同。它在特殊的情况下可以为对象创建构造函数。而稳妥构造模式他将传入的参数直接给内部方法函数引用,这样一来出了闭包函数,外部是访问不到这个参数的。
关于对象的构造我们已经大致讲完了,接下里是继承
继承
关于继承我们之前已经谈到了,原型链就是用来继承的,子类通过原型链可以集成到父类的属性与方法。但是原型链也是有缺陷的,首先当用字面量创建原型方法时,会重写原型链,切断现有原型与之前实例对象之间的联系,这些实例引用的依然是之前的原型。再者,由于引用类型不会被深复制,所以会被实例改写,那么如果解决这个问题呢?
借用构造函数:
function Baba(){
this.color = ["red","blue"];
}
function Erzi(){
Baba.call(this);
}
这样一来,当实例化了子类对象后,子类对引用类型属性的复写就不会影响原型了!
而且我们还可以从子类中想父类传递参数。在子类中添加this.age = 23后,实例化的子类能够添加年龄这个参数。非常方便!
但是这样一来,就又发生了构造函数存在的问题了!方法不能够复用!!!
所以我们又可以利用原来创建对象时所运用的方法了!把方法用原型来创建,属性使用this创建。再使用借用构造函数的方法继承属性,使用原型链来继承方法。这样就保证了实例拥有自己的属性,而且方法不会重复的写了。
另外两种方法
原型式继承:
function object(o){
function F(){}
F.prototype = o;
return new F();
}
这样子的方法进行继承相当于对传入的对象进行了一次前复制。但是它有原型链继承的一个毛病,就是引用类型始终会共享相应的值。但是如果想让一个对象始终与另一个对象保持一致的话,这种继承方式也是非常适合的。。而且在ES5中规范化了原型式继承:Object.create();在传入一个参数时与Object()方法的行为相同,第二个参数可以通过字面量的形式对已有的属性进行复写。
寄生式继承:
function(){
var clone = Object(original);
clone.sayHi = function(){
alert('Hi');
};
return clone;
}
函数接收一个对象,在这个已有对象的基础上添加方法sayHi~!
这种方法能够加强对相关。
之前讲到了组合继承的方法~组合继承的方法是最常用的一种方法,但是它也存在自己的问题~再继承方法的时候,我们使用new调用了父类的构造函数,而子类构造函数中的借用构造函数又调用了一遍父类的函数。一共有两次父类构造函数的调用:
function Baba(name){
this.name = name;
this.colors = ["red","blue","black"]
}
Baba.prototype.sayName = function(){ alert(this.name) }
function erzi(name,age){
Baba.call(this,name);
this.age = age;
}
Erzi.prototype = new Baba();
Erzi.prototype.constructor = erzi;
Erzi.prototype.sayAge = function(){ alert(this.age); };
我们在第一次调用爸爸时(原型继承时),儿子的原型会产生两个属性,名字和颜色,第二次调用时(调用儿子构造函数时),会再次创建实例属性名字和颜色,这两个属性会把原型中两个同名属性屏蔽掉。儿子的原型得到了2组同样的属性。为了解决这个问题,我们可以使用寄生组合式继承的方法:
function jishengzuhejicheng(Erzi,Baba){
var prototype = Object(Baba.prototype);
prototype.constructor = Erzi;
Erzi.prototype = prototype
}
使用原型链继承方法的步骤我们可以替换为:
zuhejishengjicheng(Erzi,Baba);
Erzi.prototype.sayAge = function(){
alert(this.age)
}
其实就是前复制父级的原型来进行继承,以防在继承过程中,父级属性的重写。。
最后介绍的这种方法,是引用类型最理想的继承范式啦!
结语
最近在拉钩投了简历了。。然而不出一天就有一个被标记为不合适。。Orz。下一篇写一写关于HTTP和AJAX,还有关于前端优化的内容吧~面试的时候应该会提到吧~(立个FLAG:我要学好React,我要自己写一个CSS3D动画的NPM模块!!!)