No.009 在JavaScript中创建对象

(以下所有内容仅用以记录学习过程中的个人理解,如有错误欢迎指出)

字面量创建

如下,直接创建一个“18岁的女孩爱丽丝”对象:

var person = { //直接创建对象
	name: "Alice",
	age: 18,
	gender: "female",
	sayName: function(){
		alert(this.name);
	}
};

new + object

该方法和字面量创建有着一样的缺点:

只能创建一次,创造多个的话会造成对象代码冗余

var person = new Object(); //使用new+object创建对象

person.name = "Alice";
person.age = 18;
person.gender = "female";
person.sayName = function(){
	alert(this.name);
}

工厂模式

为了解决字面量创建new + object可能的对象代码冗余问题,可以使用工厂模式来创建对象。

function createPerson(name,age,gender){
	var obj = new Object(); //创建的全部都是Object
	obj.name = name;
	obj.age = age;
	obj.gender = gender;
	obj.sayName = function(){
		alert(this.name);
	}
	return obj;
}

var person1 = createPerson('Alice',18,'female');
var person2 = createPerson('Tom',20,'male');

虽然工厂模式解决了对象代码冗余,但没有解决对象识别的问题。

构造函数模式

构造函数的执行过程:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象,即把this指向这个新对象;
  3. 逐行执行代码;
  4. 将新建对象作为返回值返回。
function Person(name,age,gender){
	//按照惯例,构造函数以大写字母开头
	this.name = name; //使用this来引用新建对象
	this.age = age;
	this.gender = gender;
	this.sayName = function(){
		alert(this.name);
	}
}

var person1 = new Person('Alice',18,'female');
var person2 = new Person('Tom',20,'male');
//Alice和Tom都是Person的实例

构造函数模式与工厂模式的区别在于:

  1. 没有显示创建对象;
  2. 直接将属性和方法赋值给this;
  3. 没有return语句。

我们将构造函数称作一个,用其创建的对象称作该类的实例,作为同一类对象

构造函数模式虽然解决了对象代码冗余对象识别的问题,但随着每次创建对象,构造函数中的方法也会重新创建,造成方法代码污染问题。

原型模式

解析器会往创建的每一个函数中添加一个prototype属性,该属性指向一个原型对象,该对象中包含了某一类的所有实例共享的属性和方法。

function Person(){
}

Person.prototype.name = "Alice";
Person.prototype.age = 18;
Person.prototype.gender = "female";
Person.prototype.sayName = function(){
	alert(this.name);
}

var person1 = new Person();
person1.sayName();  //Alice
var person2 = new Person();
person2.sayName();  //Alice

作为原型对象,它们都有一个constructor属性,指向prototype属性所在的函数,如:

alert(Person.prototype.constructor == Person); //true

下图展示了类、实例、原型三者之间的关系:
No.009 在JavaScript中创建对象
如果要通过对象去访问它的原型对象,可以通过使用隐含属性__proto__,即上图中对象实例的 [Prototype]

function Person(){
	......
}
var person1 = new Person();

alert(person1.__proto__ == Person.prototype); //true

每当读取一个实例的某个属性时,首先会搜索实例本身,找到则返回,如果没找到,则继续搜索其原型对象,在原型对象中找到则返回。

可知,如果实例和原型对象有相同的属性/方法,那么实例中的属性/方法会覆盖掉原型对象中对应的属性/方法。同理,当为实例添加一个属性/方法时,该属性就会屏蔽掉原型对象中的同名属性/方法。

//Person类同上

var person1 = new Person();
person1.sayName();  //Alice

var person2 = new Person();
person2.name = "Tom";  
person2.sayName();  //Tom

简单语法

为了减(tou)少(lan)重复输入Person.prototype,可以用一个包含所有属性/方法的对象字面量来重写整个原型对象,但有一点,constructor属性不再指向Person,所以需要主动设置。

function Person(){
}

Person.prototype = {
	constructor: Person, //主动地让constructor指向Person
	name: "Alice",
	age: 18,
	gender: "female",
	sayName: function(){
		alert(this.name);
	}
}

但如果仅仅使用原型模式的话,会导致所有实例都共享相同的属性,会造成实例混乱

混合使用构造函数和原型

为了防止出现实例混乱,一般创建对象时,我们会混合使用构造函数模式和原型模式。

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

这样能够最大限度地节省内存。

function Person(name, age, gender){ //实例属性
	this.name = name;
	this.age = age;
	this.gender = gender;
}

Person.prototype = { //方法和共享属性
	constructor: Person,
	sayName: function(){
		alert(this.name);
	}
}

var person1 = new Person("Alice", 18, "female");
var person2 = new Person("Tom", 20, "male");