用过JavaScript的同学们肯定都对prototype如雷灌耳,但是这究竟是个甚么东西却让初学者莫衷1是,只知道函数都会有1个prototype属性,可以为其添加函数供实例访问,其它的就不清楚了,最近看了1些 JavaScript高级程序设计,终究揭开了其神秘面纱。
每一个函数都有1个prototype属性,这个属性是指向1个对象的援用,这个对象称为原型对象,原型对象包括函数实例同享的方法和属性,也就是说将函数用作构造函数调用(使用new操作符调用)的时候,新创建的对象会从原型对象上继承属性和方法。不像传统的面向对象语言,Javascript的继承机制基于原型,而不是Class类。
在具体说prototype前说几个相干的东东,可以更好的理解prototype的设计意图。在了解JavaScript原型链之前,有必要先了解1下JavaScript的作用域链。JavaScript的函数作用域,在函数内定义的变量和函数如果不对外提供接口,那末外部将没法访问到,也就是变成私有变量和私有函数。
这样在函数对象Obj外部没法访问变量a和函数fn,它们就变成私有的,只能在Obj内部使用,即便是函数Obj的实例依然没法访问这些变量和函数
当定义1个函数后通过 “.”为其添加的属性和函数,通过对象本身依然可以访问得到,但是其实例却访问不到,这样的变量和函数分别被称为静态变量和静态函数,用过Java、C#的同学很好理解静态的含义。
在面向对象编程中除1些库函数我们还是希望在对象定义的时候同时定义1些属性和方法,实例化后可以访问,JavaScript也能做到这样
这样可以到达上述目的,但是
上面的代码运行结果完全符合预期,但同时也说明1个问题,在o1中修改了a和fn,而在o2中没有改变,由于数组和函数都是对象,是援用类型,这就说明o1中的属性和方法与o2中的属性与方法虽然同名但却不是1个援用,而是对Obj对象定义的属性和方法的1个复制。
这个对属性来讲没有甚么问题,但是对方法来讲问题就很大了,由于方法都是在做完全1样的功能,但是却又两份复制,如果1个函数对象有上千和实例方法,那末它的每一个实例都要保持1份上千个方法的复制,这明显是不科学的,这可肿么办呢,prototype应运而生。先看看对象的含义:
JavaScript 中,万物皆对象!但对象也是有区分的。分为普通对象和函数对象,Object ,Function 是JS自带的函数对象。下面举例说明
在上面的例子中 o1 o2 o3 为普通对象,f1 f2 f3 为函数对象。怎样辨别,其实很简单,凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。 f1,f2,归根结柢都是通过 new Function()的方式进行创建的。Function Object 也都是通过 New Function()创建的。
在JavaScript 中,每当定义1个对象(函数)时候,对象中都会包括1些预定义的属性。其中函数对象的1个属性就是原型对象 prototype。注:普通对象没有prototype,但有_ proto _属性。
原型对象其实就是普通对象(Function.prototype除外,它是函数对象,但它很特殊,他没有prototype属性(前面说道函数对象都有prototype属性))。看下面的例子:
从这句console.log(f1.prototype) //f1 {} 的输出就结果可以看出,f1.prototype就是f1的1个实例对象(这里就是f1的原型对象)。就是在f1创建的时候,创建了1个它的实例对象并赋值给它的prototype,基本进程以下:
所以,Function.prototype为何是函数对象就迎刃而解了,上文提到凡是new Function ()产生的对象都是函数对象,所以temp1是函数对象。
那原型对象是用来做甚么的呢?主要作用是用于继承。举了例子:
从这个例子可以看出,通过给person.prototype设置了1个函数对象的属性,那有person实例(例中:xpg)出来的普通对象就继承了这个属性。具体是怎样实现的继承,就要讲到下面的原型链了。
在深入的讲1遍:不管甚么时候,只要创建了1个新函数,就会根据1组特定的规则为该函数创建1个prototype属性(同时它也是1个对象),默许情况下prototype属性(对象)会默许取得1个constructor(构造函数)属性,这个属性是1个指向prototype属性所在函数的指针,有些绕了啊,写代码、上图!
根据上图可以看出Person对象会自动取得prototyp属性,而prototype也是1个对象,会自动取得1个constructor属性,该属性正是指向Person对象。
当调用构造函数创建1个实例的时候,实例内部将包括1个内部指针(很多阅读器这个指针名字为_ proto _ )指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间,而不是实例与构造函数之间。
Person的实例person1中包括了name属性,同时自动生成1个_ proto _属性,该属性指向Person的prototype,可以访问到prototype内定义的printName方法,大概就是这个模样的:
写段程序测试1下看看prototype内属性、方法是能够同享
果不其然!实际上当代码读取某个对象的某个属性的时候,都会履行1遍搜索,目标是具有给定名字的属性,搜索首先从对象实例开始,如果在实例中找到该属性则返回,如果没有则查找prototype,如果还是没有找到则继续递归prototype的prototype对象,直到找到为止,如果递归到object依然没有则返回毛病。一样道理如果在实例中定义如prototype同名的属性或函数,则会覆盖prototype的属性或函数。―-这就是Javascript的原型链。
JS在创建对象(不论是普通对象还是函数对象)的时候,都有1个叫做_ proto _的内置属性,用于指向创建它的函数对象的原型对象prototype。以上面的例子
一样,person.prototype对象也有_ proto _属性,它指向创建它的函数对象(Object)的prototype
继续,Object.prototype对象也有_ proto _属性,但它比较特殊,为null
这个有_ proto _ 串起来的直到Object.prototype._ proto _为null的链叫做原型链。以下图:
原型链中属性查找:
当查找1个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部 - 也就是 Object.prototype - 但是依然没有找到指定的属性,就会返回 undefined,我们来看1个例子:
通过代码运行,我们发现subtract是安装我们所说的向上查找来得到结果的,但是add方式有点小不同,这也是我想强调的,就是属性在查找的时候是先查找本身的属性,如果没有再查找原型,再没有,再往上走,1直插到Object的原型上,所以在某种层面上说,用 for in语句遍历属性的时候,效力也是个问题。
还有1点我们需要注意的是,我们可以赋值任何类型的对象到原型上,但是不能赋值原子类型的值,比如以下代码是无效的:
实例就是通过构造函数创建的。实例1创造出来就具有constructor属性(指向构造函数)和proto属性(指向原型对象),
构造函数中有1个prototype属性,这个属性是1个指针,指向它的原型对象。
原型对象内部也有1个指针(constructor属性)指向构造函数:Person.prototype.constructor = Person;
实例可以访问原型对象上定义的属性和方法。
在这里person1和person2就是实例,prototype是他们的原型对象。
再举个栗子:
原型使用方式1:
在使用原型之前,我们需要先将代码做1下小修改:
var Calculator = function (decimalDigits, tax) { this.decimalDigits = decimalDigits; this.tax = tax; };
然后,通过给Calculator对象的prototype属性赋值对象字面量来设定Calculator对象的原型。
我们就能够new Calculator对象以后,就能够调用add方法来计算结果了。
原型使用方式2:
第2种方式是,在赋值原型prototype的时候使用function立即履行的表达式来赋值,即以下格式:
它的好处在前面的Item里已知道了,就是可以封装私有的function,通过return的情势暴露出简单的使用名称,以到达public/private的效果,修改后的代码以下:
一样的方式,我们可以new Calculator对象以后调用add方法来计算结果了。
分步声明:
上述使用原型的时候,有1个限制就是1次性设置了原型对象,我们再来讲1下如何分来设置原型的每一个属性吧。
//使用原型给BaseCalculator扩大
声明了1个BaseCalculator对象,构造函数里会初始化1个小数位数的属性decimalDigits,然后通过原型属性设置2个function,分别是add(x,y)和subtract(x,y),固然你也能够使用前面提到的2种方式的任何1种,我们的主要目的是看如何将BaseCalculator对象设置到真实的Calculator的原型上。
重写原型:
在使用第3方JS类库的时候,常常有时候他们定义的原型方法是不能满足我们的需要,但是又离不开这个类库,所以这时候候我们就需要重写他们的原型中的1个或多个属性或function,我们可以通过继续声明的一样的add代码的情势来到达覆盖重写前面的add功能,代码以下:
这样,我们计算得出的结果就比原来多出了1个tax的值,但是有1点需要注意:那就是重写的代码需要放在最后,这样才能覆盖前面的代码。
hasOwnProperty是Object.prototype的1个方法,它可是个好东西,他能判断1个对象是不是包括自定义属性而不是原型链上的属性,由于hasOwnProperty 是 JavaScript 中唯逐一个处理属性但是不查找原型链的函数。
只有 hasOwnProperty 可以给出正确和期望的结果,这在遍历对象的属性时会很有用。 没有其它方法可以用来排除原型链上的属性,而不是定义在对象本身上的属性。
但有个恶心的地方是:JavaScript 不会保护 hasOwnProperty 被非法占用,因此如果1个对象碰巧存在这个属性,就需要使用外部的 hasOwnProperty 函数来获得正确的结果。
当检查对象上某个属性是不是存在时,hasOwnProperty 是唯1可用的方法。同时在使用 for in loop 遍历对象时,推荐总是使用 hasOwnProperty 方法,这将会避免原型对象扩大带来的干扰,我们来看1下例子:
我们没办法改变for in语句的行动,所以想过滤结果就只能使用hasOwnProperty 方法,代码以下:
这个版本的代码是唯1正确的写法。由于我们使用了 hasOwnProperty,所以这次只输出 moo。如果不使用 hasOwnProperty,则这段代码在原生对象原型(比如 Object.prototype)被扩大时可能会出错。
总结:推荐使用 hasOwnProperty,不要对代码运行的环境做任何假定,不要假定原生对象是不是已被扩大了
_ ptoto _属性
_ ptoto _属性(IE阅读器不支持)是实例指向原型对象的1个指针,它的作用就是指向构造函数的原型属性constructor,通过这两个属性,就能够访问原型里的属性和方法了。
Javascript中的对象实例本质上是由1系列的属性组成的,在这些属性中,有1个内部的不可见的特殊属性――_ proto _,该属性的值指向该对象实例的原型,1个对象实例只具有1个唯1的原型。
_ proto _属性和prototype属性的区分
prototype是原型对象中专有的属性。
_ proto _ 是普通对象的隐式属性,在new的时候,会指向prototype所指的对象;
_ ptoto _ 实际上是某个实体对象的属性,而prototype则是属于构造函数的属性。_ ptoto _只能在学习或调试的环境下使用。
原型模式的履行流程
1.先查找构造函数实例里的属性或方法,如果有,就立即返回。
2.如果构造函数的实例没有,就去它的原型对象里找,如果有,就立即返回
原型对象的
构造函数的
综上,整理1下: