第一百一十七篇: JavaScript 工厂模式和原型模式

好家伙,本篇为《JS高级程序设计》第八章“对象、类与面向对象编程”学习笔记

 

1.工厂模式

工厂模式是另外一种关注对象创建概念的创建模式。

它的领域中同其它模式的不同之处在于它并 没有明确要求我们使用一个构造器

取而代之,一个工厂能提供一个创建对象的公共接口,我们可以在其中指定我们希望被创建的工厂对象的类型。

   function  createPerson(name,age,job){
let person
= new Object();
person.name
= name;
person.age
= age;
person.job
= job;
person.getName
= function (){
console.log(this.name);
}
return person;
}
let person_1
= createPerson( " panghu " , " 20 " , " student " )
person_1.getName();
console.log(person_1);

 

 

 

(看上去没什么问题,但怎么总觉得怪怪的)

  let person = new  Object(); / ...
...
...
..
/

return person;

 

 

2.构造函数模式

前面几章提到过,ECMAScript中的构造函数是用于创建特定类型对象的。

像Object和Array这样的原生构造函数,运行时可以直接在执行环境中使用。

当然也可以自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。

 

   function  Person(name, age, job) {
this.name
= name;
this.age
= age;
this.job
= job;
this.getName
= function () {
console.log(this.name);
};
}
let person_1
= new Person( " panghu " , " 20 " , " student " );
person_1.getName();
console.log(person_1);

 

 

在这个例子中,Person()构造函数代替了 createPerson()工厂函数。

实际上,Person()内部的代码跟createPerson()基本是一样的,只是有如下区别。
□没有显式地创建对象。
□属性和方法直接赋值给了this。
□没有return。
另外,要注意函数名Person的首字母大写了。

按照惯例,构造函数名称的首字母都是要大写的,非构造函数则以小写字母开头。

这是从面向对象编程语言那里借鉴的( 是的,非常好的借鉴 ),有助于在ECMAScript中区分构造函数和普通函数。

毕竟ECMAScript的构造函数就是能创建对象的函数。
要创建Person的实例,应使用new操作符。以这种方式调用构造函数会执行如下操作。

(1) 在内存中创建一个新对象
(2) 这个新对象内部的[[Prototype]]特性被赋值为构造函数的prototype属性

(3)构造函数内部的this被赋值为这个新对象(即this指向新对象)。
(4)执行构造函数内部的代码(给新对象添加属性)。
(5)如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。

 

2.1.构造函数也是函数
构造函数与普通函数唯一的区别就是调用方式不同。除此之外,构造函数也是函数。

并没有把某个函数定义为构造函数的特殊语法。

任何函数只要使用new操作符调用就是构造函数,而不使用new操作符调用的函数就是普通函数。

   function  Person(name, age, job) {
this.name
= name;
this.age
= age;
this.job
= job;
this.getName
= function () {
console.log(this.name);
};
}
let person_1
= new Person( " panghu " , " 20 " , " student " );
person_1.getName();
// 作为函数调用,添加到window对象
Person( " xiaofu " , " 20 " , " student " )
window.getName();

 

 

 

此处如果将Person当做普通函数来调用,那么this指向的就是全局作用域,数据会被添加到window对象 

 

 

2.2. 构造函数的问题

构造函数虽然有用,但也不是没有问题。

构造函数的主要问题在于,其定义的方法会在每个实例上都创建一遍。

因此对前面的例子而言,person1和person2都有名为sayName()的方法,但这两个方法不是同一个Function 实例。

我们知道,ECMAScript中的函数是对象,因此每次定义函数时,都会初始化一个对象。

如果把方法分离出来,在全局作用域中进行定义,在当前对象需要多个方法,那么就要在全局作用域中定义多个函数.

这个问题可以通过原型模式解决

 

3.原型模式

(他来了,被营销号誉为Js三大难点的"原型"他来了)

 理解复杂概念之前我们先从简单的地方入手(比如新华字典)

 

 

 

然后我们知道,原型指的是原来的模型

 

每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。

实际上,这个对象就是通过调用构造函数创建的对象的原型。

使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。

原来在构造函数中直接赋给对象实例的值,可以直接赋值给它们的原型,

 

3.1.实例共享原型模式的属性和方法

   function  Person(){};

Person.prototype.name
= " panghu " ;
Person.prototype.age
= " 20 " ;
Person.prototype.job
= " student " ;
Person.prototype.getName
= function (){
console.log(this.name);
}

let person_1
= new Person();
person_1.getName();
let person_2
= new Person();
person_2.getName();

 

 

 

 

然后,我们来理解一下这段代码,

首先,我们要把Person的原型当成一个对象来看待,

于是我们现在有了三方势力,Person构造函数,Person原型对象,person_1和person_2两个实例对象

 

 

 

看看这幅图,

Person构造函数Person.prototype指向原型对象,而Person原型对象的constructor指向Person构造函数,

两个实例都只有唯一属性[[Prototype]]指向Person.prototype

 

3.2.原型层级

在通过对象访问属性时,会按照这个属性的名称开始搜索,搜索开始于对象实例本身

如果在对象实例上找到了,则返回对应的值,如果没找到,则搜索会沿着指针进入原型对象,然后在原型对象上找到属性后,再返回对应的值

   function  Person(){};

Person.prototype.name
= " panghu " ;
Person.prototype.age
= " 20 " ;
Person.prototype.job
= " student " ;
Person.prototype.getName
= function (){
console.log(this.name);
}

let person_1
= new Person();
person_1.getName();

person_1.name
= " xiaofu " ;
person_1.getName();

delete person_1.name
person_1.getName();

 

 

 

 

只要给对象实例添加一个属性,这个属性就会遮蔽原型对象上的同名属性,虽然不会修改,但会屏蔽对它的访问

 

 

 

 

3.3.原型的动态性

因为从原型上搜索值的过程是动态的,所以即使实例在修改原型之前已经存在,任何时候对原型对象所做的修改也会在实例上反映出来

   function  Person(){};

Person.prototype.saysomething
= function (){
console.log(
" yes,we can " );
}

let person_1
= new Person();

person_1.saysomething();

 

 

 

 

但重写原型又是另一码事了

   function  Person() {};

let person_1
= new Person();

Person.prototype
= {
constructor: Person,
name:
" panghu " ,
age:
" 20 " ,
job:
" student " ,
saySomething() {
console.log(
" yes,we can " );
}
}
person_1.saySomething();

 

 

 

 

虽然随时能给原型添加属性和方法,并能够立即反映在所有对象实例上、但这跟重写整个原型是两回事。

实例的[[Prototype]]指针是在调用构造函数时自动赋值的,这个指针即使把原型修改为不同的对象也不会变。

重写整个原型会切断最初原型与构造函数的联系,但实例引用的仍然是最初的原型。记住,实例只有指向原型的指针,没有指向构造函数的指针。

 

Person的新实例是在重写原型对象之前创建的。在调用 person_1.saySomething()的时候,会导致错误。

这是因为person_1指向的原型还是最初的原型,而这个原型上并没有saySomething属性。

 

 

 

 

That's all

标签: Javascript

添加新评论