No.009 在JavaScript中创建对象
在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');
虽然工厂模式解决了对象代码冗余,但没有解决对象识别的问题。
构造函数模式
构造函数的执行过程:
- 创建一个新对象;
- 将构造函数的作用域赋给新对象,即把this指向这个新对象;
- 逐行执行代码;
- 将新建对象作为返回值返回。
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的实例
构造函数模式与工厂模式的区别在于:
- 没有显示创建对象;
- 直接将属性和方法赋值给this;
- 没有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
下图展示了类、实例、原型三者之间的关系:
如果要通过对象去访问它的原型对象,可以通过使用隐含属性__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");