一、创建对象
1.1 工厂模式
工厂模式抽象了创建具体对象的过程,用函数来封装以特定接口创建对象的细节。代码如下:
function createPerson(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function() { console.log(this.name); }; return o; } var person1 = createPerson('sy', 25, 'Software Engineer') var person2 = createPerson('sansa', 25, 'Doctor')
工厂模式的优点:解决创建多个相似对象的问题
缺点:无法解决对象识别的问题,即怎样知道一个对象的类型
1.2 构造函数模式
构造函数可以创建特定类型的对象,如果是Object和Array这样的原生构造函数,在运行时会出现在执行环境中,此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function() { console.log(this.name) } } var person1 = new Person('sy', 25, 'Software Engineer') var person2 = new Person('sansa', 25, 'Doctor')
与构造函数不同之处:
没有显式地创建对象;直接将属性和方法赋给了this对象;没有return语句。
创建Person的新实例,必须使用new操作符。会经过以下步骤:
(1)创建一个新对象;
(2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
(3)执行构造函数中的代码(为这个新对象添加属性);
(4)返回新对象。
person1和person2都有一个constructor(构造函数)属性,该属性指向Person。
person1.constructor == Person; // true
构造函数的优点:可以清楚的知道一个对象的类型
缺点:构造函数中存在方法的话,每个Person实例都包含一个不同的Function实例。ECMAScript中的函数是对象,因此每定义一个函数,就是实例化了一个对象。如果将方法移到全局作用域的话,会导致这个函数只能被某个对象调用,这让全局作用域名不副实。而且如果对象需要定义很多方法,那就要定义很多全局函数,这样对自定义的引用类型没有了封装性。
1.3 原型模式(*)
使用原型对象的好处是:可以让所有对象实例共享它所包含的属性和方法。创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,prototype就是通过调用构造函数而创建的那个对象实例的原型对象。
function Person() { } Person.prototype.name = 'sy'; Person.prototype.age = 25; Person.prototype.job = 'Software Engineer'; Person.prototype.sayName = function() { console.log(this.name) }; var person1 = new Person() person1.sayName() // 'sy' var person2 = new Person() person2.sayName() // 'sy' person1.sayName === person2.sayName // true
__proto__存在于实例与构造函数的原型对象之间。
Person.prototype.isPrototypeOf(person1) // true
Object.getPrototypeOf(person1) == Person.prototype // true
注:使用for-in循环时,返回的是通过对象访问的、可枚举的属性,包括实例中的属性,也包括存在于原型中的属性。要取得对象上所有可枚举的实例属性,使用ES5的Object.keys()方法,这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。
如果要得到所有实例属性,无论是否可枚举,可以使用Object.getOwnPropertyNames(Person.prototype); // ["constructor", "name"] 显示了不可枚举的constructor属性
简单的原型语法:
function Person(){ } Person.prototype = { name: 'sy', age: '25', job: 'Software Engineer', sayName: function() { console.log(name) } }
constructor属性不再指向Person了。每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得constructor属性,而这个语法,本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。通过constructor无法确定对象的类型了。可以强制将constructor: Person,但是会导致它的[[Enumerable]]特性设置为true。实例中的指针仅指向原型,而不指向构造函数。
原型模式的优点:可以共享属性和方法
缺点:对于包含引用类型的属性,会导致所有的对象都发生变化
1.4 组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。最大限度地节省了内存,而且这种模式支持向构造函数传递参数。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ['111', '222'] } Person.prototype = { constructor: Person, sayName: function() { console.log(this.name) } } var person1 = new Person('sy', 25, 'Software Engineer') var person2 = new Person('sansa', 25, 'Doctor') person1.friends.push('333')
console.log(person1.friends === person2.friends) // false
1.5 动态原型模式
为了将构造函数和原型封装,动态原型模式把所有信息都封装在了构造函数中。可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
function Person(name, age, job){ // 属性 this.name = name; this.age = age; this.job = job; // 方法 if(typeof this.sayName != 'function'){ Person.prototype.sayName = function() { console.log(this.name) } }
} var friend = new Person('sy', 25, 'Software Engineer') friend.sayName(); // sy
使用动态原型模式时,不能使用对象字面量重写原型。如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。
1.6 寄生构造函数模式
思想:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
function Person(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ console.log(this.name); }; return o; } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas"
1.7 稳妥构造函数模式
稳妥对象:指的是没有公共属性,而且其方法也不引用this的对象。
function Person(name, age, job){ // 创建要返回的对象 var o = new Object(); // 可以在这里定义私有变量和函数 // 添加方法 o.sayName = function() { console.log(name) } // 返回对象 return o; } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas"