Js高程笔记-对象的创建(工厂模式、构造函数模式、原型模式)
一、 工厂模式
用函数来封装以特定接口创建对象的细节
function createPerson(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
可以无数次地调用这个函数, 而每次它都会返回一个包含三个属性一个方法的对象
缺点: 工厂模式虽然解决了创建多个相似对象的问题, 但却没有解决对象识别的问题( 即怎样知道一个对象的类型)。
二、 构造函数模式
创建自定义构造函数, 类似Object, Array
构造函数都应首字母大写, 以便区别非构造函数
构造函数也是函数, 只不过可以用来创建对象
function Person(name, age, job) {
this.name = name
this.age = age
this.job = job
this.sayName = function () {
alert(this.name)
}
}
var person1 = new Person('Jack', 34, 'doctor')
var person2 = new Person('Tom', 38, 'teacher')
跟工厂模式的不同:
1).没有显式地创建对象(var o = new Object())
2).直接将属性和方法赋值给了this对象
3).没有return
创建构造函数的实例(必须使用new 操作符), 经历了以下四个步骤:
1).创建一个新对象
2).将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
3).执行构造函数中的代码(为这个新对象添加属性)
4).返回新对象
每个实例对象都有一个constructor(构造函数) 属性, 该属性指向构造函数本身Person
instanceof操作符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置, 同时所有对象均继承自Object
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true
构造函数也可以当函数来使用
任何函数只要通过new 操作符来调用, 那它就可以作为构造函数, 而不通过new 操作符来调用, 那就是普通的函数
// 当作构造函数使用
var person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); //"Nicholas"
// 作为普通函数调用
Person("Greg", 27, "Doctor"); // 添加到 window
window.sayName(); //"Greg"
// 在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); //"Kristen"
构造函数的问题
缺点:
每个方法都要在每个实例上重新创建一遍, 函数也是对象实例出来的,
创建一个函数就等于实例一个对象, 以这种方式创建函数, 会导致不同的作用域链和标识符解析, 两个实例的sayName() 并不相等
alert(person1.sayName == person2.sayName); //false
//解决: 把函数放在构造函数的外部
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
alert(this.name);
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
console.log(person1.sayName());
console.log(person2.sayName());
所有实例出来的对象都共享了在全局作用域中定义的同一个函数, 导致的新问题:
在全局作用域中定义的函数实际上只能被某个对象调用, 这让全局作用域有点名不副实(既然是全局作用域定义的函数, 那么应该所有的对象都会调用这个函数, 而不是某个对象的专属函数)
缺乏封装性, 在全局定义的函数会容易导致代码冲突
三、 原型模式
每个函数都有prototype(原型) 属性, 一个指向包含可以由特定类型的所有实例共享的属性和方法的对象的指针
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
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属性, 这个属性指向函数的原型对象
Person.prototype
所有的原型对象都有constructor(构造函数) 属性, 该属性包含一个指向prototype属性所在函数的指针
Person.prototype.consturctor == Person //true
所有的实例都包含一个指针( 内部[[Prototype]] 属性, 浏览器通过__proto__访问), 指向构造函数的原型对象, 只存在于实例与构造函数的原型对象之间
而不是存在于实例与构造函数之间
person1.__proto__ == Person.prototype //true
isPrototypeOf() 用来检测实例的内部属性__proto__是否指向原型函数 Person.prototype
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
Object.getPrototypeOf() 方法返回指定对象的原型( 内部[[Prototype]] 属性的值)。
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
缺点:
省略了初始化传参, 导致所有的实例都取得相同的属性
所有的属性都会被共享, 包括包含引用类型值(Array等) 的属性
//解决: 组合使用构造函数模式和原型模式
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,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true