第六章 面向对象程序设计(2.创建对象)

  ·   JS高设(第三版)   ·   JavaScript     浏览量:

虽然Object构造函数或函数对象都可以用来创建单个对象,但是这个方法有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。

工厂模式

工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建对象的过程。
ES中无法创建类,于是开发人员发明了一种函数,用来封装以特定接口创建对象的细节,如下例子

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = job;
    o.sayName = function() {
        alert(this.name);
    };
    return o;
}

var person1 = createPerson("尼古拉斯·赵四", 29, "亚洲舞王");
var person2 = createPerson("莱昂纳多·刘能", 27, "欧洲舞王");

工厂模式虽然解决了创建多个相似对象的问题,但是却没有解决对象识别问题(即怎样知道一个对象的类型)。

构造函数模式

使用构造函数将前面例子重写

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        alert(this.name);
    }
}

var person1 = new Person("尼古拉斯·赵四", 29, "亚洲舞王");
var person2 = new Person("莱昂纳多·刘能", 27, "欧洲舞王");

构造函数始终以大写字母开头,非构造函数以小写开头
与之前不同之处

  • 没有显示地创建对象;
  • 直接将属性和方法赋值给this对象;
  • 没有return语句

要创建Person实例,必须使用new操作符,这种方式会经历以下四个步骤

  • 创建一个对象;
  • 将构造函数的作用域赋值给新对象(因此this指向这个新的对象)
  • 执行构造函数中的代码(为新对象添加属性)
  • 返回新对象。

上面的两个实例person1、2分别保存着Person的两个不同实例。
他们都有一个constructor(构造函数)属性,该属性指向Person
4

创建自定义都遭函数意味着将来可以将他的实例标识为一种特定的类型,而这正式构造函数胜过工厂模式的地方。

1.将构造函数当作函数

构造函数和函数的唯一区别在于,调用它的方式不同
任何函数只要通过new来调用,就可以作为构造函数,不通过new调用就和普通函数没什么两样
前面例子中的Person()可以通过下列方式调用
6

2.构造函数的问题

构造函数虽然好,但是每个方法都要在每个实例上重新创建一遍。
前面例子中的person1、2中的sayName()方法就不是同一个Funtion对象,

原型模式

每个函数 都有一个prototype(原型)属性,这个属性指向自己最开始的的原型对象
使用原型可以让所有实例都共享他的属性and方法
所以不用在构造函数中定义对象的实例信息,直接添加到原型对象中
7

1.理解原型对象

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获取一个constructor(构造函数)属性买这个属性是一个指向prototype的指针。就拿前面的例子来说,Person.prototype.constructor指向Person。而通过这个构造函数,我们还可以继续为原型对象添加其他属性和方法

创建自定义的构造函数之后,其原型对象只有constructor属性,其他方法都是从Object继承来的。
而创建新实例之后,每个实例都有一个内部属性**[[prototype]]** ,指向构造函数的原型对象在火狐、谷歌苹果浏览器上显示为_proto_

我总结出来就是:函数有个属性prototype,prototype指向函数的原型对象(就是{}和里面的内容),而他又有个属性constructor,constructor又指向函数,这是一个圈套。而每个实例的_proto_都会指向他们的构造函数的原型对象
1
下图是上面例子关系解析
2
也就是:
构造函数Person.prototype=>Person的原型对象
Person的原型对象的constructor属性=>Person()
两个实例的_proto_=>Person的原型对象

虽然两个实例都没有自己的属性和方法,但是却可以使用Person的原型对象的方法,因为代码在查找某个属性时,如果自身没有就会继续搜索原型对象,但是不能覆盖重写原型对象里面的值

2.原型与in操作符

有两种使用in的方法,一种是用for-in,另一种是单独使用

单独使用

3
可以获得原型对象和实例中的属性

3.更简单的原型方法

4
创建方法如上图,但此种方法相当于重写了一遍prototype,所以他的constructor就不再指向Person了,如果constructor很重要的话,可以放开途中注释,手动添加。
但是这样会导致constructor的**[[enumberable]]** 变为true,原生是不能枚举的,都重写了一遍了,他已经不再是以前那个他了,想想也合理

4.原型的动态性

5
图中先创建实例,在添加方法,依然能够访问这个方法,为什么呢。
因为sayHi他是一个指针,指向原型对象中的方法
随时在原型属性中添加方法都会在每个实例中体现出来

尽管可以随时添加,但如果重写整个原型对象就不一样了,如下图6

图中先创建了Person的实例,然后重写了原型对象,之后调用sayHi的时候就发生了一个小小的意外,原因如下,
sayName()是重写后的原型中的方法,而实例永远指向最初的原型对象,在重写的时候构造函数已经和原始的对象失去联系了
下图更清晰
7

5.原生对象的原型

可以像修改自定义对象一样修改添加自定义方法,如下我为包装类型String添加了一个自定发方法,在字符串结尾添加上自己的名字
8

书上说,尽管可以这么做,但是不推荐

6.原型对象的问题

小问题:缺少了传递参数,因此所有实例都将拥有相同的属性,但这只是小问题
大问题:由于原型中的属性和函数都是共享的,对于基本值的属性问题不大,但是对于引用类型来说,问题就比较大了
9
看吧,person1交了个朋友叫王二,结果person2的好友列表里也有他
那么要怎么解决呢,请看下文

构造函数+原型模式

  • 最常见的方法
  • 构造函数用于定义实例属性
  • 原型模式用于定义方法和共享属性
  • 每个实例都有一份自己的属性,又有着对共享方法的引用,极大限度的节省了内存
  • 还™的可以传参数,真好

11
图片自己理解

动态原型模式

12
就是上面框内的判断了,这里只会在sayName不存在的时候,才会添加到原型中,因为这是对原型所做的修改,所以能立刻反映到所有实例中。
但是要注意,不能使用字面量重写原型,前面讲过这样会切断实例和新原型之间的联系。

寄生模式构造函数

通常在之前的模式都不行的时候,可以使用寄生模式。
基本思路:创建一个函数,仅仅用于封装代码,然后返回新创建的对象

function Person(name, age, job){
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayName = function () {
    console.log(this.name);
  }
  return o;
};

var person1 = new Person('张三', 18, '流氓');
person1.sayName()

上面例子中,Person函数创建了一个对象,并以相应的属性和方法初始化该对象,然后返回他。
除了使用new操作符并把包装函数叫做构造函数之外,这和工厂模式并没有任何区别。
构造函数在不返回值的情况下会返回新的实例对象。而通过一个return语句,可以重写调用构造函数时返回的值。
这个模式可以在特殊的情况下来为对象创造构造函数。
假设我们需要创建一个具有额外方法的特殊数组。由于不能直接修改Array构造数组,因此可以使用这个模式
13
如上图,就为数组添加了一个方法,该方法返回在数组中添加|之后的字符串

说明:寄生模式返回的对象与构造函数说着与构造函数的原型之间没有关系,他返回的对象和在外部创建的对象是没有什么区别的,因此instanceof不能用来确定他的类型,所以在可以使用其他模式的时候,不建议试着这种模式

稳妥模式

没有公共属性,其方法也不适用this
适合在一些安全的环境中(禁止this、new),或者需要放置数据被其他程序修改时使用
他与寄生模式的区别

1.创建实例方法不用this

按照稳妥模式改写后的Person

function Person(name, age, job) {
    //创建要返回的duix
    var o = new Object();
    
    //可在此处定义私有变量和函数
    
    //添加方法
    o.sayName = function () {
        console.log(name)
    }
}

var friend = Person("Nikolas", 29, "doc");
firend.sayName();

除了使用sayName()方法之外,没有其它方法访问到name的值
稳妥模式和寄生模式一样,创建的对象和构造函数之间也没有什么关系,所以instanceof对这种对象没什么意义



收起 >>
第六章 面向对象程序设计(2.创建对象)