创建对象

工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 = createPerson('Nicholas',29,'Software Engineer');
var person2 = createPerson('Greg',27,'Doctor');

person1.sayName(); //Nicholas
person2.sayName(); //Greg

解决了创建多个相似对象的问题,没有解决对象识别的问题(即怎样知道一个对象的类型)

构造函数模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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',29,'Software Engineer');
var person2 = new Person('Greg',27,'Doctor');

person1.sayName(); //Nicholas
person2.sayName(); //Greg

与工厂模式的不同之处

  • 没有显式地创建对象(即new Object)
  • 直接将属性和方法赋给了this对象
  • 没有return语句
  • 函数名大写开头

这两个对象都有一个constructor(构造函数)属性,该属性指向Person

1
2
3
alert(person1.constructor==Person);  //true
alert(person2.constructor==Person); //true
//用instanceof操作符也可以检测出person1和person2同时是Object的实例

将构造函数当做函数

任何函数,只要通过new操作符来调用,那它就可以作为构造函数;

而任何函数,如果不通过new操作符来调用,那它跟普通函数也不会有什么两样

1
2
3
4
5
6
7
8
9
10
11
12
var person = new Person('Nicholas',29,'Software Engineer');
person.sayName();//Nicholas

//作为普通函数调用
Person('Greg',27,'Doctor');
window.sayName(); //Greg ,因为this对象总指向Global对象(在浏览器中就是window对象)

//在另一个对象的作用域中调用
var o = new Object();
Person.call(o,"Kristen",25,"Nurse");
o.sayName();//Kristen

构造函数的问题

每个方法都要在每个实例上重新创建一遍

1
alert(person1.sayName==person2.sayName)//false

但这并没有必要

解决方法之一:

1
2
3
4
5
6
7
8
9
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}

将sayName函数转移到构造函数外部,在构造函数内部,将sayName属性设置成等于全局的sayName函数

存在的问题:如果需要定义很多方法,则要定义很多全局函数,丝毫没有封装性

解决方法之二:

原型模式

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法

prototype通过调用构造函数而创建那个对象的原型对象

使用原型的好处是可以让所有对象实例共享它所包含的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(){

}
Person.prototype.name = 'Nicholas';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function(){
alert(this.name);
}
var person1 = new Person();
person1.sayName();

var person2 = new Person();
person2.sayName();

alert(person1.sayName==person2.sayName) //true

理解原型

创建了一个函数,会根据一组特定的规则为该函数创建一个prototype属性

默认情况下,所有prototype属性都会自动获得一个constructor属性

这个属性包含一个指向prototype属性所在函数的指针

创建了自定义的构造函数以后,其原型属性默认只会取得constructor属性

至于其他方法,都是从Object继承而来

当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),

指向构造函数的原型属性。在很多实现中,这个内部属性的名字是__proto__

需要明确:这个连接存在于实例与构造函数的原型属性之间,而不是存在于实例与构造函数之间

图示

图示

其中的__未显示出来,person属性有省略

虽然两个实例都不包含属性和方法,但却可以调用person1.sayName(),这是通过查找对象属性的过程来实现的

1
2
3
alert(Person.prototype.isPrototypeOf(person1));//true
alert(Person.prototype.isPrototypeOf(person2));//true
//即person1\person2都有一个指向Person原型的指针

每当代码读取某个对象的某个属性时,都会执行一次搜索,目标时具有给定名字的属性

搜索从对象实例本身开始,如果在实例中找到了具有给定名字的属性,则返回值

如果没有,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性

重写值会屏蔽原型中的属性

在实例中添加一个原型中同名的属性,将会屏蔽原型中的那个值, 因为提前找到了就不会再搜索下去

但并不会修改原型中的值

可以使用delete操作符来重新访问原型中的值

hasOwnProperty

hasOwnProperty()方法可以检测一个属性是存在于实例中还是原型中

当属性是实例中时,返回true,原型中时返回false

原型与in操作符

in操作符可以单独使用,也可以在for-in循环中使用

单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Person(){

}
Person.prototype.name = 'Nicholas';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();

alert(person1.hasOwnProperty(name)); //false
alert("name" in person1); //true

person1.name = "Greg";
alert(person1.name);
alert(person1.hasOwnProerty(name));//true
alert("name in person1");//true

delete person1.name;
alert(person1.name);
alert(person1.hasOwnProerty(name));//false
alert("name in person1");//true

借助in操作符和hasOwnProperty()方法可以写出判断属性在原型中的函数

1
2
3
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name)&&(name in object)
}

在使用for-in循环时,返回的是所有能够通过对象访问的,可枚举的属性,既包括存在于实例中的属性,也包括存在于原型中的属性

更简单的原型语法

每添加一个属性和方法都要敲Person.prototype过于麻烦,可以用一个包含所有属性和方法的字面量来重写整个原型对象

1
2
3
4
5
6
7
8
9
10
11
function Person(){

}
Person.prototype = {
name:'Nicholas',
age:29,
job:'Software Engineer',
sayName:function(){
alert(this.name);
}
}

弊端:constructor属性不再指向Person了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var person = new Person();
alert(person.instanceOf Object) //true
alert(person.instanceOf Person) //true
alert(person.constructor == Person) //false
alert(person.constructoe == Object) //true
//如果constructor的值很重要,可以设置回适当的值
function Person(){

}
Person.prototype = {
constructor:Person,
name:'Nicholas',
age:29,
job:'Software Engineer',
sayName:function(){
alert(this.name);
}
}

原型的动态性

即使先创建实例,再修改原型,实例依然能够使用到原型改变的东西

但若是重写整个原型对象则会断开构造函数与最初原型之间的联系(实例中的指针仅指向原型,而不指向构造函数)

原型对象的问题

首先,省略了为构造函数传递初始化参数这一环节,结果所有实例再默认情况下取得相同的值

最大的问题是由于共享导致的

对于包含引用类型值的属性来说,如果一个实例在数组中添加了值,在原型中也会相应造成改变导致所有实例都发生改变

组合使用构造函数模式和原型模式

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.course = ['JS','CSS'];
}
Person.prototype = {
constructor: Person,
sayName:function(){
alert(this.name);
}
}

实例属性都是在构造函数中定义的,而由所有实例共享的属性constructor和sayName方法则是在原型中定义

修改一方的数组并不会影响另一个实例,因为它们分别引用不同的数组

动态原型模式

将所有信息都封装在构造函数中,根据情况来决定是否初始化原型

寄生构造函数模式

在上述几种都不适用的情况下,可以使用寄生构造函数模式

1
2
3
4
5
6
7
8
9
10
11
12
function Person(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 person = new Person('Nicholas',29,'SoftWare Engineer');
person.sayName();

这个模式可以在特殊的情况下用来为对象创建构造函数,例如拥有特殊方法的数组

1
2
3
4
5
6
7
8
9
10
function SpecialArray(){
var values = new Array();
values.push.apply(values,arguments);
values.toPipedString = function(){
return this.join("|");
}
return values;
}
var colors = new SpecialArray("red","green","yellow");
alert(colors.toPipedString());//"red|green|yellow"

这个模式返回的对象与构造函数或者构造函数的原型属性之间没有关系,不能依赖instanceof操作符来确定对象类型

稳妥构造函数模式

新创建对象的实例方法不引用this

不适用new操作符调用构造函数

1
2
3
4
5
6
7
function Person(name,age,job){
var o = new Object();
o.sayName = function(){
alert(name);
}
return o;
}

创建对象
https://blog-theta-ten.vercel.app/2021/08/22/创建对象/
作者
Chen
发布于
2021年8月22日
许可协议