js的面向对象编程之创建对象一(工厂模式和构造函数模式)

1. 创建对象

Object构造函数或对象字面量都可以用来创建单个对象,但这种方式使用同一个接口会产生大量的重复代码。

2. 工厂模式

用函数来封装以特性接口创建对象的细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createPerson(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function () {
console.log(this.name);
};
return o;
}

var person1 = createPerson("jack", 18);
var person2 = createPerson("alice", 18);
console.log(person1 instanceof Object);//true
console.log(person1 instanceof createPerson);//false

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

3. 构造函数模式

构造函数始终都应该以一个大写字母开头。这个做法借鉴与其他OO语言,主要是为了区别于ECMAScript中的其他函数

以这种方式定义的构造函数是定义在Global对象中的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(name, age) {
//没有显式地创建对象
this.name = name;
this.age = age;
this.sayName = function () {
console.log(this.name);
};
//直接将属性和方法赋给了this对象

//没有return语句
}

var person1 = new Person("jack", 18);
var person2 = new Person("alice", 18);
console.log(person1 instanceof Object);//true
console.log(person1 instanceof Person);//true 解决了对象识别的问题
console.log(person1.constructor == Person);

要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
  3. 执行构造函数中的代码(为这个新对象添加属性);
  4. 返回新对象;
    1
    2
    3
    4
    5
    6
    7
    8
    + 第零步:在内存中开辟一个新空间
    + 第一步:创建一个空对象
    + 第二部:把this指向到这个空对象
    + 第三步:把空对象的 内部原型 指向构造函数的 原型对象(`Cat.prototype`)
    + 第四步:当构造函数执行完毕后,如果没有return的话,那么把当前空对象返回
    https://www.zhihu.com/question/36440948

    http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html

3.1 将构造函数当作函数

构造函数与其他函数的唯一区别,就在于调用它们的方式不同。构造函数自己也是普通函数。

3.2 构造函数的问题

构造函数的主要问题是每个方法都要在每个实例上重新创建一遍。

1
2
3
4
5
6
7
8
9
function Person(name, age) {
this.name = name;
this.age = age;
//this.name = new Function("console.log(this.name)");
//与下面的声明函数是等价的
this.sayName = function () {
console.log(this.name);
};
}

从上面代码可知,每个Person实例都包含一个不同的Function实例。这样不同实例上的同名函数也是不想等的。

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age) {
this.name = name;
this.age = age;
//this.name = new Function("console.log(this.name)");
//与下面的声明函数是等价的
this.sayName = function () {
console.log(this.name);
};
}
var person1 = new Person("jack", 18);
var person2 = new Person("alice", 18);
console.log( person1.sayName == person2.sayName );//false

而且,创建两个完成同样任务的Function实例根本没有这个必要。我们可以把函数定义转移到构造函数外部。

1
2
3
4
5
6
7
8
9
10
function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName() {
console.log(this.name);
}
var person1 = new Person("jack", 18);
var person2 = new Person("alice", 18);

这样虽然解决了两个函数做一件事的问题,但是又出现了新问题:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。而且如果对象需要很多方法,那么就要定义很多个全局函数,那我们这个自定义的引用类型就没有封装性可言了。为了解决这些问题,可以通过原型模式来解决。

1. 创建对象的方式

1.1 json的方式创建对象

用来做零时信息存储

1
2
3
4
5
6
7
8
9
var o = {key:val};
var arr = [1, 2, 3, {}];

var myObject = {
name:'jack',
say: function () {
console.log(this.name);
}
};

缺点:不能把json对象当作一个模板,进行new来构造一个新对象。

给json对象添加属性和方法

1
2
3
4
myObject.age = 18;
myObject.show = function () {
//
};

1.2 通过new

1
2
var o = new Object();
o.name = "jack";

缺点同1.1

1.3 通过构造函数

1
2
3
4
5
6
function Cat() {
// 第一步 创建一个空对象 var o = {};
//第二步 this指向这个空对象 this = {}
//
}
var cat1 = new Cat();

new的原理: