**JavaScript创建对象模式:
- 对象字面量
- 工厂模式
- 构造函数模式
- 原型模式
- 结合构造函数和原型模式
- 原型动态模式
**
面向对象的语言大都有一个类的概念,通过类可以创建多个具有相同方法和属性的对象。虽然从技术上讲,javascript是一门面向对象的语言,但是javascript没有类的概念,一切都是对象。任意一个对象都是某种引用类型的实例,都是通过已有的引用类型创建;引用类型可以是原生的,也可以是自定义的。
1、对象字面量
<code class="hljs javascript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">var</span> person = {
name : <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'Nicholas'</span>;
age : <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'22'</span>;
job :<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"software Engineer"</span>
sayName: <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">function</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">()</span> {</span>
alter(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>.name);
}
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>
例子中创建一个名为person的对象,并为它添加了三个属性(name,age,job)和一个方法(sayName()),其中,sayName()方法用于显示this.name(被解析为person.name)的值。
对象字面量可以用来创建单个对象,但这个方法有个明显的缺点:使用同一个接口创建很多对象,会产生大量重复的代码。
2、工厂模式
工厂模式是软件工程领域中一种广为人知的设计模式,工厂模式抽象了创建具体对象的过程,用函数来封装以特定的接口创建对象的细节。
function createPerson(name,age,job){
var o = new object{};
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){
alert(this.name);
};
return o;
}
var person1=creatPerson("Nicholas",22,"software Engineer");
var person2=creatPerson("Greg",24,"student");
函数creatPerson{}能够根据接受的参数构建一个包含所有必要信息的Person对象。可以无数次的调用这个函数,每次都会返回一个包含三个属性一个方法的对象。
工厂模型虽然解决了创建多个相似对象的问题,却没有解决对象识别的问题(即怎么知道一个对象的类型)。
3、构造函数模式
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
alert(this.name);
}
}
var person1 = new Person("Nicholas",22,"software Engineer");
var person2 = new Person("Greg",24,"student");
person1.sayName();
person2.sayName();
与工厂模式不同的是
- 没有显示的创建对象
- 直接将属性和方法赋给了this对象
- 没有return语句
创建Person的新实例,必须使用new操作符。调用构造函数的4个步骤:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(this指向了这个新对象)
- 执行构造函数中的代码
- 返回新对象
这个例子中创建的所有对象既是Object的实例,也是Person实例。可以通过instanceof操作符验证。
alert(person1 instanceof Object);
alert(person1 instanceof Person);
构造函数模式也有自己的问题,实际上,sayName方法在每个实例上都会被重新创建一次,需要注意的是,通过实例化创建的方法并不相等,以下代码可以证明
alert(person1.sayName == person2.sayName);//false
可以将方法移到构造器的外部作为全局函数来解决这个问题。
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
}
function sayName() {
alert(this.name);
}
在全局下创建的全局函数实际上只能被经由Person创建的实例调用,这就有点名不副实了;如果对象需要定义很对方法,那么就要定义很多个全局函数,缺少封装性。
4、原型模式
JavaScript中创建的每个函数都有一个prototype(原型)属性,它是一个指针,指向一个对象,包含了可以由特定类型的所有实例共享的属性和方法(让所有的对象实例共享它的属性和方法)
function Person() {}
Person.prototype.name ="Nicholas"
Person.prototype.age = 22
Person.prototype.job = "software Engineer"
Person.prototype.sayName(){
alert(this.name)
}
var person1 = new Person()
person1.sayName()
alert(person1.sayName == person2.sayName)
以上代码做了这几件事情:
function Person() {}
Person.prototype.name ="Nicholas"
Person.prototype.age = 22
Person.prototype.job = "software Engineer"
Person.prototype.sayName(){
alert(this.name)
}
var person1 = new Person()
var person2 = new Person()
person1.name="Greg"
alert(person1.name) //Greg 来自实例
alert(person2.name) //Nicholas 来自原型
使用delete操作符可以完全删除实例属性
delete person1.name;
alert(person1.name)
使用hasOwnProperty()方法可以检测一个属性是存在于实例还是原型中
function Person() {}
Person.prototype.name ="Nicholas";
Person.prototype.age = 22;
Person.prototype.job = "software Engineer";
Person.prototype.sayName(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
alert(person1,hasOwnProperty("name"));
person1.name="Greg"
alert(person1.name)
alert(person1,hasOwnProperty("name"));
alert(person2.name)
alert(person2,hasOwnProperty("name"));
delete person1.name;
alert(person1.name)
alert(person1,hasOwnProperty("name"));
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
下图展示了在不同情况下实例与原型之间的关系
简单的原型语法
function Person() {}
Person.prototype={
name :"Nicholas",
age : 22,
job : "software Engineer",
sayName:function(){
alert(this.name);
}
};
在上面的代码中constructor属性不再指向Person了,通过constructor无法确定对象的类型了。可以像下面这样特意将他设置回适当的值
function Person() {}
Person.prototype={
constructor:Person,
name :"Nicholas",
age : 22,
job : "software Engineer",
sayName:function(){
alert(this.name);
}
};
重设constructor属性会导致它的[[Enumerable]]特性被设置为true,默认情况,原生的constructor属性是不可枚举的,可以使用Object.defineProperty()方法来改变
Object.defineProperty(Person.prototype,"constructor",{
enumerable:false,
value:Person
});
原型中查找值的过程是一次搜索,原型对象所做的任何修改都能从实例上立即反应出来
var friend=new Person();
Person.prototype.sayHi=function()
friend,sayHi();
person实例是在添加新方法之前创建的,但仍可以访问新添加的方法,原因是实例与原型之间的松散连接关系
重写原型对象后的情况
function Person() {}
var friend=new Person();
Person.prototype={
name :"Nicholas",
age : 22,
job : "software Engineer",
sayName:function(){
alert(this.name);
}
};
friend.sayName();
调用friend.sayName()时发生错误的原因是,friend指向的原型中不包含以该字段命名的属性,如下图。
原型对象的问题
原型对象省略了为构造函数传递初始化参数这一环节,所有势力在默认情况下都取得相同的属性值。原型模型最大的问题是有其共享本性所导致的。当原型模型包含引用类型的属性来说,问题就比较严重了。来看下面的例子。
function Person() {}
Person.prototype={
constructor:Person,
name :"Nicholas",
age : 22,
job : "software Engineer",
friends:["Shelby","Court"],
sayName:function(){
alert(this.name);
}
};
var person1=new Person();
var person2=new Person();
person1.friend.push("Van");
alert(person1.friends);
alert(person2.friends);
alert(person1.friends==person2.friends);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
5、组合使用构造函数模式和原型模式
组合使用构造函数模式和原型模式中,构造函数用于定义实例属性,原型模型用于定义方法和共享的属性。这样每个实例都会有自己的一份实例属性的副本,同时也可以共享对方法的引用,最大限度的节省了内存。
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.friends=["Shelby","Court"];
}
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
}
var person1=new Person("Nicholas",22,"software Engineer");
var person2 = new Person("Greg",24,"student");
person1.friend.push("Van");
alert(person1.friends);
alert(person2.friends);
alert(person1.friends==person2.friends);
alert(person1.sayName==person2.sayName);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
6、动态原型模式
原型动态模式将需要的所有信息都封装到构造函数中,通过if语句判断原型中的某个属性是否存在,若不存在(在第一次调用这个构造函数的时候),执行if语句内部的原型初始化代码。
function Person(name,age) {
this.name = name;
this.age = age;
this.job =job;
if(typeof this.sayName != 'function') {
Person.prototype.sayName = function() {
alert(this.name);
};
}
}
var friend = new Person('Nicholas','22','Software Engineer');
var person2 = new Person('amy','21');