1.代码
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); } } var person1 = new Person("Nicholas",29,"Saftware Engineer"); var person2 = new Person("Greg",27,"Doctor"); person1.sayName();//"Nicholas" person2.sayName();//"Greg"
① 没有显示创建对象 ,如:var o = new Object();
② 直接将属性和方法赋给了this对象 //
③ 没有return语句。//return o;
3. new操作符 调用构造函数实际经历了以下4个步骤:
① 创建一个新对象
② 将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
③执行构造函数中的代码(为这个新对象添加属性)
④ 返回新对象
4. person1和person2对象都有一个属性constructor,该属性指向Person.如:
alert(person1.constructor == Person)//true
5. 但检测对象类型,还是instanceof操作符更可靠一些,person1和person2即是Object的实例,又是Person的实例
alert(person1 instanceof Object);//true alert(person1 instanceof Person);true
创建自定义的构造函数以为着将来可以将他的实例标识为一种特定的类型;这正是构造函数模式胜过工厂模式的地方。
将构造函数当做函数
构造函数与其他函数的唯一区别,就在于调用它的方式不同;但构造函数也可以像其他函数一样被调用,且作用和其他函数一样。
//当做构造函数使用 var person = new Person("Greg",27,"Doctor"); person.sayName();//Greg //当做普通函数调用 Person("Greg",27,"Doctor");//添加到window window.sayName();//Greg //在另一个对象的作用域总调用 var o = new Object(); Person.call(o,"Greg",27,"Doctor"); o.sayName();//Greg
解释:①:不使用new操作符调用Person()会将属性和方法添加给window对象。
②:也可以使用call()(或apply())在某个特殊对象的作用域中调用Person()函数。调用后o就拥有了多有的属性和sayName()方法
构造函数的问题
主要问题:就是每个方法都要再每个实例上重新创建一遍。即person1和person2都有一个名为sayName()的方法,单那两个方法不会死同一个Function实例。因此构造
方法也可以这样写:
function Person(name,age,job){ this.name= name; this.age = age; this.job = job; this.sayName = new Function("alert(this.name)");//与声明函数在逻辑上时等价的 }
下面的代码可以证明这点:
person1 . sayName == person2.sayName ;//false
然而,创建两个完成同样任务的Function实例的确没有必要;况且有this对象在,根本不用在执行代码钱就把函数绑定在特定对象上面。
可以通过函数定义转移到构造函数外部来解决这个问题:
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); }
alert(person1.sayName == person2.sayName);//ture
这样做的新问题:
在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。且无封装性可言。
可通过原型来解决这个问题。
原型模式(prototype):
我们创建的每个函数都有一个prototype属性,这个属性是一个对象,他的用途是包含可以由特定类型的所有实例共享的属性和方法。
那么prototype就是通过调用构造函数而创建的某个对象的原型对象。
使用原型的好处:让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数总定义对象的信息,而是可以将这些信息添加到原型对象总。
例如:
function Person(){} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = functon(){ alert(this.name); } var person1 = new Person(); person1.sayName();//"Nicholas"; var person2 = new Person(); person2.sayName();//"Nicholas"; alert(person1.sayName == person2.sayName);//true
要理解运行的工作原理,必须先理解ECMAScript中原型的性质。
1.理解原型
无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性。在默认i情况下,所有prototype属性都会自动获得
一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针。就前面个例子:Person.prototype.constructor指向Person.而通过这个构造函数
我们还可继续添加其他属性和方法。
以前面使用Person构造函数和Person.prototype创建实例的代码为例,下图展示了各个对象间的关系:
1部分:在Person构造函数中,默认只包含prototype属性。
2部分:prototype也是一个对象,包含可以由特定类型的所有实例共享的属性和方法
3和4部分:当调用构造函数创建一个实例后,该实例的内部将包含一个指针,该指针指向构造函数的原型属性。
可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。如果对象的_proto_指向调用isPrototypeOf()方法的对象(Person.prototype),那么就返回true.
Person.prototype.isPrototypeOf(person1);//true
搜索对象属性时,都会执行一次搜索,目标是具有给定名字的属性,搜索首先从对象实例本身开始。如果在实例中找到了该名字的属性,则返回该属性的值;
如果没找到,则继续搜索指针事项的原型对象,在原型对象中查找具有给定名字的属性,如果找到则返回值。
可以通过对象实例访问到运行中的值,但却不能通过对象实例重写原型中的值。
例如:
function Person(){} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); var person2 = new Person(); person1.name = "Greg"; alert(person1.name);//"Greg" 来自实例 alert(person2.name);//"nicholas" 来自原型
添加的属性只能阻止我们访问原型中的那么属性,但不会修改那么属性。即使将这个属性设为null,也只会在实例中设置这个属性。
不过,使用delete操作符则而已完成删除该实例属性,从而让我们能够从新访问属性中的属性。
delete person1.name; alert(person1.name);//”Nicholas"来自原型
使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法只在给定属性存在于对象实例中时,才返回true.
function Person(){} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); var person2 = new Person(); alert(person1.hasOwnProperty("name"));//false person1.name = "Greg"; alert(person1.name);//"Greg" 来自实例 alert(person1.hasOwnProperty("name"));//true alert(person2.name);//"nicholas" 来自原型alert(person2.hasOwnProperty("name"));//false
delete person1.name; alert(person1.name);//”Nicholas"来自原型alert(person1.hasOwnProperty("name"));//false
下图展示了上面例子在不同情况下的实现与原型的关系。
2. 原型与in 操作符
有两种方式使用in操作符:单独使用和在在for-in循环中使用。
在单独使用时,in操作符会通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
function Person(){} Person.prototype.name = "Nicholas"; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); var person2 = new Person(); alert(person1.hasOwnPrototype("name"));//false alert("name" in person1);//true person1.name = "Grey"; alert(person1.name);//Grey alert(person1.hasOwnPrototype("name"));//true alert("name" in person1);//true alert(person2.name);//Nicholas alert(person2.hasOwnPrototype("name"));//false alert("name" in person1);//true同时使用hasOwnProperty()方法和in操作符,就可以确定该属性到底是存在于对象中,还是存在于原型中,如下图所示:
function hasPrototypeProperty(Object,name){ return !object.hasOwnProperty(name)&&(name in object) }在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性,其中既包括存在于实例中的属性,也包括包含于原型中的属性。
屏蔽了原型中布可枚举属性的实例属性也会在for-in循环中返回,因为根据规定,所有开发人员定义的属性都是可枚举的--ie除外。
3. 更简单的原型语法
为了从市局上更好的封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来从斜整个原型对象:如下面的例子
function Person(){} Person.prototype = { name:"Nicholas", age:29, job: "Software Engineer", sayName : function(){ alert(this.name); } }
这样的写法,有一个例外,constructor属性不再执行Person了。
这样的写法,本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),
不再指向Person函数。
尽管instanceof操作符还能反悔正确的结果,单通过constructor已经无法正确确定对象的类型了,如下所示:
var person = new Person (); alert(person instanceof Object);//true alert(person instanceof Person);//true alert(person.constructor == Person);//false alert(person.constructor == Object);//true如果constructor的值真的很重要,可以像下面这样特意将它设置回适当的值
function Person(){} Person.prototype = { constructor : Person,//特意name:"Nicholas", age:29, job: "Software Engineer", sayName : function(){ alert(this.name); } }
以上代码包含了一个constructor属性,并将它的值设置为Person,从而确保了通过该属性能够访问到适当的值。
var person = new Person (); alert(person instanceof Object);//true alert(person instanceof Person);//true alert(person.constructor == Person);//true alert(person.constructor == Object);//false
4. 原型的动态性由于在原型中查找值的过程是一次搜索,因此我们队原型对象所做的任何修改都能够立即从实例上反应出来--即使是先创建了实例后修改原型也照样如此。
例如:
var person = new Person();
Person.prototype.sayHi = functon(){
alert("hi");
}
person.sayHi();//”hi"没问题!
但是,如果是重写整个原型对象,情况就不一样了。
function Person(){} var person = new Person(); Person.prototype = { constructor: Person, name : "Nicholas", age = 29, job : "Software Engineer", sayName : function(){ alert(this.name); } } person.sayName();//error
过程内幕图:
5. 原生对象的原型
例如原生引用类型(Object,Array,String等)都在其构造函数的原型上定义了方法。
alert(typeof Array.prototype.sort);//"function alert(typeof String.prototype.substring);//function通过原生对象的原型,不仅可以取得所有默认方法的引用,而且也可以定义新方法。可以像修改自定义对象的原型一样修改原生对象的原型,
因此可以随时添加方法。例如:
String.prototype.startWith = function(){ return this.indexOf(text) == 0; } var msg = "hello world!"; alert(msg.startsWith("hello"));//true
6. 原型对象的问题
缺点一:所有实例在默认情况下都将取得相同的属性值
最大的缺点:由其共享的本性所导致的。原型中所有属性是被很多实例共享的,这种共享对于函数很合适。对于包含基本类型值的数以也说的过去
,然后对于包含引用类型值的属性来说,问题就比较突出了。例如:
function Person(){} Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", friends :["Shelby","court"], sayName : function(){ alert(this.name); } } var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends);//"Shelby,Court,Van alert(person2.friends);//"Shelby,Court,Van alert(person1.friends===person2.friends);//true
这个问题正是我们很少看到有人单独使用原型模式的原因所在。