一, 工厂模式
工厂模式抽象了创建对象的具体过程.
- 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');
调用 createPerson()可以根据参数创建对象, 多次调用, 返回多个相似对象. 但是这个模式不能识别对象类型.
二, 构造函数模式
ECMAScript 中的构造函数可以用来创建特定类型对象. 像 Object,Array 这样的原生构造函数, 在运行时会自动出现在执行环境中. 也可以创建自定义的构造函数, 从而自定义对象的属性和方法.
改写上面的工厂模式创建对象:
- 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');
上面的构造函数模式创建新对象, 必须使用 new 操作符, 以上面的方式调用构造函数来创建对象, 会经过一下几个步骤:
a. 创建一个新对象
b. 将构造函数的作用域赋值给新对象(this 则指向新对象)
c. 执行构造函数中的代码(为新对象添加属性)
d. 返回新对象
这个例子的最后 person1 和 person2 分别保存着 Person 的一个不同的实例.
1. constructor 属性
person1 和 person2 这两个对象都有一个 constructor 属性, 该属性指向 Person.
故:
- person1.constructor ==Person;//true
- person2.constructor ==Person;//true
2. 检测对象类型 instanceof
person1 和 person2 这两个对象既是 Object 的实例, 也是 Person 的实例.
- person1 instanceof Object;//true
- person1 instanceof Person;//true
- person2 instanceof Object;//true
- person2 instanceof Person;//true
创建自定义的构造函数可以将它的实例标识为一种特定的类型.
3. 将构造函数当做函数
构造函数与其他函数的唯一区别在于调用方式不同. 任何函数只要通过 new 操作符调用, 也可以作为构造函数.
Person() 函数可以通过以下方法来调用:
- // 当做构造函数调用
- var person1 = new Person('Greg', 27, 'Doctor');
- person.sayName();//'Greg'
- // 作为普通函数调用
- Person('Greg', 27, 'Doctor');// 结果是为 window 添加了 name,age 属性和 sayName 方法
- window.sayName();//'Greg'
- // 在另一个作用域中调用
- var o = new Object();
- Person.call(o,'Greg',25, 'Doctor');
- o.sayName();//'Greg'
4. 构造函数的问题
每个实例的方法都包含一个新的 Function 实例, 这样会导致不同的作用域链和标识符解析. 这个问题可以通过原型模式来解决.
person1.sayName == person2.sayName;//false
三, 原型模式
我们创建的每个函数都有一个 prototype 属性, 这个属性是一个指针, 指向一个对象. 这个对象是: 通过调用构造函数来创建的实例对象的原型对象.
使用原型对象的好处: 让所有实例对象可以共享原型对象上包含的属性和方法, 不必在构造函数中定义对象实例的信息.
例如:
- function Person() {
- }
- Person.prototype.name = 'Greg',
- Person.prototype.age = 27;
- Person.prototype.job = 'Doctor';
- Person.prototype.sayName = function () {
- alert(this.name);
- };
- var person1 = new Person();
- person1.sayName();//'Greg'
- var person2 = new Person();
- person2.sayName();//'Greg'
- person1.sayName == person2.syName;//true
1. 理解原型对象
无论什么时候, 只要创建了一个新函数, 就会根据一组特定规则为该函数创建一个 prototype 属性, 这个属性指向函数的原型对象.
默认情况下, 创建了自定义构造函数, 其原型对象默认只会取得 constructor 属性, 这个属性指向 prototype 属性所在的函数.
例如: Person.prototype.constructor == Person;//true
当通过构造函数创建一个新实例后, 该实例内容包含一个内部属性, 该属性是一个指针, 叫 [Prototype], 指向该实例的构造函数的原型对象, 注意这个连接存在于实例与构造函数的原型对象之间, 不是存在于实例与构造函数之间.
例如:
上图展示了 Person 构造函数, Person 的原型属性以及 Person 的两个实例之间的关系. Person.prototype 指向了原型对象, 而 Person.prototype.constructor 又指回了 Person. 原型对象中包含了 constructor 属性, 以及后来添加的其他属性和方法. Person 的两个实例 person1 和 person2 都包含一个内部属性[Prototype], 该属性指向了 Person.prototype, 与构造函数没有直接的关系. person1 和 person2 的属性方法都来自原型对象.
2. 判断对象之间的关系 isPrototypeOf()
通过 isPrototypeOf() 方法来确定对象之间是否存在关系. 如果 [Prototype] 指向调用 isPrototypeOf() 方法的对象(Person.prototype), 则这个方法返回 true.
例如:
- Person.prototype.isPrototypeOf(person1);//true
- Person.prototype.isPrototypeOf(person2);//true
3. 获取对象的原型对象: Object.getPrototypeOf()
Object.getPrototypeOf()方法, 接受一个参数, 即要获取 [Prototype] 的对象.
- function Person() {}
- Person.prototype.name = 'Greg',
- Person.prototype.age = 27;
- Person.prototype.job = 'Doctor';
- Person.prototype.sayName = function () {
- alert(this.name);
- };
- var person1 = new Person();
- Object.getPrototypeOf(person1) == Person.prototype;//true
- Object.getPrototypeOf(person1).name;// 'Greg'
4. 访问对象属性时的内部搜索
每当读取对象的属性时, 都会执行一次搜索, 目标是具有给定名字的属性. 搜索首先从实例对象的本身开始, 如果实例对象具有给定名字的属性, 则返回属性值; 如果没有找到, 则继续搜索指针指向的原型对象, 在原型对象中查找给定名字的属性, 如果原型对象中找到, 则返回属性值; 如果没有继续向上搜索, 直到 Object.prototype, 都没有则返回 undefined.
所以不能通过对象实例重写原型中的的属性. 如果为实例添加了一个与原型对象中同名的属性, 实际则是在对象中创建了该属性, 则将不能再访问到原型对象中的同名属性.
例如:
- function Person() {}
- Person.prototype.name = 'Greg',
- Person.prototype.age = 27;
- Person.prototype.job = 'Doctor';
- Person.prototype.sayName = function () {
- alert(this.name);
- };
- var person1 = new Person();
- var person2 = new Person();
- person1.name = 'Nicholas';
- alert(person1.name);//'Nicholas'- 来自实例本身
- alert(person2.name);//'Greg'- 来自原型
此时要想再访问原型中的 name 属性, 可以使用 delete 操作符删除 person1.name.
5. 判断对象属性是属于原型还是本身
使用 hasOwnProperty() 方法可以检测一个属性是存在于实例中, 还是存在于原型对象中, 只有在给定属性存在于对象实例中时才返回 true.
- function Person() {}
- Person.prototype.name = 'Greg',
- Person.prototype.age = 27;
- Person.prototype.job = 'Doctor';
- Person.prototype.sayName = function () {
- alert(this.name);
- };
- var person1 = new Person();
- var person2 = new Person();
- person1.name = 'Nicholas';
- person1.hasOwnProperty('name');//true
- person2.hasOwnProperty('name');//false
6. 原型与 in 操作符
有两种方式使用 in 操作符: 单独使用和在 for-in 循环中使用.
a. 单独使用
单独使用时, in 操作符可以判断给定属性是否能够访问, 无论是实例中还是原型中, 可以访问则返回 true.
那么如果 in 操作符返回 true,hasOwnProperty()返回 false, 则能确定属性存在于原型中.
b. for-in 循环
使用 for-in 循环时, 返回的是所有能够通过对象访问的, 可枚举的属性. 其中既包括存在于实例中的属性, 也包括存在于原型中的属性.
7. 获取对象属性
a. 获取实例自身可枚举属性
Object.keys()方法, 接受一个对象作为参数, 一个包含所有可枚举的实例属性组成的字符串数组.
- Object.keys(person1);//["name"]
- Object.keys(Person.prototype);// ["name", "age", "job", "sayName"]
b. 获取对象所有属性包括不可枚举
Object.getOwnPropertyNames()方法, 接受一个对象作为参数, 返回参数对象的所有自身属性, 包括不可枚举的属性.
Object.getOwnPropertyNames(Person.prototype);// ["constructor", "name", "age", "job", "sayName"]
上例结果中包含了不可枚举的 constructor 属性.
8. 更简单的原型语法
上面原型模式可以改写为:
- function Person() {}
- Person.prototype = {
- name: 'Nicholas',
- age: 29,
- job: 'Software Enginner',
- sayName: function () {
- alert(this.name);
- }
- }
- var person1 = new Person();
上面代码中将 Person.prototype 设置为了一个新对象, 语法简单了, 但是此时的 constructor 属性将不再指向 Person 了.
因为默认情况下: 每创建一个函数, 就会同时创建 prototype 对象, 该对象自动获得 constructor 属性, 而 constructor 指向构造函数. 上面的代码重写了 prototype 对象, 因此 constructor 属性也变成了新对象的 constructor 属性, 指向 Object()构造函数.
- function Person() {}
- Person.prototype = {
- name: 'Nicholas',
- age: 29,
- job: 'Software Enginner',
- sayName: function () {
- alert(this.name);
- }
- }
- var person1 = new Person();
- person1.constructor == Object;//true
- person1.constructor == Person;//false
若要 constructor 有正确的指向, 则可以在改写时手动设置:
- function Person() {}
- Person.prototype = {
- constructor: Person,
- name: 'Nicholas',
- age: 29,
- job: 'Software Enginner',
- sayName: function () {
- alert(this.name);
- }
- }
- var person1 = new Person();
- person1.constructor == Person;//true
注: 这样改写 constructor 属性会导致 Enumerable 特性被设置为 true. 默认情况, 原生的 constructor 属性是不可枚举, 可以使用 Object.defineProperty()方法改写.
- function Person() {}
- Person.prototype = {
- constructor: Person,
- name: 'Nicholas',
- age: 29,
- job: 'Software Enginner',
- sayName: function () {
- alert(this.name);
- }
- }
- Object.defineProperty(Person.prototype, 'constructor', {
- enumerable: false,
- value: Person
- });
- var person1 = new Person();
- person1.constructor == Person;//true
9. 原型的动态性
由于在原型中查找值的过程是一次搜索, 因此对原型对象所做的修改都会立即从实例中反应出来, 即使是先创建了实例, 后修改原型也是如此.
例如:
- function Person(){}
- var person = new Person();
- Person.prototype.name = 'Greg';
- alert(person.name);//'Greg'
但是, 如果是重写整个原型对象就非如此了. 因为调用构造函数创建实例时, 会为实例添加一个 [Prototype] 指针, 指向最初的原型对象. 而重写整个原型对象就等于切断了实例与现有原型之间的联系.
- function Person(){}
- var person = new Person();
- Person.prototype.name = 'Greg';
- alert(person.name);//'Greg'
- Person.prototype={
- name:'Tom',
- age:23
- };
- alert(person.name);//'Greg'
10. 原生对象的原型
原型模式的重要性不仅体现在创建自定义类型方面, 原生的引用类型也是采用这种模式创建的. 所有的原生引用类型都在其构造函数的原型上定义了方法.
例如: Array.prototype 中有 sort(),slice() 等方法, String.prototype 中有 substring() 方法.
对于原生对象的原型也可以修改它, 例如
- String.prototype.firstLetter = function () {
- return this.slice(0,1);
- }
- var str = 'hello world';
- str.firstLetter();//'h'
但是, 最好不要修改原生对象的原型.
来源: http://www.qdfuns.com/article/46690/ad00f64571cc282e716831dcde965ed1.html