ps:前端渣第一次写博客,排版什么的还不清楚,后续会慢慢改进。
时隔两个月再次温习前端,发现很多东西都忘了,昨天刚看到constructor和prototype一脸懵逼,回去查资料和看书,对对象的理解又比从前啃书的时候更加深入。本文仅对JavaScript创建对象的各种模式进行辅助性的理解,有错误的地方欢迎指正。
创建对象
字面量对象
var person = { name:"myName", age:"18", sayName:function(){ alert(this.name); } }
缺点:每次创建一个对象都需要写这么一段代码,而不同的地方只有几个数据,代码得不到重用。
工厂模式
<span style="white-space:pre"> </span>function Person(name,age){ <span style="white-space:pre"> </span>var o = new Object(); <span style="white-space:pre"> </span>o.name = name; <span style="white-space:pre"> </span>o.age = age; <span style="white-space:pre"> </span>o.sayName = function(){ <span style="white-space:pre"> </span> alert(this.name); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>return o; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>var p1 = Person("myName",21);
<pre name="code" class="javascript"> <span style="white-space:pre"> </span>alert(p1 instanceof Object);<span> </span>//true
<span style="white-space:pre"> </span>alert(p1 instanceof Person);<span> </span>//false这样解决了代码得不到重用的问题,但是书本又指出了一个新的缺点:没有解决对象识别问题。
这句话什么意思呢,以前看书的时候并没有仔细区分它们的差异。查了资料:通过工厂模式新创建的对象没有办法知道这个对象是什么类型的。我们以Person作为一个类来创建对象,但是到最后,这个对象的类并不是Person,而是Object。
看看代码:Person只是一个普通的函数,也被普通地调用。在Person函数中创建了一个Object对象并对其赋予属性和方法,将此对象返回。至始至终操作的都是Object对象,所以我们创建出来的对象自然就跟Person没什么关系,Person只是一个被执行的函数。
构造函数模式
<span style="white-space:pre"> </span>function Person(name,age){ <span style="white-space:pre"> </span> this.name = name; <span style="white-space:pre"> </span> this.age = age; <span style="white-space:pre"> </span> this.sayName = function(){ <span style="white-space:pre"> </span> alert(this.name); <span style="white-space:pre"> </span> } <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>var p1 =new Person("myName",21); <span style="white-space:pre"> </span>alert(p1 instanceof Object);<span style="white-space:pre"> </span>//true <pre name="code" class="javascript"> <span> </span>alert(p1 instanceof Person);<span style="white-space:pre"> </span>//true此时可以发现通过构造函数模式创建的对象,可以成功识别它的类型(Person类型)。
1.new Person(); //创建一个Person对象,而不是Object对象
2.将构造函数的作用域赋给新的Person对象(this指向新对象)
3.执行Person函数中的代码(赋值)
4.返回新对象
我们new的是Person对象,而Person类型继承于Object类型,所以代码中的p1对象既是Object类型的对象,同时也是Person类型的对象。
看着好像很完美?呵呵,书本又提出了一个问题:使用构造函数的主要问题,就是每个方法都要再每个实例上重新创建一遍(注意红色字体)。
这又是什么意思呢?专研一下:我们的构造函数中,有sayName这个方法的声明,声明这个对象的sayName是一个方法,每次创建一个对象的时候,这个方法也是同时被创建出来。我创建两个对象,就创建了两次sayName方法,创建三个对象就创建了三次sayName方法。来看代码:
<span style="white-space:pre"> </span>function Person(name,age){ <span style="white-space:pre"> </span> this.name = name; <span style="white-space:pre"> </span> this.age = age; <span style="white-space:pre"> </span> this.sayName = function(){ <span style="white-space:pre"> </span> alert(this.name); <span style="white-space:pre"> </span> } <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>var p1 = new Person("myName",21); <span style="white-space:pre"> </span>var p2 = new Person("myName2",22); <span style="white-space:pre"> </span>alert(p1.sayName == p2.sayName);<span style="white-space:pre"> </span>//false这里的代码说明两个对象的相同方法并不是同一个对象(指向不同的内存地址)
但是,咱们的sayName方法,好像长得一模一样哦:作用都是用来弹出警告框,告诉我们这个对象的名字是什么。
既然这样,都是一种方法,那为什么要创建多次呢?这就多此一举了啊。
这就是构造函数模式创建对象的缺点。
原型模式
每个类型都有prototype(原型)属性,包含该类型的所有实例所共享的属性和方法。书上是这么说,但对于刚接触原型的人可能比较难理解。
换个表达方式:每个类型的prototype就像一个模型,带有属性和方法,只要是从这个类型new出来的对象,属性和方法都跟这个prototype一样。
来看例子比较好理解:
<pre name="code" class="javascript"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span></span>
<span style="white-space:pre"> </span>function Person(){}; <span style="white-space:pre"> </span>Person.prototype.name = "myName"; <span style="white-space:pre"> </span>Person.prototype.age = 18; <span style="white-space:pre"> </span>Person.prototype.sayName = function(){ <span style="white-space:pre"> </span> alert(this.name); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>var p1 = new Person(); <span style="white-space:pre"> </span>var p2 = new Person(); <span style="white-space:pre"> </span>alert(p1.sayName==p2.sayName); //true
此时p1和p2两个对象,无论是属性还是方法,都指向相同的地方。p1的属性与p2的同名属性就是同一个东西(内存地址都相同),同样,p1与p2的同名方法也是这样的关系。意思就是,我创建了两个Person对象,但是对象的属性和方法我只实例化了一次,不管我创建多少个对象,都只实例化一次,实例化的是Person的原型对象。
new出来的对象的属性和方法指向原型对象的同名属性和方法。当改变原型的属性和方法,则相应的对象的属性和方法也将被改变。也就是说,对象的属性和方法并不是自己本身的属性和方法,而是指向原型的属性和方法。来看个例子:
function Person(){}; Person.prototype.name = "myName"; Person.prototype.age = 18; Person.prototype.sayName = function(){ alert(this.name); } var p1 = new Person(); alert(p1.age); //18 Person.prototype.age = 20; alert(p1.age); //20p1对象在原型的属性被更改之前是18,原型的属性被更改之后变成20,其中并没有对p1对象进行操作。通过这个例子可以知道,用原型模式产生的对象的属性和方法并不属于其本身,而是指向原型的同名属性和方法。如果想让对象拥有自己的属性和方法,可以通过对对象本身进行赋值,新的属性和方法将会覆盖原型中的同名属性和方法。
原型模式的缺点:1.所有实例在默认情况下都将取得相同的属性值(这只是小问题) 2.共享的本性(最大的问题)
对于原型模式其共享的本性,对sayName这样的函数来说是好事,因为使用原型模式本来就是为了只创建一个函数。但是对于其他属性(非函数)问题就出来了:基本类型的属性没什么,问题是引用类型的属性。来看例子:
<span style="white-space:pre"> </span>function Person(){ <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span> <span style="white-space:pre"> </span> Person.prototype = { <span style="white-space:pre"> </span> constructor:Person, <span style="white-space:pre"> </span> name:"myName", <span style="white-space:pre"> </span> age:29, <span style="white-space:pre"> </span> sayName:function(){ <span style="white-space:pre"> </span> alert(this.name); <span style="white-space:pre"> </span> }, <span style="white-space:pre"> </span> friends:["a","b"] <span style="white-space:pre"> </span> } <span style="white-space:pre"> </span>var person1 = new Person(); <span style="white-space:pre"> </span>var person2 = new Person(); <span style="white-space:pre"> </span>person1.friends.push("c"); <span style="white-space:pre"> </span>alert(person1.friends);<span style="white-space:pre"> </span> //abc <span style="white-space:pre"> </span>alert(person2.friends); <span style="white-space:pre"> </span>//abc <span style="white-space:pre"> </span>alert(person1.friends===person2.friends); //truefriends属性是数组类型的,这里修改了person1的friends的属性值,修改了同一地址的引用类型的值,相应的person2的friends的指向的原型的值也就是改变之后的值了。此时如果并不希望person2的friends值被更改,prototype模式做不到。所以,对于这种引用类型的属性,不能使用prototype模式来创建对象。
组合模式
综上对所有创建对象的分析,每种都有缺点,到底怎么创建对象才最好呢?
用组合模式:构造函数模式+原型模式
为对象的非共享属性赋值,用构造函数模式,为对象的方法及共享的属性赋值,用原型方法,看看代码:
<span style="white-space:pre"> </span>function Person(name,age){ <span style="white-space:pre"> </span> this.name = name; <span style="white-space:pre"> </span> this.age = age; <span style="white-space:pre"> </span> this.friends = ["a","b"] <span style="white-space:pre"> </span>}//构造函数模式 <span style="white-space:pre"> </span>Person.prototype = { <span style="white-space:pre"> </span>constructor:Person, <span style="white-space:pre"> </span>sayName:function(){ <span style="white-space:pre"> </span> alert(this.name); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>}//原型模式 <span style="white-space:pre"> </span>var person1 = new Person("myName1",18); <span style="white-space:pre"> </span>var person2 = new Person("myName2",20); <span style="white-space:pre"> </span>person1.friends.push("c"); <span style="white-space:pre"> </span>alert(person1.friends); //abc <span style="white-space:pre"> </span>alert(person2.friends); //ab <span style="white-space:pre"> </span>alert(person1.friends===person2.friends); //false <span style="white-space:pre"> </span>alert(person1.sayName===person2.sayName); //true这种模式是目前使用最广泛及认同度最高的一种创建自定义类型的方法。
当然除了上述几种模式之外,还有各种各样的创建对象的方式,可以根据实际需要来挑选合适的方式,本人并没有非常深入的研究,在此不作叙述。
没有写博客之前对写博客并没有什么特别的感觉,觉得可有可无。但真正为了写出一篇让自己满意的博客的时候,就没有这么随便了,对于技术知识点的理解必须自己先深入理解了之后才敢写上来,知识又通过仔细推敲使之变得通俗易懂才写上来。总而言之,写博客,能让自己对知识点的理解有非常大的帮助。作为一个前端技术新人,希望自己能通过博客提升自己的实力,有错误的地方,欢迎来喷,来者不拒。
<span> </span>alert(p1 instanceof Object);
原文:http://blog.csdn.net/papo_red/article/details/51878626