通过Object构造函数或对象字面量创建对象时,使用同一个接口创建很多对象时,会产生大量的重复代码。为了简化,引入了工厂模式。
工厂模式
function createPerson(name, age, job) { var obj = new Object(); obj.name = name; obj.age = age; obj.job = job; obj.sayHello(){ alert(this.name); }; return obj; } var p1 = createPerson("xxyh", 19, "programmer"); var p2 = createPerson("zhangsan", 18, "student");
这种创建对象的方式大大简化了代码,然而也存在不足,那就是无法确定对象的类型。为了解决这个问题,出现下面这种模式。
构造函数模式
创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { alert(this.name); }; } var p1 = new Person("xxyh", 19, "programmer"); var p2 = new Person("Jack", 18, "student");
上例中,Person()取代了createPerson(),除此之外,还有几点不同:
•没有显示地创建对象;
•直接将属性和方法赋值给了this对象
•没有return语句
创建Person对象,必须使用new操作符。分为4个步骤:
•创建一个新对象
•将构造函数的作用域赋给新对象
•执行构造函数中的代码
•返回新对象
p1和p2分别保存着Person的一个实例。
alert(p1.constructor == Person); // true alert(p2.constructor == Person); // true
检测类型时最好使用instanceof:
alert(p1 instanceof Object); // true alert(p1 instanceof Person); // true alert(p2 instanceof Object); // true alert(p2 instanceof Person); // true
p1和p2都是Object的实例,因为所有对象均继承自Object。
2.1将构造函数当作函数
// 当作构造函数使用 var person = new Person("xxyh", 19, "programmer"); person.sayName(); // "xxyh" // 当作普通函数 Person("zhangsan", 18, "student"); // 添加到window window.sayName(); // "zhangsan" // 在另一个对象的作用域中调用 var obj = new Object(); Person.call(obj, "Jack", 29, "manager"); obj.sayName(); // "Jack",obj拥有了所有属性和方法
2.2构造函数的问题
使用构造函数的问题,就是每个方法都要在每个实例上重新创建一遍。p1和p2都有一个sayName()方法,但是他们不是一个Function的实例。在JavaScript中,函数时对象,因此每定义一个函数,就实例化了一个对象。
构造函数也可以这样定义:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = new Function("alert(this.name)"); }
因此,不同实例上的同名函数时不相等的:
alert(p1.sayName == p2.sayName); // false
然而,创建两个同样功能的Function是多余的,根本不需要在执行代码前就把函数绑定到特定对象上面。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName() { alert(this.name); } var p1 = new Person("xxyh", 19, "programmer"); var p2 = new Person("Jack", 18, "student");
上面将sayName()的定义移到构造函数外部,然后在构造函数内部将属性sayName设置为全局的sayName函数。这样,sayName包含了指向函数的指针,p1和p2共享了全局作用域中定义的同一个sayName()函数。
但是,这样做又出现了新问题:在全局作用域中定义的函数只能被某个对象调用。而且如果对象定义了很多方法,那么引用类型就失去了封装性。
原型链模式
每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象。这个对象的用途是:包含可以由特定类型的所有实例共享的属性和方法。prototype是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。这就是说不必在构造函数中定义对象实例的信息,而是将这些信息添加到原型对象中。
function Person() { } Person.prototype.name = "xxyh"; Person.prototype.age = 19; Person.prototype.job = "programmer"; Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person(); person1.sayName(); // "xxyh" var person2 = new Person(); person2.sayName(); // "xxyh" alert(person1.sayName == person2.sayName); // true
3.1理解原型对象