在JavaScript中,函数能够有属性。每个函数都有一个特殊的属性叫原型(prototype)。编程
在下面的代码中为函数p定义原型markdown
function P(){//构造函数
this.x = x; //声明私有属性,并初始化为参数x
}
P.prototype.x = 1;
var p1 = new P (10); //实例化对象,并设置参数为10
P.prototype.x = p1.x; //设置原型属性值为私有属性值
console.log(P.prototype.x); //返回10
复制代码
访问原型对象有3种方法,简单说明以下:函数
__proto__
其中,obj表示一个实例对象,constructor表示构造函数,__proto__
是一个私有属性,可读写,与prototype属性相同,均可以访问原型对象。Object.getPrototypeOf(obj)是一个静态函数。参数为示例对象,返回值是参数对象的原型对象。测试
下面建立一个空的构造函数,而后实例化,分别使用上述三种方法访问实例对象的原型。ui
var F = function() {};//构造函数
var obj = new F();//实例化对象
var prototype1 = Object.getPrototypeOf(obj);//引用原型
var prototype2 = obj.__proto__;//引用原型
var prototype3 = obj.constructor.prototype;//引用原型
var prototype4 = F.prototype;
console.log(prototype1 === prototype2); //true
console.log(prototype1 === prototype3); //true
console.log(prototype1 === prototype4); //true
console.log(prototype2 === prototype3); //true
console.log(prototype2 === prototype4); //true
console.log(prototype3 === prototype4); //true
复制代码
设置原型对象有3种方法,简单说明以下:this
__proto__
= prototypeObj其中,obj表示一个实例对象,prototypeObj表示原型对象。spa
下面代码简单演示利用上述三种方法为对象直接设置原型:prototype
var proto = {name : "prototype"}; //原型对象
var obj1 = {}; //普通对象直接量
obj1.__proto__ = proto; //设置原型
console.log(obj1.name);
var obj2 = {}; //普通对象直接量
Object.setPrototypeOf(obj2, proto); //设置原型
console.log(obj2.name);
var obj3 = Object.create(proto); //建立对象,并设置原型
console.log(obj3.name);
复制代码
使用isPrototypeOf()方法能够判断该对象是否为参数对象的原型。isPrototypeOf() 是一个原型方法,能够在每一个实例对象上调用。设计
下面代码简单演示如何检测原型对象:code
var F = function () {}; //构造函数
var obj = new F(); //实例化
var proto1 = Object.getPrototypeOf(obj); //引用原型
console.log(proto1.isPrototypeOf(obj)); //true
复制代码
也可使用下面代码检查不一样类型的实例:
var proto = Object.prototype;
console.log(proto.isPrototypeOf({})); //true
console.log(proto.isPrototypeOf([])); //true
console.log(proto.isPrototypeOf(//)); //true
console.log(proto.isPrototypeOf(function () {})); //true
console.log(proto.isPrototypeOf(null)); //false
复制代码
原型属性能够被全部实例访问,而私有属性只能被当前实例访问。
在下面示例中,演示如何定义一个构造函数,并为实例对象定义私有属性:
function F () { //声明一个构造函数
this.a = 1; //为构造类型声明一个私有属性
this.b = function () { //为构造类型声明一个私有属性
return this.a;
};
}
var e = new F(); //实例化构造函数
console.log(e.a); //调用实例对象的属性a,返回1
console.log(e.b()); //调用实例对象的方法b,提示1
复制代码
构造函数 f 中定义了两个私有属性,分别是属性 a 和 方法b()。当构造函数实例化后,实例对象继承了构造函数的私有属性。此时能够在本地修改实例对象的属性 a 和方法 b()。
e.a = 2;
console.log(e.a);
console.log(e.b());
复制代码
若是给构造函数定义了与原型属性同名的私有属性,则私有属性会覆盖原型属性值。
若是使用 delete 运算符删除私有属性,则原型属性会被访问。在上面示例的基础上删除私有属性,则会发现能够访问原型属性。
私有属性能够在实例对象中被修改,不一样实例对象之间不会相互干扰:
function F () { //声明一个构造类型
this.a = 1; //为构造类型声明一个私有属性
}
var e = new F (); //实例e
var g = new F (); //实例g
console.log(e.a); //返回值为1,说明它继承了构造函数的初始值
console.log(g.a); //返回值为1,说明它继承了构造函数的初始值
e.a = 2; //修改实例e的属性a的值
console.log(e.a); //返回值为2,说明e的属性a的值改变了
console.log(g.a); //返回值为1,说明g的属性a的值没有受影响
复制代码
上面示例演示了若是使用私有属性,则实例对象之间就不会相互影响。可是若是但愿统一修改实例对象中包含的私有属性值,就须要一个个的修改,工做量会很大。
原型属性将会影响全部实例对象,修改任何原型属性值,则该构造函数的全部实例都会看到这种变化,这样就省去了私有属性修改的麻烦。
function F () {}; //声明一个构造函数
F.prototype.a = 1; //为构造类型声明一个私有属性
var e = new F (); //实例e
var g = new F (); //实例g
console.log(e.a); //返回值为1,说明它继承了构造函数的初始值
console.log(g.a); //返回值为1,说明它继承了构造函数的初始值
F.prototype.a = 2; //修改原型属性值
console.log(e.a); //返回值为2,说明实例e的属性a的值改变了
console.log(g.a); //返回值为2,说明实例g的属性a的值改变了
复制代码
在上面示例中,原型属性值会影响全部实例对象的属性值,对于原型方法也是如此。原型属性或原型方法能够在构造函数结构体内定义。
function f () {}; //声明一个空的构造类型
f.prototype.a = 1; //在结构体外为构造类型声明一个原型属性
f.prototype.b = function () { //在结构体外为构造类型声明一个原型方法
return f.prototype.a; //返回原型属性值
}
复制代码
prototype 属性属于构造函数,因此必须使用构造函数经过点语法来调用 prototype 属性,再经过 prototype 属性来访问原型对象
利用对象原型与私有属性之间的这种特殊关系能够设计如下有趣的演示效果:
function P (x, y, z) { //构造函数
this.x = x; //声明私有属性x并赋值参数x的值
this.y = y; //声明私有属性y并赋值参数y的值
this.z = z; //声明私有属性z并赋值参数z的值
}
P.prototype.del = function () { //定义原型方法
for (var i in this) { //遍历本地对象,删除实例内的全部属性和方法
delete this[i];
}
}
P.prototype=new P (1, 2, 3); //实例化,并把实例对象传递给原型对象
var p1 = new P (10, 20, 30); //实例化构造函数p为p1
console.log(p1.x); //返回10,私有属性x的值
console.log(p1.y); //返回20,私有属性y的值
console.log(p1.z); //返回30,私有属性z的值
p1.del(); //调用原型方法删除全部私有属性
console.log(p1.x); //返回1,原型属性x的值
console.log(p1.y); //返回2,原型属性x的值
console.log(p1.z); //返回3,原型属性x的值
复制代码
上面示例定义了构造函数 P,声明了 3 个私有属性,并实例化构造函数,把实例对象赋值给构造函数的原型对象。同时定义了原型方法 del(),该方法将删除实例对象的全部私有属性和方法。最后,分别调用属性 x、y 和 z,返回的是私有属性值,调用方法 del(),删除全部私有属性,再次调用属性 x、y 和 z,则返回的是原型属性值。
利用原型为对象设置默认值。当原型属性与私有属性同名时,删除私有属性后访问原型属性,便可以把原型属值做为初始化默认值。
function P (x) { //构造函数
if (x) { //若是参数存在,则设置属性,该条件是关键
this.x = x; //使用参数初始化私有属性x的值
}
}
P.prototype.x = 0; //利用原型属性,设置私有属性x的默认值
var p1 = new P(); //实例化一个没有带参数的对象
console.log(p1.x); //返回0,即显示私有属性的默认值
var p2 = new P(1); //再次实例化,传递一个新的参数
console.log(p2.x); //返回1,即显示私有属性的初始化值
复制代码
利用原型间接实现本地数据备份。把本地对象的数据彻底赋值给原型对象,至关于为该对象定义一个副本,也就是备份对象。当对象属性被修改时,就能够经过原型对象来恢复本地对象的初始值。
function P (x) { //构造函数
this.x = x;
}
P.prototype.backup = function () { //原型方法,备份本地对象的数据到原型对象中
for (var i in this) {
P.prototype[i] = this[i];
}
}
var p1 = new P(1); //实例化对象
p1.backup; //备份实例对象中的数据
p1.x = 10; //改写本地对象的属性值
console.log(p1.x); //返回10,说明属性值已经被改写
p1 = P.prototype; //恢复备份
console.log(p1.x); //返回1,说明对象的属性值已经被恢复
复制代码
利用原型还能够为对象属性设置"只读"特性,这在必定程度上能够避免对象内部被任意修改的问题。下面实例演示了如何根据平面上两点坐标来计算它们之间的距离。构造函数P用来设定定位坐标,当传递两个参数值时,会返回以参数为坐标值的点。若是省略参数则默认点为圆点(0,0)。而在构造函数L中经过传递的两点坐标对象计算它们的距离。
function P (x, y) { //求坐标点构造函数
if (x) this.x = x; //初始x轴值
if (y) this.y = y; //初始y轴值
P.prototype.x = 0; //默认x轴坐标
P.prototype.y = 0; //默认y轴坐标
}
function L (a, b) { //求两点距离构造函数
var a = a; //参数私有化
var b = b; //参数私有化
var w = function () { //计算x轴距离,返回对函数引用
return Math.abs(a.x - b.x);
}
var h = function () { //计算y轴距离,返回对函数引用
return Math.abs(a.y - b.y);
}
this.length = function () { //计算两点距离,调用私有方法w()和h()
return Math.sqrt(w() * w() + h() * h());
}
this.b = function () { //获取起点坐标对象
return a;
}
this.e = function () { //获取终点坐标对象
return b;
}
}
var p1 = new P (1, 2); //实例化P构造函数,声明一个点
var p2 = new P (10, 20); //实例化P构造函数,声明另外一个点
var l1 = new L (p1, p2); //实例化L构造函数,传递两个对象
console.log(l1.length()); //返回20.12461179749811,计算两点距离
l1.b().x = 50; //不经意改动方法b()的一个属性为50
console.log(l1.length()); //返回43.86342439892262,说明影响两点距离值
复制代码
在测试中会发现,若是无心间修改了构造函数 L 的方法 b() 或 e() 的值,则构造函数 L 中的 length() 方法的计算值也随之发生变化。这种动态效果对于须要动态跟踪两点坐标变化来讲,是很是必要的。可是,这里并不须要当初始化实例以后,随意的被改动坐标值。毕竟方法 b() 和 e() 与参数 a 和 b 是没有多大联系的。
为了不由于改动方法 b() 的属性 x 值会影响两点距离,能够在方法 b() 和 e() 中新建一个临时性的构造类,设置该类的原型为 a,而后实例化构造类并返回,这样就阻断了方法 b() 与私有变量 a 的直接联系,它们之间仅是值得传递,而不是对对象 a 的引用,从而避免由于方法 b() 的属性值变化而影响私有对象 a 的属性值。
this.b = function () { //方法b()
function temp () {}; //临时构造类
temp.prototype = a; //把私有对象传递给临时构造类的原型对象
return new temp(); //返回实例化对象,阻断直接返回a的引用关系
}
this.e = function () { //方法e()
function temp () {}; //临时构造类
temp.prototype = a; //把私有对象传递给临时构造类的原型对象
return new temp(); //返回实例化对象,阻断直接返回a的引用关系
}
复制代码
还有一种方法是在给私有变量 w 和 h 赋值时,不是赋值函数,而是函数调用表达式,这样私有变量 w 和 h 存储的时值类型数据,而不是对函数结构的引用,从而就再也不受后期相关属性值的影响。
function l (a, b) { //求两点距离构造函数
var a = a; //参数私有化
var b = b; //参数私有化
var w = function () { //计算x轴距离,返回函数表达式的计算值
return Math.abs(a.x - b.x);
} ()
var h = function () { //计算y轴距离,返回函数表达式的计算值
return Math.abs(a.y - b.y);
} ()
this.length = function () { //计算两点距离,直接使用私有变量 w 和 h 来计算
return Math.sqrt(w() * w() + h() * h());
}
this.b = function () { //获取起点坐标
return a;
}
this.e = function () { //获取终点坐标
return b;
}
}
复制代码
在JavaScript中,实例对象在读取属性时老是先检查私有属性。若是存在,则会返回私有属性值;不然就会检索prototype原型;若是找到同名属性,则返回prototype原型的属性值。
prototype原型容许引用其余对象。若是在prototype原型中没有找到指定的属性,则JavaScript将会根据引用关系,继续检索prototype原型对象的prototype原型,依次类推。
原型链解决主要问题是继承问题.
下面示例演示了对象属性查找原型的基本方法和规律:
function A (x) { //构造函数A
this.x = x;
}
A.prototype.x = 0; //原型属性x的值为0
function B (x) { //构造函数B
this.x = x;
}
B.prototype = new A (1); //原型对象为构造函数a的实例
function C (x) { //构造函数c
this.x = x;
}
C.prototype = new B(2); //原型对象为构造函数b的实例
var d = new C(3); //实例化构造函数C
console.log(d.x); //调用实例对象d的属性x,返回值为3
delete d.x; //删除实例对象的私有属性x
console.log(d.x); //调用实例对象d的属性x,返回值为2
delete C.prototype.x; //删除c类的原型属性x
console.log(d.x); //调用实例对象d的属性x,返回值为1
delete B.prototype.x; //删除b类的原型属性x
console.log(d.x); //调用实例对象d的属性x,返回值为0
delete A.prototype.x; //删除a类的原型属性x
console.log(d.x); //调用实例对象d的属性x,返回值为undefined
复制代码
原型链可以帮助用户更清楚的认识 JavaScript 面向对象的继承关系,如图所示。
在JavaScript中,一切都是对象,函数时第一型。Function和Object都是函数的实例。构造函数的父原型指向Function的原型,Function.prototype的原型时Object的原型,Object的原型也指向Function的原型,Object.prototype是全部原型的顶层。
Function.prototype.a = function () { //Function原型方法
console.log("Function");
}
Object.prototype.a = function () { //Object原型方法
console.log("Object");
}
function F () { //构造函数f
this.a = "a";
}
F.prototype = { //构造函数f的原型方法
w : function () {
console.log("w");
}
}
console.log(F instanceof Function); //返回true,说明f是Function的实例
console.log(F.prototype instanceof Object); //返回true,说明f的原型也是对象
console.log(Function instanceof Object); //返回true,说明Function是Object的实例
console.log(Function.prototype instanceof Object); //true,说明Function是Object的实例
console.log(Object instanceof Function); //返回true,说明Object 是Function的实例
console.log(Object.prototype instanceof Function); //false,说明Object.prototype是原型顶
复制代码
原型继承是一种简化的继承机制,也是JavaScript原生支持的继承模式。在原型继承中,类和实例概念被淡化了,一切都从对象的角度来考虑。原型继承不在须要使用类来定义对象的解构,直接定义对象,并被其余对象引用,这样就造成了一种继承关系,其中引用对象被成为原型对象。JavaScript可以根据原型链来查找对象之间的这种继承关系。
function A (x) { //A类
this.x1 = x; //A的私有属性x1
this.get1 = function () { //A的私有方法get1()
return this.x1;
};
}
function B(x) { //B类
this.x2 = x; //B的私有属性x2
this.get2 = function () { //B的私有方法get2()
return this.x2 + this.x2;
};
}
B.prototype = new A (1); //原型对象继承A的实例
function C (x) { //C类
this.x3 = x; //C的私有属性x3
this.get3 = function () { //C的私有方法get3()
return this.x3 * this.x3;
};
}
C.prototype = new B (2); //原型对象继承B的实例
复制代码
在上面示例中,分别定义了 3 个构造函数,而后经过原型链把它们串联在一块儿,这样 C 就可以继承 B 和 A 函数的成员,而 B 可以继承 A 的成员。
prototype最大的特色就是可以容许对象实例共享原型对象的成员。所以,若是把某个对象做为一个类型的原型,那么这个对象的类型也能够做为那些以这个对象为原型的实例的父类。
var b = new B (2); //实例化B
var c = new C (3); //实例化C
console.log(b.x1); //在实例对象b中调用A的属性x1,返回1
console.log(c.x1); //在实例对象c中调用A的属性x1,返回1
console.log(c.get3()); //在实例对象c中调用C的方法get3(),返回9
console.log(c.get2()); //在实例对象c中调用B的方法get2(),返回4
复制代码
基于原型的编程是面向对象编程的一种特定形式。在这种编程模型中,不须要声明静态类,而是经过复制已经存在的原型对象来实现继承关系的。所以,基于原型的模型没有类的概念,原型继承中的类仅是一种模式,或者说是沿用面向对象编程的概念。
原型继承的优势是结构简练,使用简便,可是也存在如下几个缺点:
JavaScript 容许经过 prototype 为原生类型扩展方法,扩展方法能够被全部对象调用。例如,经过 Function.prototype 为函数扩展方法,而后为全部函数调用。
为 Function 添加一个原型方法 method,该方法能够为其余类型添加原型方法。
Function.prototype.method = function (name, func) {
this.prototype[name] = func;
return this;
};
复制代码
下面利用 method 扩展方法为 Number 扩展一个 int 原型方法。该方法能够对浮点数进行取整。
Number.method('int', function () {
return Math[this < 0 ? 'ceil' : 'floor'] (this);
});
console.log((-10 / 3).int()); //-3
复制代码
Number.method 方法可以根据数字的正负来判断是使用 Math.ceil 仍是 Math.floor,这样就不须要每次都编写上面的代码。
经过为原生的类型扩展方法,能够大大提升 JavaScript 编程灵活性。可是在扩展基类时务必当心,避免覆盖原生方法。建议在覆盖以前先肯定是否已经存在该方法。
Function.prototype.method = function (name, func) {
if (!this.prototype[name]) { //检测是否已经存在同名属性
this.prototype[name] = func;
return this;
}
};
复制代码
另外,可使用 hasOwnProperty 方法过滤原型属性或者私有属性。