JavaScript 笔记(7)- 创建对象1(工厂模式、构造函数模式、原型模式)

一、工厂模式

工厂模式抽象了创建对象的具体过程。

调用createPerson()可以根据参数创建对象,多次调用,返回多个相似对象。但是这个模式不能识别对象类型。

二、构造函数模式

ECMAScript 中的构造函数可以用来创建特定类型对象。像Object,Array这样的原生构造函数,在运行时会自动出现在执行环境中。也可以创建自定义的构造函数,从而自定义对象的属性和方法。
改写上面的工厂模式创建对象:

上面的构造函数模式创建新对象,必须使用 new 操作符,以上面的方式调用构造函数来创建对象,会经过一下几个步骤:
a. 创建一个新对象
b. 将构造函数的作用域赋值给新对象(this则指向新对象)
c. 执行构造函数中的代码(为新对象添加属性)
d. 返回新对象
这个例子的最后 person1 和 person2 分别保存着 Person 的一个不同的实例。

1. constructor 属性

person1和person2这两个对象都有一个 constructor属性,该属性指向 Person。
故:

2.检测对象类型 instanceof

person1和person2这两个对象既是Object的实例,也是Person的实例。

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

3.将构造函数当做函数

构造函数与其他函数的唯一区别在于调用方式不同。任何函数只要通过new 操作符调用,也可以作为构造函数。
Person() 函数可以通过以下方法来调用:

4.构造函数的问题

每个实例的方法都包含一个新的 Function 实例,这样会导致不同的作用域链和标识符解析。这个问题可以通过原型模式来解决。

三、原型模式

我们创建的每个函数都有一个 prototype 属性,这个属性是一个指针,指向一个对象。这个对象是:通过调用构造函数来创建的实例对象的原型对象
使用原型对象的好处:让所有实例对象可以共享原型对象上包含的属性和方法,不必在构造函数中定义对象实例的信息。
例如:

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。
例如:

3.获取对象的原型对象:Object.getPrototypeOf()

Object.getPrototypeOf()方法,接受一个参数,即要获取[Prototype]的对象。

4.访问对象属性时的内部搜索

每当读取对象的属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从实例对象的本身开始,如果实例对象具有给定名字的属性,则返回属性值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找给定名字的属性,如果原型对象中找到,则返回属性值;如果没有继续向上搜索,直到Object.prototype,都没有则返回undefined。
所以不能通过对象实例重写原型中的的属性。如果为实例添加了一个与原型对象中同名的属性,实际则是在对象中创建了该属性,则将不能再访问到原型对象中的同名属性。
例如:

此时要想再访问原型中的 name 属性,可以使用 delete 操作符删除 person1.name。

5.判断对象属性是属于原型还是本身

使用 hasOwnProperty() 方法可以检测一个属性是存在于实例中,还是存在于原型对象中,只有在给定属性存在于对象实例中时才返回true。

6.原型与 in 操作符

有两种方式使用 in 操作符:单独使用和在 for-in 循环中使用。

a.单独使用

单独使用时,in 操作符可以判断给定属性是否能够访问,无论是实例中还是原型中,可以访问则返回 true。
那么如果 in 操作符返回 true,hasOwnProperty()返回false,则能确定属性存在于原型中。

b. for-in 循环

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

7.获取对象属性
a.获取实例自身可枚举属性

Object.keys()方法,接受一个对象作为参数,一个包含所有可枚举的实例属性组成的字符串数组。

b.获取对象所有属性包括不可枚举

Object.getOwnPropertyNames()方法,接受一个对象作为参数,返回参数对象的所有自身属性,包括不可枚举的属性。

上例结果中包含了不可枚举的 constructor 属性。

8.更简单的原型语法

上面原型模式可以改写为:

上面代码中将 Person.prototype 设置为了一个新对象,语法简单了,但是此时的 constructor 属性将不再指向 Person 了。
因为默认情况下:每创建一个函数,就会同时创建 prototype 对象,该对象自动获得 constructor 属性,而 constructor 指向构造函数。 上面的代码重写了 prototype对象,因此 constructor 属性也变成了新对象的 constructor 属性,指向Object()构造函数。

若要 constructor 有正确的指向,则可以在改写时手动设置:

注:这样改写 constructor 属性会导致 Enumerable 特性被设置为 true。默认情况,原生的constructor属性是不可枚举,可以使用 Object.defineProperty()方法改写。

9.原型的动态性

由于在原型中查找值的过程是一次搜索,因此对原型对象所做的修改都会立即从实例中反应出来,即使是先创建了实例,后修改原型也是如此。
例如:

但是,如果是重写整个原型对象就非如此了。因为调用构造函数创建实例时,会为实例添加一个[Prototype]指针,指向最初的原型对象。而重写整个原型对象就等于切断了实例与现有原型之间的联系。

10.原生对象的原型

原型模式的重要性不仅体现在创建自定义类型方面,原生的引用类型也是采用这种模式创建的。所有的原生引用类型都在其构造函数的原型上定义了方法。
例如:Array.prototype 中有sort(),slice() 等方法,String.prototype 中有 substring() 方法。
对于原生对象的原型也可以修改它,例如

但是,最好不要修改原生对象的原型。

评论 ( 0 )
最新评论
暂无评论

赶紧努力消灭 0 回复