加州提子面包

JavaScript创建对象的方式

注意: 本文来自阅读《JavaScript高级程序设计(第三版)》的笔记或者也可以称作是书摘吧👾

一般方式

  • 构造函数
  • 对象字面量

工厂模式

这种模式抽象了创建具体对象的过程。如下例子所示。

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.job = job;
o.sayName = functioin () {
alert (this.name);
};
return o;
}
// 实例化两个类
var person1 = createPerson('xixi', 29, 'Software Engineer');
var person2 = createPerson('waXaw', 27, 'teacher');

虽然解决了常见太多相似类的问题,但却没有解决对象识别的问题。

构造函数模式

ArrayObject 这样的原生构造函数,在运行时会自动创建执行环境,还可以自定义对象属性和方法。如下例子所示。

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = functioin () {
alert (this.name);
};
}
// 实例化两个类
var person1 = new Person('xixi', 29, 'Software Engineer');
var person2 = new Person('waXaw', 27, 'teacher');

与之前的工厂模式对比可以有以下发现:

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

注意: 构造函数应该以一个大写字母开头

实例化一个对象,要经过以下步骤:

  1. 创建一个新对象
  2. 将构造函数的作用域赋值给新对象(因此 this 就指向了这个新对象)
  3. 执行构造函数中的代码(为这个类对象添加属性)
  4. 返回对象

person1person2 分别保存着 Person 类的一个不同的实例。 这个两个对象都有一个 constructor (构造函数)属性,该属性指向 Person
创建自定义的构造函数意味着将来可以将它的实例为一种特定的类型,此处正是构造函数模式胜过工厂模式的地方。

构造函数是一种特殊的函数,他可以通过 new 操作符来调用。

构造函数调用方式:

  • 1.当做构造函数使用
1
2
var person = new Person('xixi', 29, 'Software Engneer');
person.sayName(); //'xixi'
  • 2.作为普通函数使用
1
2
Person('waXaw', 24, 'Teacher');
window.sayName(); //'waXaw'
  • 3.在对象的另一个作用域中调用
1
2
3
var o = new Object();
Person.call(o, 'mig', 23, 'CEO');
o.sayName(); //'mig'

存在的问题

构造函数的方法 (即 sayName )每次都要重新创建一遍。也就是说 person1person2sayName 不是同一个 Function 创建的。
可以通过

1
alert(person1.sayName === person2.sayName()); // false

解决方法:
函数定义转移到构造函数的外部。如下例所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName =sayName;
};
functioin sayName() {
alert (this.name);
};
// 实例化两个类
var person1 = new Person('xixi', 29, 'Software Engineer');
var person2 = new Person('waXaw', 27, 'Teacher');

新的问题:

  • sayName 使得全局变量名不副实
  • 自定义的引用类型没有了封装性可言

原型模型

原型( prototype )属性:

这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。prototype 就是通过构造函数而创建的那个对象实例的原型对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person() {
};
Person.prototype.name = 'Nicholas';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engneer';
Person.prototype.sayName = function () {
alert(this.name);
};
var person1 = new Person();
person1.sayName(); // 'Nicholas'
var person2 = new Person();
person2.sayName(); // 'Nicholas'
alert(person1.sayName === person2.sayName); // true

理解原型对象

当创建一个新函数的时候,就会创建一个 prototype 属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象会自动获得一个 constractor (构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。如图所示,Person.prototype.constructor 指向 Person 。也就是说,可以通过这个构造函数为原型对象添加其他属性和方法。

prototype

注意: 虽然可以通过对象实例访问保存在原型中的值,却不能通过对象实例重写原型中的值。

如果在实例中添加一个属性,该属性与实例中的一个属性同名,那在实例中常见该对象,该属性会将原型中的那个属性屏蔽。

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

原型与 in 操作符

用法

  • 单独使用。 in 操作符会在通过对象能够访问对象时返回 true ,无论存在于实例还是原型中。
  • for-in 循环。返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,既包括存在于实例中的属性和原型中的属性。不可枚举的 constructor 属性,可以通过 Object.keys()Object.getOwnPropertyName() 方法来替代 for-in 循环。

更简单的原型语法

可以通过如下方式:

1
2
3
4
5
6
7
8
9
10
11
function Person () {
};
Person.prototype = {
name: 'Nicholas',
age: 29,
job: 'Software Engineer',
sayName: function () {
alert(this.name);
}
};

上面的代码将 Person.prototype 设置为等于一个以对象字面量形式创建的对象。

例外 constructor 属性不再指向 Person

为了解决上面的问题,可以如下

1
2
3
4
5
6
7
8
9
10
11
12
function Person () {
};
Person.prototype = {
constructor: Person,
name: 'Nicholas',
age: 29,
job: 'Software Engineer',
sayName: function () {
alert(this.name);
}
};

注意: 以此方式重设 constructor 属性将会导致他的 [[Enumerable]] 特性被设置为 true 默认情况下, constructor 属性是不可枚举的

原型的动态

注意: 实例中的指针仅指向原型,而不是指向构造函数。

原生对象的原型

  • 可以取得默认方法的引用。例如。

    1
    2
    alert(typeof Array.prototype.sort); // 'function'
    alert(typeof String.prototype.substring); //function

  • 还可以定义新方法

1
2
3
4
5
6
String.prototype.startWith = function (text) {
return this.indexOf(text) === 0;
};
var msg = 'Hello World!';
alert(msg.startWith('Hello')); //true

注意: 不建议在产品化的程序中修改原生对象的原型。

原型对象的问题

  • 省略了为构造函数传递初始化参数这一环节,导致所有实例在默认情况下都将取得相同的属性值。
  • 最大的问题在于其共享的本性导致的。原型中所有属性都是实例共享的。对于函数适合。但是对于类型值的属性来说,问题就突出了。

组合使用构造函数和原型模式

创建自定义类型的最常见方式。

特点

构造函数模式用于定义实例属性;原型模式用于定义方法和共享的属性。

好处

  • 每个实例都会有自己的一份实例属性的副本
  • 但同时又共享着对方法的引用
  • 最大限度的节省内存。
  • 还支持向构造函数传递参数

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ['Shelby', 'Court'];
};
Person.prototype = {
constructor: Person,
sayName: function () {
alert(this.name);
}
};
var person1 = new Person('Nicholas', 29, 'SoftWare Engineer');
var person2 = new Person('Greg', 27, 'Doctor');
person1.friends.push('Van');
alert(person1.friends); // 'Shelby, Court, Van'
alert(person2.friends); // 'Shelby, Court'
alert(person1.friends === person2.friends);
// false
alert(person1.sayName === person2.sayName);
// true

此种方式是目前 ECMAScript 中使用最广泛认同度最高的一种创建自定义类型的方法。 换句话说,这是用来定义引用类型的一种默认模式。

动态原型模式

动态原型模式将所有信息封装在构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同事使用构造函数和原型的优点。如例子所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(name, age, job) {
// 属性
this.name = name;
this.age = age;
this.job = job;
// 方法
if (typeof this.sayName != 'function') {
Person.prototype.sayName = function () {
alert(this.name);
}
}
};
var friend = new Person('Nicholas', 29, 'Software Engineer');
friend.sayName();

寄生构造函数模式

主要思想: 创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后返回新的对象。下面是例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name, age, job) {
var o = {};
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
alert(this.name);
};
return o;
};
var friend = new Person('Nicholas', 29, 'Software Engineer');
friend.sayName(); // 'Nicholas'

补充说明:

  • 返回的对象与构造函数或者与构造函数的原型属性之间没有关系,也就是说构造函数返回的对象与在构造函数外创建的对象没有什么不同。
  • 不能依赖 instanceof 操作符来确定对象类型。
  • 在能使用别的模式的情况下,不要使用该模式。

稳妥构造函数模式

是什么?

没有公共属性 | 其方法也不引用 this 的对象

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person(name, age, job) {
// 创建一个返回的对象
var o = new Object();
// 可以在这里定义私有变量和函数
// 添加方法
o.sayName = function () {
alert(name);
};
return o;
};
var friend = Person('Nicholas', 29, 'Software Engineer');
friend.sayName(); // 'Nicholas'
------ 本文结束 ------