面向对象设计——创建对象
1.工厂模式
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("Lucy",30,"Software Engineer"); var person2 = createPerson("Greg",30,"Doctor");
优势:可无数次调用,每次都返回一个包含三个属性一个方法的对象,避免出现多个相似对象的问题。
缺点:无法判断对象的类型。
2.构造函数模式
与其他函数的区别在于调用方式,通过new操作符来调用,如果不用new,跟其他函数没有什么区别。
function Person(name,age,job) { this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); } } var person1 = new Person("Lucy",30,"Software Engineer"); var person2 = new Person("Greg",30,"Doctor"); 作为构造函数调用: var person = new Person("Greg",30,"Doctor"); person.sayName(); //Greg 作为普通函数调用: Person("Greg",30,"Doctor"); //添加到window window.sayName();或者sayName(); //Greg 在另一个对象作用域中调用 var o = new Object(); Person.call(o,"Kristen",30,"Nurse"); o.sayName(); //Kristen
优势:
(1)创建自定义的构造函数,意味着可以将实例标识为一种特定的类型,是胜过工厂模式的地方。
alert(person1 instanceof Person); //true
alert(person1 instanceof Object); //true
(2)每个方法都要每个实例上重新创建一遍,例子中person1和person2都有一个sayName()的方法,但是两个方法不是同一个Function实例。注:ECMAScript中的函数是对象,因此每定义一个函数,也就实例化一个对象。
等价于:this.sayName = new Function("alert(this.name)");
避免方案:
function Person(name,age,job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); } //弊端:全局作用域的函数实际只能被某个对象调用,让全局作用域名不副实;若对象需要定义很多方法,就对应很多全局方法。
3.原型模式
function Person() {} Person.prototype.name = "Lucy"; Person.prototype.age = 30; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name) } var person1 = new Person(); person1.sayName(); //Lucy
//简单语法
function Person() {} Person.prototype = {
constructor: Person, //确保constructor属性指向Person函数 name: "Lucy", age: 30, sayName: function(){ alert(this.name) } }; var person1 = new Person(); person1.sayName(); //Lucy
注:若将var person1 = new Person();提升到第二行,则报错,因为重写原型对象切断了现有实例和新原型之间的联系,它们的引用任然是最初的原型。
优势:可以让所有实例共享它所包含的属性和方法。
原理:只要创建了一个函数,即为该函数创建一个prototype属性,这个属性指向函数的原型对象。默认情况下,所有的原型对象都会自动获得一个constructor(构造函数)属性,这个属性指向函数。
方法:
确定原型与实例关系的两个方法:
(1)instanceof操作符
person1 instanceof Person //true
(2)isPropotypeOf()方法
Person.prototype.isPrototypeOf(person1) //true
其他:
Object.getPrototypeOf()方法:Object.getPrototypeOf(person1)返回[[prototype]]的值
hasOwnProperty():检测一个属性是否存在于实例中
in操作符:只要通过对象能访问到属性,就返回true,同上结合即可确定该属性是存在于对象中还是原型中。
弊端:原型模式的所有的属性被很多实例共享,即由于其共享本质所致;尤其是引用类型值的属性(即["red","green","yellow"])。
4.组合使用构造函数模式和原型模式(认可度高,推荐)
即构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性
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,Court,Van' alert(person1.friends); //'Shelby,Court'
缺点:有其他OO语言开发经验的人员在看到独立构造函数和原型时,有些困惑。
5.动态原型模式
把所有信息都封装在了构造函数中,通过在构造函数中初始化原型(仅在有必要的情况下)。
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; //方法 if(typeof this.name != function){ Person.prototype.sayName = function(){ alert(this.name); } } }
优势:对于采用这种模式创建的对象,还可以使用instanceof操作符来确定它的类型。即 person1 instanceof Person; //true
6.寄生构造函数模式
除了使用new操作符外,其他跟工厂模式一模一样。
function SpecialArray(){ var values = new Array(); //添加值 values.push.apply(values, arguments); //添加方法 values.toPipedString = function(){ return this.join('|'); } //返回数组 return values; } var colors = new SpecialArray('red','blue','green'); alert(colors.toPipedString()); //'red|blur|green'
缺点:返回的函数与构造函数或者说与构造函数的原型属性一点关系没有,跟工厂模式一样的,不能依赖instanceof操作符来确定类型。
7.稳妥构造函数模式
function Person(name, age, job){ var o = new Object(); //可以在这里定义私有变量和函数 o.sayName = function(){ alert(name); } return o; } var person1 = Person('Nicholas', 29, 'Software Engineer'); person1.sayName(); //'Nicholas'
优势:除了调用sayName方法外没有别的办法可以访问到其数据成员,适合安全环境中,防止数据被其他程序改动时使用。
与寄生构造函数的区别:
(1)新创建对象的实例方法不使用this
(2)不使用new操作符调用构造函数
缺点:与寄生构造函数类似,操作符instanceof对此对象不起作用。