JavaScript之常见设计模式(1)(面向对象的程序设计)

来源:互联网 发布:南京通联数据 怎么样 编辑:程序博客网 时间:2024/05/10 02:29

创建对象(设计模式)

虽然Object构造函数或对象字面量都可以用创建对象,但这样有个缺点:使用同一个接口创建许多对象,这样容易产生大量代码。为了减少一个多余的、不必要的代码,我们在创建对象时,可以使用一些常见的设计模式来创建对象。

工厂模式

工厂模式抽象了创建具体对象的过程用函数来封装 以特定接口 创建对象 的细节。(封装细节--特定接口创建相似对象)

将原始方法封装到函数,并返回这个对象。返回对象的引用。

function gcms (name, age, job) {    var obj = new Object(); //新创建一个对象    //为对象添加属性    obj.name = name;    obj.age = age;    obj.job = job;    //为对象添加方法    obj.sayName = function () {        return this.name;    };    return obj; //返回obj对象引用}//将传入了参数的返回值即obj对象赋值给变量person1,此时person1也成为了一个对象。var person1 = gcms("tom", 21, "web");console.log(person1); //返回person1对象的所有信息console.log(person1.name); //返回person1对象的属性name的值21//将传入了参数的返回值即obj对象赋值给变量person1,此时person2也成为了一个对象。var person2 = gcms("bob", 30, "Doctor");console.log(person2.age); //30


用函数来封装 以特定接口 创建对象 的细节。避免使用过多的代码创建 多个相似 的对象,用函数将相似对象的相似部分封装起来,如相同的属性名name、age、job。相同的方法名sayName等。函数gcms()能够根据接收的参数来创建包含所有信息的对象,可以无数次调用这个函数,每次都会返回包含三个属性和一个方法的对象。

工厂模式:将参数传入函数后,直接赋值给变量即可。

优点:工厂模式解决了 创建 多个相似对象 而使用过多代码 的问题。即 创建 同一类型同属性 的对象可以多次调用
缺点创建同一类型不同属性时,就不能多次调用了,还不能知道所创建对象(实例)的类型。


构造函数模式

构造函数可以用创建 特定类型的实例对象,像Array和Object这样的原生构造函数。还可以创建自定义的构造函数,从而定义 自定义对象类型的属性和方法。

用构造函数将前面的例子重写一次:

function Gzhs (name, age, job) {    //为对象添加属性    this.name = name;    this.age = age;    this.job = job;    //为对象添加方法    this.sayName = function () {         return this.name;    };}var person1 = new Gzhs("tom", 21, "web");console.log(person1.name); //tomconsole.log(person1.age); //21console.log(person1.job); //21console.log(person1 instanceof Object); //可以知道person1的类


用构造函数可以创建person1的副本,当有多个由此构造函数创建的实例对象时,它们是相互独立的,改变其中一个实例,另一个实例不会有影响。由此特性,可以与原型模式结合使用创建构造函数,那么它们的实例对象的属性就有副本,不会相互影响,共享的只是方法。

这是一个自定义的构造函数Gzhs,构造函数的首写字母就是大写,非构造函数的首字母为小写,只是为了区分,构造函数也是函数,只不过可以用来创建对象而已。

构造函数模式与工厂模式相比较:

1、没有 显式地 创建对象。即 var obj = Object();

2、直接将属性和方法给了this对象,this对象也代表了作用域。

3、没有了return语句。

要创建Gzhs的 新实例,必须使用new关键字创建,这样会经历以下四个步骤:var person1 = new Gzhs();

1、创建一个新对象。

2、将构造函数的作用域赋给新对象(this对象就指向了这个新对象

3、执行构造函数中的代码(为这个新对象添加属性,也就是构造函数中的属性)

4、返回新对象。


构造函数模式比工厂模式:

构造函数模式可以知道它的实例对象的类型,也就是说可以将其实例标识为一种特定的类型(也就是构造函数名类型)。如以上的例子,实例对象person1属于Gzhs类型。


对构造函数Gzhs()的实例person1可以用instanceOf来检测其类型:

console.log(person1 instanceOf Object); //trueconsole.log(person1 instanceOf Gzhs); //true


person1作为Gzhs()的实例,其内部有个constructor(构造属性)是一个指针,指向构造函数Gzhs的。


可以将 自定义的构造函数的实例 标识为一种特定的类型。


以上person1和person2之所以同时是Object的实例,是因为 所有对象都继承自Object类。所有对象的原型均是Object的实例,其均指向Object类。


将构造函数当作函数

构造函数与其它函数的唯一区别就是 调用的方式不同。任何函数,只要通过new来调用就是构造函数,如果不通过new来调用,那它就是一个普通函数

//当作构造函数使用var person = new Person("Jon", 25, "Doctor");peron.sayName(); // Jon//当作普通函数使用Person("Greg", 27, "Doctor"); //全局函数,this对象指向window对象,属性和方法均被添加到window对象里window.sayName(); //Greg  //在另一个函数中作用域中调用var o = new Object();Person.call(o, "year", 25); //O对象拥有Person对象的所有属性和方法。o.sayName(); //year o.sayName()==》Person.sayName()

最后一个使用方式,使用call方法在对象o的作用域中调用Person函数,这样o对象就拥有了Person的所有属性和方法。也就是说用Person对象来替换o对象,这样o对象就可以使用Person对象的所有属性和方法了。


构造函数的问题

构造函数的问题之一每个方法 都要在 每个实例上重新创建一遍。上述例子的person1和person2都有一个名为sayName的方法,这两个实例都要重新创建一遍这个方法,但两个方法不是同一个Function实例。函数就是对象,每定义一个函数,也就是实例化一个对象。也就是说定义了两个函数,却做相同的事情(返回name属性值)。还不如让person1和person2共享一个方法去做同样的事。

从逻辑上讲,此时的构造函数也可以这样定义:
function Person (name, age, job) {    this.name = name;    this.age = age;    this.job = job;    this.sayName = new Function("alert(this.name)"); //与声明函数在逻辑上是等价的。}


从上可以看出 每个Person实例都包含一个不同的Function实例。以这种方式创建函数,会导致不同的 作用域链和标识符解析。因此,不同实例 的 同名函数 是 不相等 的

以下代码可以证明这点:
console.log(person1.sayName == person2.sayName); //false



然而,创建 两个完成 同样任务的Function实例是没有必要的;有this对象在,不用在 执行代码前 就把函数绑定到特定对象上面。可以通过把函数转移到构造函数外部来解决这个问题,也就是将这个方法变成全局作用域下的方法。
 
function Person (name, age, job) {    this.name = name;    this.age = age;    this.job = job;    this.sayName = sayName; //将全局作用域下的函数的引用赋值给this.sayName,这样就指向了全局函数。}//定义一个全局函数,通过指针让实例共享这个函数。function sayName () {    alert(this.name);}var person1 = new Person("Tom", 21, "Web");var person2 = new Person("Bob", 20, "Doctor");

 
我们把sayName()函数转移到构造函数外部来,即把它作为全局作用域下的函数,在构造函数内部,我们将this.sayName属性设置为等于全局的sayName函数,由于sayName包含的是指向函数的指针(将指向一个某函数的指针sayName赋给了this.sayName属性,那么this.sayName属性也就是一个指向全局函数的指针),这样person1和person2对象就相当于共享了在全局作用域中的sayName()函数了,这样就解决了两个函数做同一个件事的问题。


构造函数模式的新问题就是:如果需要定义多个方法,则要在全局作用域下定义多个全局函数,于是我们这个自定义的引用类型就没有封装性可言了。

思考:怎样不用在全局作用域下定义方法,又能让构造函数的实例对象共享此方法呢?

没错,为了解决这个问题,就有了原型模式。


原型模式

我们创建的每个函数都有一个Prototype(原型)属性这个属性是一个指针指向一个函数(原型函数prototype)这个函数的作用就是:包含由特定类型(包含原型对象的函数)的实例共享的 属性和方法。简单点说就是 原型对象包含了实例对象所共享的属性和方法。使用原型对象的好处就是:可以让所有对象实例共享属性和方法。不必在构造函数中定义 对象实例 的信息,而是将这些信息直接封装到prototype原型对象中。

function Person () {    //将属性添加到原型对象中。    Person.prototype.name = "Tom";     Person.prototype.age = 20;    Person.prototype.job = "Doctor";    //将方法添加到原型对象中。    Person.prototype.sayName = function () {        alert(this.name);    };}var person1 = new Person(); //创建一个实例对象person1.sayName(); //"Tom"  // person1访问了原型对象中的属性和方法。var person2 = new Person();person2.sayName(); //"Tom"console.log(person1.sayName == person2.sayName); //true person1和person2共享原型对象中的属性和方法,共享的方法只创建一次即

如上,创建了一个构造函数Person(一般构造函数的函数名首字母为大写),并将一些属性和方法直接添加到了原型对象中,此时Person成为了空函数,但还是可以创建Person的实例person1和person2,这两个实例拥有了相同的属性和方法,person1.sayName()弹出"Tom",person2.sayName()弹出"Tom",此时person1和person2的方法和属性是所有实例共享的,也就是说person1和person2访问的都是同一组属性和同一个sayName()方法。测试"person.sayName() == person2.sayName()" 返回true。那么person1和person2有了同一方法去做相同的事情(返回name属性值)。


原型模式解决了构造函数的问题即:全局函数只能被某个对象调用、需要多个方法时就需定义多个全局函数。而使用原型模式,方法就可以共享,且方法也不用在全局作用域中定义了。


理解原型对象

无论什么时候,只要创建一个新函数,就会根据特定规则为该函数创建一个prototype属性,该属性是一个指针,指向该函数的原型对象在默认情况下,原型对象就会创建一个constructor(构造函数)属性,该属性指向prototype属性所在的函数。就拿上一个例子说,Person.prototype.constructor指向Person,通过这个 构造函数,可以为原型对象添加其它的属性和方法。

当调用由构造函数创建的 实例后,该 实例内部 就包含一个指针(内部属性[[prototype]]或_proto_属性),指向原型对象要明确重要的一点就是:[[prototype]]这个连接点存在于实例与构造函数的原型对象之间,不是存在于实例与构造函数之间。也就是说可以通过某个方法(isPrototyOf())判断实例与原型对象之间是否有[[prototype]]属性。




在图中,展示了构造函数Person、构造函数的原型对象、构造函数的两个实例person1、person2之间的关系。其中,person.prototype指向prototype对象,person.prototype.constructor指向构造函数Person,两个实例对象中包含的内部属性[[prototype]]均紧紧指向了构造函数的原型对象prototype。也就是说它们与构造函数Person没有直接的关系。

要注意的是:这两个实例虽然都没有属性和方法,但它们可以使用person1.sayName()和person2.sayName(),这是通过查找属性来实现的。


虽然在实现中无法访问到[[prototype]]这个属性,但可以通过isPrototypeof()这个方法来确定对象(实例 与 原型对象)之间有没有这种关系。从本质上讲,[[prototype]]指向调用ifPrototypeOf()方法的person.prototype,那么这个方法就会返回true。

console.log(Person.prototype.isPrototypeOf(person1)); //trueconsole.log(Person.prototype.isPrototypeOf(person2)); //true

我们用isPrototypeOf()检测了person1和person2,因为它们内部都有个指向Person.prototype的指针,所以返回了true。


我们可以通过Object.getPrototypeOf()方法返回某实例的内部属性[[prototype]]的值,即 取得一个对象的原型对象可以得到原型对象的属性

console.log(Object.getPrototypeOf(person1) == Person.prototype); //trueconsole.log(Object.getPrototypeOf(person1).name); //Tomconsole.log(Object.getPrototypeOf(person1).age); //20console.log(Object.getPrototypeOf(person1).job); //Doctor

第一行代码只是确定返回的对象是否是这个对象的原型。第三、四、五行代码取得了原型对象的属性。


查找属性、方法的过程:每当代码读取某个对象的某个属性和方法时,都会进行一次搜索,搜索从目标对象本身开始,判断目标是否具有给定名字的属性,如果有给定名字的属性就返回其属性的值,如果没有就会 继续搜索指针指向的原型对象,在原型对象中查找给定名字的属性,如果找到就返回其属性的值。


上例中,我们调用person1.sayName()方法时,解析器就会问:person1本身是否有sayName()方法,答:没有,然后继续搜索person1内部属性指向的原型对象,再问:person1的原型有吗?答:有。于是我们就可以调用person1.sayName()方法了。


注:prototype原型对象创建的constructor属性也是共享的,可以通过实例访问到,返回的结果就是:function person(),因为constructor就指向prototype所在的构造函数。

console.log(person1.constructor); //function Person() 构造属性


以上例子的所有代码及返回的结果:

function Person () {    //将属性及其的值添加到原型对象中。    Person.prototype.name = "Tom";     Person.prototype.age = 20;    Person.prototype.job = "Doctor";    //将方法添加到原型对象中。    Person.prototype.sayName = function () {        alert(this.name);    };}var person1 = new Person(); //创建一个实例person1.sayName(); //"Tom"console.log(person1.constructor); //function Person()var person2 = new Person();person2.sayName(); //"Tom"console.log(person1.sayName == person2.sayName); //true//确定实例与原型对象是否有这种关系console.log(Person.prototype.isPrototypeOf(person1)); //trueconsole.log(Person.prototype.isPrototypeOf(person2)); //true//确定实例person1指向的原型对象是不是Person的原型对象console.log(Object.getPrototypeOf(person1) == Person.prototype); //true//返回所指向的原型对象的属性console.log(Object.getPrototypeOf(person1).name); //Tomconsole.log(Object.getPrototypeOf(person1).age); //20console.log(Object.getPrototypeOf(person1).job); //Doctor


返回的结果:




虽然我们可以通过对象实例来 访问原型对象中 的属性值和调用原型对象中的方法,但却不能通过对象实例来重写原型对象的属性值。如果我们通过构造函数创建了一个实例,该实例可以访问其原型对象中的属性和方法,但如果我们这时给实例添加一个新属性或重写属性,其属性名与原型对象中的属性同名,则实例中的属性会屏蔽掉原型对象的属性。就会阻止访问原型对象中的同名属性。

function Person () {    //将属性及其的值添加到原型对象中。    Person.prototype.name = "Tom";     Person.prototype.age = 20;    Person.prototype.job = "Doctor";//将方法添加到原型对象中。Person.prototype.sayName = function () {        alert(this.name);    };}var person1 = new Person(); //创建一个实例person1.name = "Bob"; //会屏蔽掉原型对象中同属性名的属性。var person2 = new Person();console.log(person1.name); //Bob--来自实例本身添加的属性console.log(person2.name); //Tom--来自原

返回结果:



为某个实例新添加或重写属性不会反应到原型对象中,此时我们通过这个实例访问原型对象中的同名属性,返回的就是实例新添加或重写的那个属性的属性值。重写或新添加的属性只是屏蔽了原型对象中同名的属性的访问,但此时,我们可以通过另一个实例来访问原型对象中的那个属性。


这是属性查找的结果。

person1中的name被新值屏蔽了(替代),屏蔽了来自原型对象中name属性的值,但也会正常返回,只不过是新值"Bob"。当在访问person1.name时,解析器就会搜索person1的name属性,当在person1实例本身内部找到时,就会返回其属性值,就不会去找原型对象中的属性了。而对于person2就没有那么幸运了,在person2本身实例中没有找到给定属性名的属性(因为并没有为person2添加同属性名的属性),就会继续在其原型对象中搜索查找,结果在原型对象中找到了其属性,于是返回了属性值"Tom"。


当为某个实例添加属性时,这个属性就会屏蔽原型对象中相同属性名的属性,也就是说 添加这个属性只会 阻止访问原型对象中 与其相同属性名 的属性,但不会改变原型对象的属性,即使给实例的属性设置null。如果我们用delete操作符删除 对象实例中添加的那个属性,那么实例就会继续访问原型对象中的属性了。

function Person () {    //将属性及其的值添加到原型对象中。    Person.prototype.name = "Tom";     Person.prototype.age = 20;    Person.prototype.job = "Doctor";//将方法添加到原型对象中。Person.prototype.sayName = function () {        alert(this.name);    };}var person1 = new Person(); //创建一个实例person1.name = "Bob"; //会屏蔽掉原型对象中同属性名的属性。var person2 = new Person();console.log(person1.name); //Bob--来自实例本身添加的属性console.log(person2.name); //Tom--来自原型对象中的属性delete person1.name; //删除person1中新添加的属性console.log(person1.name); //Tom--来自原型对象中的属性

返回结果:


可以看出,通过delete操作符删除实例重写或新添加的属性后,实例就可以访问对象中的同名属性了。相当于delete撤消了屏蔽、打开了实例与原型对象之间是的连接点(_proto_属性)。



hasOwnProperty()

使用hasOwnProperty()方法来确定 给定属性 是存在于 实例对象中,还是存在于 原型对象中当给定属性是存在于实例对象中时返回true

格式:实例.hasOwnProperty("给定属性名");

function Person () {    //将属性及其的值添加到原型对象中。    Person.prototype.name = "Tom"; Person.prototype.age = 20;Person.prototype.job = "Doctor";//将方法添加到原型对象中。Person.prototype.sayName = function () {    alert(this.name);};}var person1 = new Person(); //创建一个实例person1.name = "Bob";console.log(person1.hasOwnProperty("name")); //trueconsole.log(person1.name); //Bob--来自实例本身添加的属性var person2 = new Person();console.log(person2.name); //Tom--来自原型对象中的属性    console.log(person2.hasOwnProperty("name")); //falsedelete person1.name;console.log(person1.hasOwnProperty("name")); falseconsole.log(person1.name); //Tom--来自原型对象中的属性

效果:



当用delete操作将person1中的属性删除掉后,属性name就不再属性实例person1了,而是属于原型对象,所以返回false。



原型与in操作符

in操作符有两种用法:第一种就是for-in遍历对象属性并返回属性。第二种单独使用in操作符时in操作符通过 对象 能够访问 给定属性时返回true,无论该属性是在实例对象中,还是在原型对象中。确定给定属性是否在实例对象或原型对象中。如果在,返回true。

in操作符

格式:"给定属性名" in 实例对象

function Person () {    //将属性及其的值添加到原型对象中。    Person.prototype.name = "Tom"; Person.prototype.age = 20;Person.prototype.job = "Doctor";//将方法添加到原型对象中。Person.prototype.sayName = function () {    alert(this.name);};}var person1 = new Person(); //创建一个实例person1.name = "Bob";console.log(person1.hasOwnProperty("name")); //trueconsole.log(person1.name); //Bob--来自实例本身添加的属性//当name属性存在于person1对象中时console.log( "name" in person1); //truevar person2 = new Person();console.log(person2.name); //Tom--来自原型对象中的属性    console.log(person2.hasOwnProperty("name")); //falseconsole.log("name" in person2); //truedelete person1.name;console.log(person1.hasOwnProperty("name")); falseconsole.log(person1.name); //Tom--来自原型对象中的属性//当name属性从person1中删除时console.log("name" in person1); //true

效果:



以上代码,name属性要么是在实例对象中访问到的,要么就是在原型对象中访问到的。均返回true。


hasOwnproperty()和in组合使用

当我们将hasOwnProperty()方法和in操作一起使用的时候,我们就能确定给定属性到底存在否?如果给定属性是存在的,是存在于实例对象中,还是存在于原型对象中。


格式:function hasOwnPropertyIn (object, name) {

               hasOwnProperty(name)  && return (name in object) 

}


代码如下:

//判断函数function hasOwnPropertyIn (object, name) {        return object.hasOwnProperty(name) && (name in object);}function Person () {    //将属性及其的值添加到原型对象中。    Person.prototype.name = "Tom"; Person.prototype.age = 20;Person.prototype.job = "Doctor";//将方法添加到原型对象中。Person.prototype.sayName = function () {    alert(this.name);};}var person1 = new Person(); //创建一个实例person1.name = "Bob";console.log(hasOwnPropertyIn(person1, "name")); //truevar person2 = new Person();console.log(person2.name); //Tom--来自原型对象中的属性    console.log(hasOwnPropertyIn(person2, "name")); //falsedelete person1.name;console.log(person1.name); //Tom--来自原型对象中的属性console.log(hasOwnPropertyIn(person1, "name")); //false

效果:



hasOwnProperty()方法当name属性存在于实例对象中时,返回true,存在于原型对象中时,返回false。in操作符,只要实例对象中或原型对象中存在name属性就会返回true。



for-in操作符

for-in循环操作符遍历并返回可通过对象访问的、可枚举的属性。无论该属性是存在于实例对象中,还是存在于原型对象中。但此方法也会返回屏蔽了原型中不可枚举(即将[[enumerable]]标记为false的属性)的实例属性。因为,开发人员定义的属性均是可枚举的。

Object.keys()方法

想要取得实例对象本身上所有 可枚举的属性。可以使用Object.keys()方法。该方法返回的是一个包含字符串的数组,数组元素就是可枚举的对象属性

特别注意Object.keys() 方法只返回 对象本身 的所有可枚举属性不会返回来自原型对象的属性

格式:Object.keys(某实例对象);

function Person () {    //将属性及其的值添加到原型对象中。    Person.prototype.name = "Tom"; Person.prototype.age = 20;Person.prototype.job = "Doctor";//将方法添加到原型对象中。Person.prototype.sayName = function () {    alert(this.name);};}var key = Object.keys(Person.prototype);console.log(key); //[] 空数组 此时Person本身为空函数,没有属性。var keys = new Person();keys.name = "Bob";keys.age = 30;console.log(Object.keys(keys)); //["Bob", "30"] 此时keys对象本身有添加的属性name和age

效果:





Object.getOwnPropertyNames()方法

想要取得对象所有的属性(不管可枚举还是不可枚举)。可以使用Object.getOwnPropertyNames()方法。该方法返回的是一个字符串数组,数组元素就是对象属性

该方法会返回一个原型对象中不可枚举的属性"constructor"。

function Person () {    //将属性添加到原型对象中。    Person.prototype.name = "Tom";     Person.prototype.age = 20;    Person.prototype.job = "Doctor";    //将方法添加到原型对象中。    Person.prototype.sayName = function () {        alert(this.name);    };}var person = new Person(); //实例化一个对象//获取原型对象中的所有属性(不管是可枚举还是不可枚举)var keys = Object.getOwnPropertyNames(Person.prototype);console.log(keys);

效果:



对于遍历构造函数:

function Person () {    //将属性添加到原型对象中。    Person.prototype.name = "Tom";     Person.prototype.age = 20;    Person.prototype.job = "Doctor";    //将方法添加到原型对象中。    Person.prototype.sayName = function () {        alert(this.name);    };}//获取构造函数中所有属性(不管是可枚举还是不可枚举)var keys = Object.getOwnPropertyNames(Person);console.log(keys);

效果:




简化原型语法

如前面,每添加一个属性和方法,就要敲一遍Person.Prototype,简化一下,就可以省去一些重复的工作。常见的是用一个包含所有属性和方法的对象字面量来重写原型对象

也就是说,这种方法是重写原型对象。

function Person () {} //先定义一个空构造函数//重写原型对象Person.prototype = {    //将属性添加到原型对象中    name : "Tom",    age : 20,    job : "Doctor",    //将方法添加到原型对象中    sayName : function () {        alert(this.name);    }};


如果此时创建一个实例,例:person1,那么"person1.constructor == Person"会返回 false,我们知道,constructor是原型对象中的一个属性,它指向构造函数的。但在这里,我们在本质上已经重写了默认的protoytpe对象,变成了新对象的prototype属性,已经指向了Object构造函数了。


思考:

function Person () {} //定义一个空函数//为其原型对象添加属性和方法Person.prototype = {     name : "Tom",     age : 20,     job : "Doctor",     sayName : function () {alert(this.name);     }};console.log(Object.keys(Person)); //返回[],Person为空函数,没有属性。console.log(Object.keys(Person.prototype)); //返回[],Person为原型函数。console.log(Object.getOwnPropertyNames(Person.prototype)); //["constructor"]var keys = new Person(); //实例化一个对象keys.name = "Bob"; //新添加属性keys.age = 30;console.log(Object.keys(keys)); //["Bob", "30"] 此时keys对象本身有属性name和ageconsole.log(Object.keys(Person.prototype)); //["name", "age", "job", "sayName"] 原型对象中有属性。console.log(Object.getOwnPropertyNames(Person.prototype)); //["constructor", "name", "age", "job", "sayName"]





以上为什么会出现这个情况?keys()方法不是只返回对象本身拥有的可枚举的属性,不返回原型对象的属性吗? getOwnPropertyNames()方法也是。

解析:此时用Person()构造函数实例化一个对象就行。


原型的动态性

在原型对象中查找值是一次搜索,所以当我们修改原型对象中的值也会即时地在实例对象中即时反应出来。即使是先创建了实例后再修改原型对象中的值。

function Person () {}Person.prototype = {    name : "Tom",    age : 20,    job : "Doctor",    sayName : function () {        alert(this.name);    }};var person = new Person(); //先创建一个实例对象Person.prototype.add = "hi"; //再为原型对象添加新的属性console.log(person.add); //hi 还是能即时反应出来

因为在执行"console.log(person.add)'这段代码时,解析器会先搜索person是否有add这个属性,如果没找到,则继续查找原型对象中是否有这个名称的属性,因为实例对象与原型对象之间的连接点只不过是一个指针([[constructor]])不是一个固定的副本,(原型对象修改的值能即时反馈回来)所以能查找到修改的属性。查找到后返回该属性的值。


我们知道,随时为原型对象添加属性和方法,都能从实例中反应出来。但如果是重写了原型对象,那么用实例再去调用属性和方法就会出错。因为重写原型就断开了实例与原型之间的联系(_proto_属性)。重写之后的原型变成了另外构造函数的原型了。

function Person () {    Person.prototype = {        name : "Tom",        age : 21    };}var person = new Person();//重写原型对象Person.prototype = {    sayName : function () {alert("hi")}};console.log(person.name); //undefinedperson.sayName(); //error


原生对象的原型

原型对象的强大不仅体现在创建自定义类型(包括构造函数)上,就连原生的引用类型,就是采用的这种模式创建的。所有的原生引用类型(Array、String、Object、Date、function等)都是采用这种模式创建的。比如:在Array.prototype中的sort()方法,而在String.prototype中的subString()方法等.,当然,不推荐为原生引用类型的原型添加方法和属性。


原型模式的问题

问题1:原型模式中的构造函数没有参数,导致所有的实例对象在被创建时都将取得相同的属性值。

问题2:对于 包含引用类型值的属性来说,就是一个大问题。

function Person () {}Person.prototype = {    name : "Tom",    age : 20,    job : "Doctor",    color : ["red", "blue", "yellow"],    sayName : function () {        alert(this.name);    }};var person1 = new Person();var person2 = new Person();person1.color.push("orange"); //原意是为person1这个实例的color属性再添加一个值orangeconsole.log(person1.color); //["red", "blue", "yellow", "orange"] 好嘛,这是自己想要的结果console.log(person2.color); //["red", "blue", "yellow", "orange"] 这是什么情况?没给person2添加属性值啊!!!

返回结果:



从这个例子可以看出,我们原意是为person1.color再添加一个属性值,但实际上color数组是存在于Person.prototype中而不是person1中的,刚才的修改会通过指针即时传向原型对象中,person1与person2指向同一个对象同一个数组,所以刚才的修改也会通过person2.color反应出来。


实例一般要有属于自己的全部属性才算是不同的实例吧。


1 0
原创粉丝点击