JavaScript 不包含传统的类继承模型,而是使用prototypal 原型模型。虽然这经常被当作是JavaScript 的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大。实现传统的类继承模型是很简单,但是实现JavaScript 中的原型继承则要困难的多。
由于JavaScript 是唯一一个被广泛使用的基于原型继承的语言,所以理解两种继承模式的差异是需要一定时间的,今天我们就来了解一下原型和原型链。
原型
10年前,我刚学习JavaScript的时候,一般都是用如下方式来写代码: var decimalDigits = 2, tax = 5;
function add(x, y { return x + y; }
function subtract(x, y { return x - y; }
//alert(add(1, 3;
通过执行各个function来得到结果,学习了原型之后,我们可以使用如下方式来美化一下代码。
原型使用方式1
在使用原型之前,我们需要先将代码做一下小修改: var Calculator = function (decimalDigits, tax { this.decimalDigits = decimalDigits; this.tax = tax; };
然后,通过给Calculator对象的prototype属性赋值对象字面量来设定Calculator对象的原型。
Calculator.prototype = { add: function (x, y { return x + y; },
subtract: function (x, y { return x - y; } };
//alert((new Calculator(.add(1, 3;
这样,我们就可以new Calculator对象以后,就可以调用add方法来计算结果了。 原型使用方式2
第二种方式是,在赋值原型prototype的时候使用function立即执行的表达式来赋值,即如下格式:
Calculator.prototype =function( { } (;
它的好处在前面的帖子里已经知道了,就是可以封装私有的function,通过return的形式暴露出简单的使用名称,以达到public/private的效果,修改后的代码如下:
Calculator.prototype = function ( { add = function (x, y { return x + y; },
subtract = function (x, y { return x - y; } return { add: add, subtract: subtract } } (;
//alert((new Calculator(.add(11, 3;
同样的方式,我们可以new Calculator对象以后调用add方法来计算结果了。
分步声明
上述使用原型的时候,有一个限制就是一次性设置了原型对象,我们再来说一下如何分来设置原型的每个属性吧。
var BaseCalculator = function ( { //为每个实例都声明一个小数位数 this.decimalDigits = 2; };
//使用原型给BaseCalculator扩展2个对象方法 BaseCalculator.prototype.add = function (x, y { return x + y; };
BaseCalculator.prototype.subtract = function (x, y { return x - y; };
首先,声明了一个BaseCalculator对象,构造函数里会初始化一个小数位数的属性decimalDigits,然后通过原型属性设置2个function,分别是add(x,y和subtract(x,y,当然你也可以使用前面提到的2种方式的任何一种,我们的主要目的是看如何将BaseCalculator对象设置到真正的Calculator的原型上。
var BaseCalculator = function( { this.decimalDigits = 2;
};
BaseCalculator.prototype = { add: function(x, y { return x + y; },
subtract: function(x, y { return x - y; } };
创建完上述代码以后,我们来开始: var Calculator = function ( { //为每个实例都声明一个税收数字 this.tax = 5; };
Calculator.prototype = new BaseCalculator(;
我们可以看到Calculator的原型是指向到BaseCalculator的一个实例上,目的是让Calculator集成它的add(x,y和subtract(x,y这2个function,还有一点要说的是,由于它的原型是BaseCalculator的一个实例,所以不管你创建多少个Calculator对象实例,他们的原型指向的都是同一个实例。
var calc = new Calculator(;
alert(calc.add(1, 1;
//BaseCalculator 里声明的decimalDigits属性,在 Calculator里是可以访问到的 alert(calc.decimalDigits;
上面的代码,运行以后,我们可以看到因为Calculator的原型是指向
BaseCalculator的实例上的,所以可以访问他的decimalDigits属性值,那如果我不想让Calculator访问BaseCalculator的构造函数里声明的属性值,那怎么办呢?这么办:
var Calculator = function ( { this.tax= 5; };
Calculator.prototype = BaseCalculator.prototype;
通过将BaseCalculator的原型赋给Calculator的原型,这样你在Calculator的实例上就访问不到那个decimalDigits值了,如果你访问如下代码,那将会提升出错。
var calc = new Calculator(; alert(calc.add(1, 1; alert(calc.decimalDigits; 重写原型
在使用第三方JS类库的时候,往往有时候他们定义的原型方法是不能满足我们的需要,但是又离不开这个类库,所以这时候我们就需要重写他们的原型中的一个或者多个属性或function,我们可以通过继续声明的同样的add代码的形式来达到覆盖重写前面的add功能,代码如下:
//覆盖前面Calculator的add( function
Calculator.prototype.add = function (x, y { return x + y + this.tax; };
var calc = new Calculator(; alert(calc.add(1, 1;
这样,我们计算得出的结果就比原来多出了一个tax的值,但是有一点需要注意:那就是重写的代码需要放在最后,这样才能覆盖前面的代码。
原型链
在将原型链之前,我们先上一段代码: function Foo( { this.value = 42; }
Foo.prototype = { method: function( {} };
function Bar( {}
// 设置Bar的prototype属性为Foo的实例对象 Bar.prototype = new Foo(; Bar.prototype.foo = 'Hello World';
// 修正Bar.prototype.constructor为Bar本身 Bar.prototype.constructor = Bar;
var test = new Bar( // 创建Bar的一个新实例 // 原型链 test [Bar的实例]
Bar.prototype [Foo的实例] { foo: 'Hello World' } Foo.prototype {method: ...}; Object.prototype {toString: ... /* etc. */};
上面的例子中,test 对象从Bar.prototype 和Foo.prototype 继承下来;因此,它能访问Foo 的原型方法method。同时,它也能够访问那个定义在原型上的Foo 实例属性value。需要注意的是new Bar(
不会创造出一个新的Foo 实例,而是重复使用它原型上的那个实例; 因此,所有的Bar 实例都会共享相同的value 属性。 属性查找
当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找 到给定名称的属性为止,到查找到达原型链的顶部- 也就是
Object.prototype - 但是仍然没有找到指定的属性,就会返回undefined,我们来看一个例子:
function foo( {
this.add = function (x, y { return x + y; } }
foo.prototype.add = function (x, y {
return x + y + 10; } Object.prototype.subtract = function (x, y { return x - y; } var f = new foo(; alert(f.add(1, 2; //结果是 3,而不是 13 alert(f.subtract(1, 2; //结果是-1 通 过 代码 运 行 , 我 们 发现 subtract 是 安 装 我 们 所说 的 向 上 查 找 来 得 到 结 果的 , 但 是 add 方 式 有点 小 不 同 , 这 也 是 我想 强 调 的 , 就 是 属 性在 查 找 的时 候 是 先 查 找 自 身 的属 性 , 如 果 没 有 再 查找 原 型 , 再 没 有 , 再往 上 走 ,一 直 插 到 Object 的 原 型 上 ,所以 在 某 种 层面 上 说 ,用 for in 语 句 遍 历 属性 的 时 候 , 效 率 也 是个 问 题 。 还 有 一点 我 们 需 要 注 意 的 是, 我 们 可 以 赋 值 任 何类 型 的 对 象 到 原 型 上 , 但是 不 能 赋 值 原 子 类 型的 值 , 比 如 如 下 代 码是 无 效 的 : function Foo( {} Foo.prototype = 1; // 无效 hasOwnProperty 函数 hasOwnProperty 是 Object.prototype 的 一 个 方法 , 它可 是 个 好 东 西, 他 能 判断 一 个 对 象 是 否 包 含自 定 义 属 性 而 不 是 原型 链 上 的 属 性 , 因 为 hasOwnProperty 是 JavaScript 中 唯 一 一 个 处理 属 性 但 是 不 查 找 原型 链 的 函数 。 // 修改 Object.prototype Object.prototype.bar = 1; var foo = {goo: undefined}; foo.bar; // 1 'bar' in foo; // true foo.hasOwnProperty('bar'; // false
foo.hasOwnProperty('goo'; // true 只 有 hasOwnProperty 可 以 给 出 正确 和 期 望 的结 果 ,这 在遍 历 对 象 的 属 性时 会 很 有 用 。 没 有其 它 方 法 可 以 用 来 排除 原 型 链 上 的 属 性 ,而 不 是 定 义在 对 象 自 身 上 的 属 性。
但 有 个恶 心 的 地 方 是 : JavaScript 不 会保 护 hasOwnProperty 被 非 法 占 用, 因 此 如 果 一 个 对 象碰 巧 存 在 这 个 属 性 ,就 需 要 使 用 外 部 的 hasOwnProperty 函 数 来 获取 正 确 的 结 果 。 var foo = { hasOwnProperty: function( { return false; }, bar: 'Here be dragons' }; foo.hasOwnProperty('bar'; // 总是返回 false // 使用{}对象的 hasOwnProperty,并将其上下为设置为 foo
{}.hasOwnProperty.call(foo, 'bar'; // true 当 检 查 对 象 上 某 个 属 性 是 否 存 在 时 , hasOwnProperty 是 唯 一 可 用 的 方 法 。 同 时 在 使 用 for in loop 遍 历 对 象 时 , 推 荐 总 是 使 用 hasOwnProperty 方 法 , 这 将 会 避 免 原 型 对 象 扩 展 带 来 的 干 扰 , 我 们 来 看 一 下例 子 : // 修改 Object.prototype
Object.prototype.bar = 1; var foo = {moo: 2}; for(var i in foo { console.log(i; // 输出两个属性:bar 和 moo } 我 们 没 办 法 改 变 for in 语 句 的 行 为 , 所 以 想 过 滤 结 果 就 只 能 使 用 hasOwnProperty 方 法 , 代码 如 下 : // foo 变量是上例中的 for(var i in foo { if (foo.hasOwnProperty(i { console.log(i; } } 这 个 版本 的 代 码 是 唯 一 正 确的 写 法 。 由 于 我 们 使用 了 hasOwnProperty, 以 这 次只 输 出 moo。 果 不使 用 hasOwnProperty, 所 如 则 这 段代 码 在 原 生 对 象 原 型( 比 如 Object.prototype)被 扩 展 时 可 能会 出错。 总 结 :推 荐 使 用 hasOwnProperty, 不要 对 代 码运 行 的 环 境 做 任 何 假 设 ,不 要 假 设 原 生 对 象 是否 已 经 被 扩 展 了 。 总结
原 型 极大 地 丰 富 了 我 们 的 开发 代 码 , 但 是 在 平 时使 用 的 过 程 中 一 定 要 注 意上 述 提 到 的 一 些 注 意事 项 。
百度搜索“70edu”或“70教育网”即可找到本站免费阅读全部范文。收藏本站方便下次阅读,70教育网,提供经典综合文库js高端系列教程(26)――JavaScript探秘:强大的.在线全文阅读。
相关推荐: