ECMAScript学习笔记(六)——对象的创建

Posted by Csming on 2018-09-13

面向对象程序设计

JavaScript高级程序设计的第六章。

创建ECMAScript对象的一百种方法。

创建对象的一百种方法。

除了之前记录过的直接new一个Object然后添加对象的方法,还有对象字面量的方法。ECMAScript还有一百种方法创建一个对象。嗯。

使用Object构造函数和对象字面量的方法创建对象,会产生很多重复代码嗯,并且无法知道对象的类型。

工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
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");

工厂模式能够解决Object构造函数和对象字面量会产生的大量重复代码的问题,但是却无法解决对象识别的问题。我们无法知道一个对象的类型。

构造函数模式

1
2
3
4
5
6
7
8
9
10
11
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");

Person()中的代码,没有显式的创建对象,直接将属性和方法赋给了this对象,也没有return语句。

按照惯例,构造函数始终都应该以一个大写字母开头。

要创建Person的实例,就需要使用new操作符。以这种方式调用构造函数会经历四个步骤:

(1) 创建一个新对象
(2) 将构造函数的作用域赋给新对象.this指向了这个新对象
(3) 执行构造函数中的代码
(4) 返回新对象

person1和person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor属性,这个属性指向Person。

1
2
3
person1.contructor == Person;
person1 instanceof object;
person1 instanceof Person;

创建自定义的构造函数,意味着将来可以将它的实例标识为一种特定的类型。

对于这种构建方法,有以下几个特点。

1.将构造函数当做函数

构造函数和普通的函数是一样的。只不过用new操作符来调用的时候,就会变成一个构造函数了。

2.构造函数的问题

构造函数,会使类型的每个方法都要在每个实例上重新创建一遍。(如上述示例中的sayName方法)

所以,每个实例所拥有的方法,是不同的实例。

于是,可以这样创建一个对象:

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

function sayName() {
alert(this.name);
}

但是这样子,就在全局作用域中创建了一个,只有Person对象才能调用的方法。

十分恶心。于是,这些问题是可以通过原型模式处理掉的嗯。

原型模式

我们创建的每个函数都有一个prototype属性,用于指向一个对象。这个对象包含特定类型的所有实例共享的属性和方法。

可以理解为,prototype就是通过调用构造函数创建的那个对象实例的原型对象。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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(); // "Nicholas"

var person2 = new Person();
person2.sayName(); // "Nicholas"

使用这种方法,新对象的属性和方法都是所有实例共享的。

原型对象

当创建一个新函数时,就会根据一组特定的规则,为该函数创建一个prototype属性。

prototype属性指向函数的原型对象 所有的原型对象会自动获得一个constructor属性

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

例如:Person.prototype.constructor指向Person。

创建了自定义的构造函数吼,其原型对象默认只会取得constructor属性。而其他的方法都是由Object继承而来的。

当调用构造函数创建一个新实例后,该**实例** 的内部会有一个指针,指向构造函数的原型对象。

开发中可以使用isPrototypeOf()方法,来确定对象间是否存在Prototype的关系。

1
alert(Person.prototype.isPrototypeOf(person1)); // true

在实例中,可以重写原型中的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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(); // "Nicholas"

var person2 = new Person();
person2.name = "Greg";
person2.sayName(); // "Greg"

更简单的原型语法:

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

}

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

上述代码,将Person.prototype设置为等于一个以对像字面量形式创建的新对象。但是这样做,就完全重写了prototype,于是constructor属性就不再指向Person了。这时候的constructor指向Object.

原型的动态性

在原型中查找值的过程是一次搜索。

我们队原型对象所做的任何修改都能够立即从实例上反映出来。即时是创建实例后修改原型。这是由于实例与原型之间的松散连接关系导致的。

1
2
3
4
5
6
7
var friend = new Person();

Person.prototype.sayHi = function() {
alert("hi");
};

friend.sayHi();

但,如果是直接重写整个原型对象,就不一样了。

1
2
3
4
5
6
7
8
9
var friend = new Person();

Person.prototype = {
constructor: Person,
name: "Nicholas",
...
};

friend.sayHi(); // error

原生对象的原型

所有的原生引用类型,都是采用原型模式创建的。原生引用类型都在其构造函数的原型上定义了方法。

通过原生对象的原型,不仅可以取得所有默认方法的引用,而且可以定义新方法。可以随时为原生对象添加方法嗯。

1
2
3
4
5
6
String.prototype.startsWith = function(text) {
return this.indexOf(text) == 0;
}

var msg = "Hello world";
alert(msg.startWith("Hello")); // true

原型对象的问题

原型模式,省略了为构造函数传递初始化参数的环节。所以,所有实例在默认情况下都会取得相同的属性值。

另外,原型中的所有属性会被所有实例共享。对于引用类型值的属性来说,是很蛋疼的……

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

}

Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
friends: ["Shelby", "Court"],
sayName: function () {
alert(this.name);
}
};

var person1 = new Person();
var person2 = new Person();

person1.friends.push("Van");

alert(person1.friends); // Shalby, Court, Van
alert(person2.friends); // Shalby, Court, Van

如上可见,所有实例共享的friends指向的是同一个引用值。

组合使用构造器模式和原型模式

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

person1.friends.push("Van");

alert(person1.friends); // Shalby, Court, Van
alert(person2.friends); // Shalby, Court

这个一个用来定义引用类型的默认模式。

动态原型模式

动态原型模式,把所有信息都封装在构造函数总,而通过构造函数中初始化原型,又保持了同时使用构造函数和原型的有点。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name, age, job) {
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", 29, "Software Engineer");
friend.sayName();

以上代码,只在原型下的sayName()方法不存在的情况下,才会将其添加到原型中。

寄生(parasitic)构造函数模式

创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后返回新创建的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
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 friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); // Nicholas

这个例子中,Person函数创建了一个新对象,然后用相应的属性和方法初始化后,返回了这个对象。

除了使用new操作符,并且把包装函数叫做构造函数之外,这个模式跟工厂模式一模一样。

这个模式能在特殊情况下为对象创建构造函数。当我们想创建一个具有额外方法的特殊数组,由于不能修改Array构造函数,所以可以使用这个模式:

1
2
3
4
5
6
7
8
function SpecialArray() {
var values = new Array();
values.push.apply(values, arguments);
values.toPipedString = function() {
return this.join("|")
}
return values;
}

稳妥构造函数模式

稳妥对象 就是没有公共属性,而且其方法也不引用this的对象。稳妥对象适合一些安全的环境,或者放置数据被其他应用程序改动的时候使用。

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

在这种方法下,除了sayName()以外,没有其他方法可以访问name。