6.2.1工厂模式:
function creatPerson(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 = new createPerson("lilei",22,"software engineer")
person1.sayName() // 'lilie'
6.2.2构造函数模式:
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("lilei",22,"software engineer");
person1.sayName() // "lilei"
说明:person1这个对象有一个constructor(构造函数)属性,该属性指向Person
alert(person1.constructor == Person) // true
对象的constructor属性最初是用来标识对象类型的。但是,提到检测对象类型,还是instanceof 操作符更可靠些。
alert(person1 instanceof Object) //true
alert(person1 instanceof Person) //true
在这里person1对象既是Object实例,同时也是Person的实例。
1.将构造函数当做函数:
构造函数与其他函数的唯一区别,就在于调用它们的方式不同。不过,构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数,只要通过new操作符来调用,那它就可以作为构造函数。而任何函数不通过构造函数调用,那它和普通的函数么有区别。
前面Person()函数的栗子:
// 当作构造函数使用
var person = new Person("lilei",22," software engineer ");
person.sayName() //"lilei"
// 作为普通函数调用
Person("lilei",22," software engineer ") //添加到window
window.sayName() // "lilei"
// 在另一个对象作用域中调用
var o = new Object()
Person.call( o , "Kristen", 25, "Nurse")
o.sayName() // "Kristen"
2.构造函数的问题:
构造函数的问题在于每个方法都要在每个实例上重新创建一遍。因此没定义一个函数,也就实例化了一个对象。从逻辑角度讲,此时的构造函数也可以这样定义:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName =new Function(){ console.log( this.name ) }
}
从这个角度上看构造函数,每个Person实例都包含一个不同的Function实例的本质。
所以构造函数还可以把函数定义写到构造函数外部来解决创建相同的Function实例的问题。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName =sayName
}
function sayName() { console.log(this.name) }
var person1 =new Person("lilei",22,"software engineer");
person1.sayName() // "lilei"
6.2.3原型模式:
每个函数都有一个prototype(原型) 属性,这个属性是一个指针,指向一个对象。
换句话说,不必在构造函数中定义对象实例的信息,而是可以将信息添加到原型对象中。
栗子:function Person(){ }
Person.prototype.name = "lilei";
Person.prototype.age = 22;
Person.prototype.job = "software engineer";
Person.prototype.sayName = function() { console.log(this.name) }
var person1 = new Person()
person1.sayName() // "lilei"
1.理解原型对象
ECMAScript5增加了一个新方法,叫 Object.getPrototypeOf(),在所有支持的实现中,这个方法返回[[ Prototype ]] 的值。
Object.getPrototypeOf( person1 ) == Person.prototype // true
Object.getPrototypeOf(person1).name //"lilei"
解释:Object.getPrototypeOf() 返回的对象实际上是这个对象的原型。第二行代码去得了原型对象的中name属性值。
所以使用 Object.getPrototypeOf() 可以方便的去得一个对象的原型。这在利用原型实现继承的情况下是非常重要的。
多个对象实例共享原型所保存的属性和方法的基本原理:
当代码读取某个对象的某个属性时,会先后执行两次搜索。当调用person1.sayName()时,首先解析器会问:“person1有sayName属性吗?”答:“没有”。然后,会继续搜索,再问:“person1的原型有sayName属性吗?”答:“有”。于是,它就读取了保存在原型对象中的函数。
我们可以在实例中创建该属性,该属性就会屏蔽原型中的那个属性。
function Person(){}
Person.prototype.name = "liil"
var person1 = new Person() var person2 = new Person()
person1.name = "mali"
console.log(person1.name) // mali
console.log(person2.name) // liil
使用delete操作符删除person1.name , person1会恢复对原型中name属性的链接
delete person1.name
console.log( person1.name ) // liil ——来自原型
hasOwnPrototype()方法可以检测一个属性是否存在于实例中,还是存在于原型中:
只在给定属性存在于对象属性中,才会返回true
function Person(){};
Person.prototype.name = "mali";
var person1 = new Person();
console.log( person.name ) // mali 来自实例
console.log(person1.hasOwnProperty("name")); // false
person1.name = "gery";
console.log( person.name ) // gery 来自实例
console.log(person1.hasOwnProperty("name")); // true
delete person1.name;
console.log( person1.name ) // mali 来自原型
console.log(person1.hasOwnProperty("name")); // false
2.原型与in操作符
两种方式使用in操作符:单独使用、for-in循环中使用。
in操作符,属性存在于实例中或是原型中就返回true。
同时使用hasOwnProperty()方法和 in 操作符,就可以确定该属性到底存在于对象中,还是原型中。
栗子:function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name) && (name in object)
}
解释:object是对象,name是属性名。由于in操作符只要通过对象能够访问到属性就返回true,
hasOwnProperty() 只在属性存在于实例中,才返回true,因此只要in操作符返回true而hasOwnProperty()返回false,
就确定属性存在于原型中。
hasPrototypeProperty()函数应用:
function Person(){}
Person.prototype.name = "mali"
var person = new Person()
alert( hasPrototypeProperty( person, "name") ) // true 对象person中的属性name存在Person原型中
person.name = "gery"
alert( hasPrototypeProperty( person, "name") ) // false 对象person中的属性name不存在Person原型中
Object.keys() 方法
接受对象作为参数,返回可for in 枚举的对象属性组成的数组。
var person = {
name:"kk",
age:55,
work:"software"
}
var keys = Object.keys(person)
alert(keys) // ["name", "age", "work"]
3.原型的动态性
由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来——即使是先创建了实例后修改原型也照样如此。栗子:
function Person() {}
var friend = new Peson()
Person.prototype.sayhi = function(){ alert(" hi ") }
friend.sayHi() // "hi"
解释:其原因可以归结为实例与原型的松散连接关系。当调用friend.sayHI()时,首先会在实例中搜索名为sayHI的属性,在没有找到的情况下会搜索原型。因为实例与原型之间的连接只不过是一个指针,而非一个副本。因此就可以在原型中找到新的sayHi属性并返回保存在那里的函数。
但是,如果重新写整个原型对象,情况就不一样了。我们知道,调用构造函数时会为实例添加一个指向最初原型的[[Prototype]]指针,而把原型修改为另一个对象就等于切断了构造函数与最初原型之间的联系。
请记住,实例中的指针仅指向原型,而不指向构造函数。栗子:
function Person(){}
var friend = new Person()
Person.prototype = {
constructor:Person,
name : "lilei",
sayName:function(){ alert( this.name )}
}
friend.sayName() // error
解释:这里先创建了Person 的一个实例,然后又重写了其原型对象。然后在调用friend.sayName()时发生的错误,因为friend指向的原型中不包括以该名字命名的属性。
4.原生对象的原型
原型模式的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式创建的。所有原生引用类型(Object、Array、String,等等)都在其构造函数的原型上定义了方法。例如:在Array.prototype中可以找到sort()方法,在String.prototype中可以找到substring()方法。栗子:
alert( typeof Array.prototype.sort ) // " function "
alert( typeof Array.prototype.substring ) // " function "
通过原生对象的原型,不仅可以取得所有默认方法的引用,而且可以定义新方法。可以像修改自定义对象的原型一样修改原生对象的原型,因此可以随时添加方法。栗子:给String添加一个名为startWith() 方法
String.prototype.startsWidth = function(text) {
return this.indexOf(text) == 0
}
var msg = " Hello World "
alert( msg.startsWith( " Hello " )) // true
5.原型对象的问题
它忽略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。原型的最大问题是由其共享的本性所导致的。原型中所有属性是被很多实例共享的,这总共享对于函数非常合适。然而,对于包含引用类型值的属性来说,问题就比较突出了。栗子:
function Person() {}
Person.prototype = {
constructor: Person,
name:"nick",
age:29,
friends: ["shelby","court"]
}
var person1 = new Person()
var person2 = new Person()
person1.friends.push("van")
console.log(person1.friends) // ["shelby", "court", "van"]
console.log(person2.friends) // ["shelby", "court", "van"]
console.log(person2.friends === person1.friends) // true
解释:Person.prototype对象有一个名为friends的属性,该属性包含一个字符串数组。然后,创建了Person的两个 实例。接着,修改了person1.friends引用的数组。由于friends数组是存在于Person.prototype而非person1中,所以刚刚的修改也会通过person2.friends(与person1.friends指向同一个数组)反映出来。假如我们的初衷就是像这样在所有实例中共享一个数组,那么这个结果我没有话可说。可是,实例一般都是要有属于自己的全部属性的。而这个问题正是我们很少看到有人单独使用原型的原因所在。
6.2.4组合使用构造函数模式和原型模式
创建自定义类型的最常见方式,是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。栗子:
function Person(name,age){ // 构造函数定义实例属性
this.name = name;
this.age = age;
this.friends = [ "seli","curt" ]
}
Person.prototype = { // 原型定义方法和共享属性
constructor: Person,
sayName : function (){
console.log(this.name)
}
}
var person1 = new Person("nick",26)
var person2 = new Person("grey",27)
person1.friends.push("van")
console.log(person1.friends) // ["seli", "curt", "van"]
console.log(person2.friends) // ["seli", "curt"]
解释:这种构造函数与原型混成的模式,是目前ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法。
6.2.5动态原型模式
在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过 检查某个应该存放的方法是否有效,来决定是否需要初始化原型。栗子:
function Person(name,age){
this.name = name; // 属性
this.age = age;
if( typeof this.sayName != " function "){
Person.prototype.sayName = function() {
alert( this.name )
}
}
}
var friend = new Person ( "klli" , 28)
friend.sayName();