javascript prototype
函数(functions)在javascript中本身就是对象,它有方法和属性。关于函数的属性,prototype是比较重要的一个。
function foo(a,b){
return a*b;
}
typeof foo.prototype;//object
可以为prototype添加属性,
foo.prototype={}
这个属性对foo函数没有任何影响,仅仅当foo作为构造函数的时候。prototype才会有意义.
给prototype添加属性和方法
前几节学过的用构造函数创建对象,主要的思路还是用new操作符访问this。这个this包含了构造函数返回的对象。我们可以往this里添加属性和方法也就是,也就是给这个对象添加了属性和方法。让我们看看下列的代码
function Gadget(name,color){
this.name=name;
this.color=color;
this.whatAreYou=function(){
return 'I am a '+this.color+' '+this.name;
}
}
添加属性和方法到prototype中,是另一种给对象添加功能的方法。让我们添加下price和rating和getInfo().
Gedget.prototype.price = 100;
Gedget.prototype.rating= 3;
Gedget.prototype.getInfo=function(){
return 'Rating: '+this.rating+',price: '+this.price;
}
如果你感觉这么写很麻烦我们也可以用到以前创建对象所用到的方法
Gadget.prototype = {
price: 100,
rating: 3,
getInfo: function() {
return 'Rating: ' + this.rating + ', price: ' + this.price;
}
};
这两种方法都是一样的。
h调用Prototype的属性和方法
所有的属性和方法都可以添加到prototype中,对于对象是直接可以访问的。如果创建了一个对象,就可以访问所有的属性和方法了。
var newtoy = new Gadget('webcam','black');
newtoy.name;//webcam
newtoy.color;//black
newtoy.whatAreYou();//I am black webcam
newtoy.price;//100
newtoy.rating;//3
newtoy.getInfo();//Rating:3,price:100
对象的传递,实质上传递的是个引用,也就意味这每次创建个对象,prototype属性并不是复制而成,这样就可以动态的修改prototype的属性,所有创造的对象的属性也就会随着prototype的更改而更改。代码如下
Gadget.prototype.get=function(what){
return this[what];
}
newtoy.get('price');//100
newtoy.get('color');//'black'
newtoy对象是在get方法建立之前而生成的,但是newtoy还是可以访问get方法的。这就是prototype的妙处。
自身属性和prototype属性的对比
在上个例子中getInfo这个方法,用的是this来调用rating和price的。当然也可以用Gedget.prototype来重写这个方法
Gadget.prototype.getInfo = function() {
return 'Rating: ' + Gadget.prototype.rating + ', price: ' + Gadget.prototype.price;
};
这个上面的方法有什么不同?首先要了解prototype更多的细节问题.
var newtoy = new Gadget('webcam','black');
当访问newtoy.name的时候,Javascript引擎会检索这个对象的所有属性直到找到name的属性,并返回它的值。
newtoy.name;//webcam
如果访问rating会怎么样呢?Javascript引擎首先会检索这个对象的所有属性,发现并没有叫rating这个属性。然后再去找创造这个对象的构造函数的prototype(也就是newtoy.constructor.prototype).如果这个属性找到就返回。
newtoy.rating;//3
当然这么访问和如下代码是一样的
newtoy.constructor.prototype.rating;//3
对prototype的属性进行重写
前几个例子说明了如果没有自身的属性,就会找prototype的属性。下面引出了这样一个问题,如果自身的属性和prototype的属性都一样的话,会怎么样呢。看如下代码
function Gadget(name) {
this.name = name;
}
Gadget.prototype.name = 'foo';//foo
在创建一个新的对象
var toy = new Gadget('camera');
toy.name;//camera
发现了toy.name的值是camera.这就相对于prototype的name属性进行重写。
delete toy.name;//true
toy.name;//foo
如果删除自身属性name,prototype的属性name就生效了
当然你可以重新创建toy的属性
toy.name='camera';
toy.name;//camera
穷举属性(Enumerating Properties)
要列出对象所有的属性可以用for.in循环来做
var o = {p1: 1, p2: 2};
for (var i in o) {
console.log(i + '=' + o[i]);
}
结果为:p1=1,p2=2
但是有以下几点需要注意
并不能把对象的所有属性列出来。像constructor属性就没有。可以列出的属性我们叫它可以枚举的(enumerable),可以用propertyIsEnumerable()方法来判断属性是否可以列出来。
可以把prototype的属性列出来,如果要判断对象的自身属性可以用hasOwnProperty() 方法
虽然可以列出所有prototype的属性,但是如果使用propertyIsEnumerable()方法来校验prototype的属性,它的返回结果都是false.
让我们看个综合例子就明白了
function Gadget(name, color) {
this.name = name;
this.color = color;
this.someMethod = function(){return 1;}
}
Gadget.prototype.price = 100;
Gadget.prototype.rating = 3;
创建一个新的对象
var newtoy = new Gadget('webcam','black');
for (var prop in newtoy) {
console.log(prop + ' = ' + newtoy[prop]);
}
结果为
name = webcam
color = black
someMethod = function () { return 1; }
price = 100
rating = 3
如果要区分自身属性和prototype属性,那就用hasOwnProperty()方法
for (var prop in newtoy) {
if (newtoy.hasOwnProperty(prop)) {
console.log(prop + '=' + newtoy[prop]);
}
}
结果为
name=webcam
color=black
someMethod=function () { return 1; }
再来看一下propertyIsEnumerable的方法
newtoy.propertyIsEnumerable('name');//true
大部分内置的属性和方法都是不可以列举的
newtoy.propertyIsEnumerable('constructor');//false
任何的属性来自prototype的都不能被列举
newtoy.propertyIsEnumerable('price');//false
如果进入prototype内部去调用属性就可以被列举了
newtoy.constructor.prototype.propertyIsEnumerable('price');//true
扩展内置对象
内置对象就是由构造函数Array,String,Object。函数可以扩展它们的prototype,也就意味着你可以通过prototype添加Array的功能。
Array.prototype.inArray = function(needle) {
for (var i = 0, len = this.length; i < len; i++) {
if (this[i] === needle) {
return true;
}
}
return false;
}
现在所有的Array都有了新的方法。
var a = ['red', 'green', 'blue'];
a.inArray('red');//true
a.inArray('yellow');//false
通过prototype扩展Array的功能真是很简单的事情。
关于扩展内置对象的讨论
Prototype库是个比较有名的javascript库,它通过这种方法,把javascript变成了类似ruby的语言。但是YUI却反对这种做法。如果你会使用Javascript,一般希望用javascript正常的写法,改变了核心方法,会对开发人员造成许多困惑。
现在的javascript和浏览器功能都逐渐的增多,本身开发人员对Javascript核心的扩展,很可能明天就会没什么用处了。最佳实践是先判断核心的功能是否存在,然后再去扩展。如:
if (!String.prototype.reverse) {
String.prototype.reverse = function() {
return Array.prototype.reverse.apply(this.split('')).join('');
}
}
关于prototype令人迷惑的地方
function Dog(){
this.tail=true;
}
var benji = new Dog();
var rusty = new Dog();
Dog.prototype.say = function(){
return 'Woof';
}
benji.say();//Woof
rusty.say();//Woof
benji.constructor;//Dog()
rusty.constructor;//Dog()
benji.constructor.prototype.constructor;//Dog()
typeof benji.constructor.prototype.tail;//undefined
Dog.prototype={paws:4,hair:true};
//Dog.prototype.constructor = Dog;
typeof benji.paws;//undefined
alert(benji.say());//Woof
var lucy = new Dog();//new object
alert(lucy.paws);
alert(lucy.tail)
lucy.constructor;//Object();
benji.constructor;//Dog():
typeof lucy.constructor.prototype.paws;//undefined
typeof benji.constructor.prototype.paws;//number
新建一个构造函数并创建两个对象
function Dog(){
this.tail=true;
}
var benji = new Dog();
var rusty = new Dog();
建完对象之后,我们仍然可以添加属性到prototype中,对象可以访问这个新添加的属性
Dog.prototype.say = function(){
return 'Woof';
}
benji.say();//Woof
rusty.say();//Woof
让我们来看看创建对象的构造函数,一起看来很正常
benji.constructor;//Dog()
rusty.constructor;//Dog()
看如下代码很有意思的事情发生了。。。。。
benji.constructor.prototype.constructor;//Dog()
当我们想知道prototype的构造函数是啥的时候,问题出现了。。。结果是Dog()。但是这不完全正确。prototype对象仅仅是个Object对象,他并没有任何关于Dog的属性。。。测试看看
typeof benji.constructor.prototype.tail;//undefined
让我们继续深入下去。当完全重写prototype对象时看看会怎么样。
Dog.prototype={paws:4,hair:true};
发现了以前创造的对象竟然访问不了新的prototype的属性。。。
typeof benji.paws;//undefined
benji.say();//Woof
说明了旧的对象还是指向以前的Prototype了。
让我们新建一个对象看看会怎样
var lucy = new Dog();//new object
lucy.say();//TypeError:lucy.say is not a function
lucy.paws;//4
说明新建的对象已经指向新的prototype了。
但是lucy这个对象的构造函数变成了object
lucy.constructor;//Object();
benji.constructor;//Dog():
最令人感到困扰的是prototype的属性
typeof lucy.constructor.prototype.paws;//undefined
typeof benji.constructor.prototype.paws;//number
所以如果想完全重写prototype属性,建议用以下的方式。
Dog.prototype={paws:4,hair:true};
Dog.prototype.constructor=Dog;
这样就不会造成以上的困扰了。。。。