面向对象程序设计
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();
var person2 = new Person(); person2.sayName();
|
使用这种方法,新对象的属性和方法都是所有实例共享的。
原型对象
当创建一个新函数时,就会根据一组特定的规则,为该函数创建一个prototype属性。
prototype属性指向函数的原型对象 所有的原型对象会自动获得一个constructor属性
constructor属性包含一个指向prototype属性所在函数的指针。
例如:Person.prototype.constructor指向Person。
创建了自定义的构造函数吼,其原型对象默认只会取得constructor属性。而其他的方法都是由Object继承而来的。
当调用构造函数创建一个新实例后,该**实例
** 的内部会有一个指针,指向构造函数的原型对象。
开发中可以使用isPrototypeOf()方法,来确定对象间是否存在Prototype的关系。
1
| alert(Person.prototype.isPrototypeOf(person1));
|
在实例中,可以重写原型中的值:
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();
var person2 = new Person(); person2.name = "Greg"; person2.sayName();
|
更简单的原型语法:
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"));
|
原型对象的问题
原型模式,省略了为构造函数传递初始化参数的环节。所以,所有实例在默认情况下都会取得相同的属性值。
另外,原型中的所有属性会被所有实例共享。对于引用类型值的属性来说,是很蛋疼的……
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); alert(person2.friends);
|
如上可见,所有实例共享的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); alert(person2.friends);
|
这个一个用来定义引用类型的默认模式。
动态原型模式
动态原型模式,把所有信息都封装在构造函数总,而通过构造函数中初始化原型,又保持了同时使用构造函数和原型的有点。
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();
|
这个例子中,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。