JavaScript中原型的使用
这篇文章来自于我的博客, 后面会慢慢的把东西整理整理移到博客上去。
每个函数都有一个prototype属性,该属性指向一个对象。而该属性只有在该函数做为构造器时才会发生作用,当函数作为构造器时,通过new去创建对象, 对象的原型(内置的prototype属性)会指向构造器(函数)的prototype属性。对象会自动拥有这个对象的构造函数的prototype的成员属性和方法.
先看一个示例:
function P () {
}
typeof P.prototype;
//"object"
//prototype指向的是一个对象
我们来修改P的prototype属性.
var protoObj = {
date: new Date(),
say: function() {
console.log('Hello World');
}
}
P.prototype = protoObj;
var obj = new P();
obj.date;
obj.say();
当我们将P作为构造器来创建obj时,obj就会拥有对P.prototype的访问权限。obj便可以将P.prototype对应的对象中的属性当作自己的属性来用。
将方法挂到类的原型上
我们定义一个构造函数,包含属性和方法,代码如下:
function Person(name, age) {
this.name = name;
this.age = age;
this.getName = function() {
return this.name;
};
}
这种方式,每次生成一个对象时会创建一个函数对象用来获取name属性
再看一种定义类的方式:
function getName() {
return this.name;
}
function Person(name, age) {
this.name = name;
this.age = age;
this.getName = getName;
}
和上一种写法相比,我们只需要创建一次方法函数,在构造函数中引用它们。更优雅的写法如下,将方法挂到原型上。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function() {
return this.name;
}
Person.prototype可以被Person的所有实例共享。JavaScript 允许在程序中的任何时候修改原型(prototype)中的一些东西,也就是说可以在运行时(runtime)给已存在的对象添加额外的方法。如给String添加一个方法来返回逆序的字符串。
var s = "Simon";
s.reversed(); // TypeError on line 1: s.reversed is not a function
String.prototype.reversed = function() {
var r = "";
for (var i = this.length - 1; i >= 0; i--) {
r += this[i];
}
return r;
}
s.reversed(); // nomiS
此部份参看这里
利用原型添加属性
有以下代码:
function Gadget(name, color) {
this.name = name;
this.color = color;
}
添加原型的两种方式:
- 属性依次添加
Gadget.prototype.price = 100;
Gadget.prototype.rating = 3;
- 添加一个对象
Gadget.prototype = {
price: 100,
rating: 3
}
原型的使用
javascript中每个对象中并没有属于自己的原型副本,我们可以随时修改prototype属性,并且由同一构造器创建的所有对象的prototype属性都会同时改变
自身属性和原型属性
var newtoy = new Gadget('test', 'red');
当我们在调用对象的某个属性时,javascript引擎会查询该对象的所有属性,如果没有找到。则会去查询创建这个对象的构造器函数的原型newtoy.constructor.prototype,如果在原型中找到了这个属性,就使用该属性。
- 利用
hasOwnProperty(prop)来判断一个属性是自身属性还是原型属性。 - 获取某个对象的所有属性列表: for-in 循环
- 并不是所有的属性都会在for-in中显示,如数组的length和constructor, 那些会显示的属性称为可枚举的,可以通过各对象所提供的
propertyIsEnumerable()来判断。ES5可以指定哪些属性可枚举。 - 原型链中的各个属性也会被列出来,前提是它们是可枚举的。
- 对于所有的原型属性,
propertyIsEnumerable()都会返回false,包括那些在for-in循环中可枚举的属性。
- 并不是所有的属性都会在for-in中显示,如数组的length和constructor, 那些会显示的属性称为可枚举的,可以通过各对象所提供的
function Gadget(name, color) {
this.name = name;
this.coloe = color;
this.getName = function() {
return this.name;
}
}
Gadget.prototype.price = 100;
Gadget.prototype.rating = 3;
var newtoy = new Gadget('webcam', 'black');
newtoy.propertyIsEnumerable('name'); //true;
newtoy.propertyIsEnumerable('constructor'); //内建属性和方法通常是不可枚举的
newtoy.propertyIsEnumerable('price'); //false; 原型链中的属性也是不可枚举的
但是,newtoy.constructor.prototype.propertyIsEnumerable('price')返回的是true; 其它很好理解,因为price原本就在newtoy.constructor.prototype这个对象上。
isPrototypeOf()
每一个对象都有一个isPrototypeOf方法,用来判断当前对象是否是另一个对象的原型。
getPrototypeOf()
ES5中提供的获取某个对象的原型的方法。
var mokey = {
hair: true,
feeds: 'banana'
}
function Human(name) {
this.name = name;
}
Human.prototype = mokey;
var george = new Human('George');
Object.getPrototypeOf(george).feeds; // banana
对于另一部份实现了ES5的功能,但没有getPrototypeOf方法的浏览器,我们可以采用__proto__
扩展内建对象
采用原型来扩展内建对象
- 在Array上添加一个查询数组是否存在某特定值的方法
Array.prototype.inArray = function(data) {
for (var i = 0, len = this.length; i < len; i ++) {
if (this[i] == data) {
return true;
}
}
return false;
}
var colors = ['red', 'green', 'blue'];
colors.inArray('red');
- 字符串反转
String.prototype.reverse = function() {
return Array.prototype.reverse.call(this.split('')).join('');
}
__proto__ 链接 (该属性非标准,先不做深入了解)
__proto__和prototype不是等价的,__proto__是某个实例对象的属性,而prototype则是构造器的属性。当一个对象被创建时,它的__proto__ 属性和内部属性[[Prototype]]指向了相同的对象 (也就是它的构造函数的prototype属性).
// 声明一个函数作为构造函数
function Employee() {
/* 初始化实例 */
}
// 创建一个Employee实例
var fred = new Employee();
// 测试相等性
fred.__proto__ === Employee.prototype; // true
原型的陷阱;
当我们对原型对象进行替换时,会触发原型链上的异常
function Dog() {
this.tail = true;
}
var benji = new Dog();
var rusty = new Dog();
Dog.prototype.say = function() {
return 'woof';
}
然后我们对Dog的原型进行如下替换:
Dog.prototype = {
paws: 4,
hair: true
}
再来看原有对象:
typeof benji.paws; //undefined
benji.say();// woof
之前创建的对象使用的是之前的prototype,我们来新建对象
var lucy = new Dog();
lucy.paws; //4
lucy.say;//undefined
lucy.constructor//function Object() { [native code] }
typeof lucy.__proto__.say; //undefined
typeof lucy.__proto__.paws; //number
会发现,其constructor的指向都不对了,为此,我们需要重写constructor。
Dog.prototype.constructor = Dog;