Prototypes in JavaScript
JavaScript 是" 入易深难 " 但也就是这个深才给程序员带来了无限乐趣. Prototype 是Javascript非常重要属性之一.所以如果有不清楚或想知道请翻看以下文章.
(英文好的建议直接阅读原文)
又是一篇深入理解JavaScript的好文篇, 再次分享.希望您看完之后有豁然开朗的感觉.(仅供个人学习使用). 如需转载请注明地址/作者 谢谢.
英文原文地址 : http://net.tutsplus.com/tutorials/javascript-ajax/prototypes-in-javascript-what-you-need-to-know/
======================Enein翻译并学习着...=======================
在文章开头, 作者就说了关于新手对于'prototype'的神秘感. 在定义function初function里就会有很多属性. 其中的prototype是比较重要和不是很好理解的.这篇文章主要就是 详细介绍prototype以及在项目中的实际应用.
What is Prototype?
prototype 是初始化Object就存在的属性, 可以为其再次加入属性.
var myObject = function(name){ this.name = name; return this; }; console.log(typeof myObject.prototype); // object myObject.prototype.getName = function(){ return this.name; };
在上面的代码中, 我们创建了一个function, 但当我们调用myObject()的时候, 它会返回window对象, 因为它被定义在全局作用域下, this 代表全局对象.同样它也没有被实例化(后面会有更详细的介绍).
console.log(myObject() === window); // true
The Secret Link
我们继续之前的讨论, 看看这个'神秘的'prototype是怎么工作的.
每个object在JavaScript定义或实例化的时候都会存在一个内置属性"__proto__"(两个下划线), 这使得原型链可以被访问, 无论如何在你的应用程序中直接使用"__proto__"都不是最好的.
它不是在所有的浏览器中都是有效的.
这个__proto__不能和对象的prototype相混淆, 它们是两个不同的属性. 它们的关系很紧密, 所以最重要的是能了解它们并区别它们.当我创建function的时候, 我们是定义了一个Funtcion类型的Object
console.log(typeof myObject); // function
"Function 在JavaScript中是内置对象" 因此它有它自己的属性(e.g length 和 arguments) 和 方法(e.g call 和 apply), 同样它也是有它自己的object("__proto__"), 这意味着, 在JavaScript 解析引擎中, 类似这样的:
Function.prototype = { arguments: null, length: 0, call: function(){ // secret code }, apply: function(){ // secret code } ... }
事实上没有这个简单, 这里只是简单的介绍一下原型链的结构.
上面我们有给myObject设置一个name方法, 没有在给其它的了, 但它为什么还能调用 length call apply ...
console.log(myObject.length); // 1 (being the amount of available arguments)
这是因为, 在我们定义myObject的时候, 它会创建一个 __proto__属性并且会为Function.prototype设置它的值. 所以, 当你访问 myObject.length 的时候, 它会尝试去 myObject 里找属性并调用, 但没有找到, 然后它就会去它的原型链上去找, 通过 __proto__ 链, 找到并返回数据.
你可能对 myObject.length 返回1 不是很理解, 为什么不是2,3或其它的数字, 这是因为 myObject 事实上是 Function的一个实例.
console.log(myObject instanceof Function); // true console.log(myObject === Function); // false
当一个对象创建的时候, 这个 __proto__ 是被更新并指向这个构造器的 prototype , 在上面的案例中, 就是 Funtiion :
console.log(myObject.__proto__ === Function.prototype) // true
另外, 在你创建一个新的Function对象的时候, 在Function 构造器的代码内部会统计可用参数的数量(上面的function(name)参数为一个所以)并且更新 this.length , 所以上面的案例为 1.
如果我们使用 new 关键字来创建myObject的一个新实例, __proto__ 将会指向myObject.prototype来作为新实例的构造方法.
var myInstance = new myObject(“foo”); console.log(myInstance.__proto__ === myObject.prototype); // true
此外我们还可以访问Function.prototype的本地方法, 比如 call, apply , 现在我尝试访问myObject的getName:
console.log(myInstance.getName()); // foo var mySecondInstance = new myObject(“bar”); console.log(mySecondInstance.getName()); // bar console.log(myInstance.getName()); // foo
你可以想你一下这是很方便的, 因为它可以很方便的去'操作'一个对象, 创建你自己需要的实例. --> Next Top;
Why Is Use Prototype Better?
举个例子, 我们要开发一个canvas游戏, 我可能会需要很多个对象立即出现在屏幕上, 每个对象都会有它的属性, 像 x, y坐标 width, height等等.
Like this:
var GameObject1 = { x: Math.floor((Math.random() * myCanvasWidth) + 1), y: Math.floor((Math.random() * myCanvasHeight) + 1), width: 10, height: 10, draw: function(){ myCanvasContext.fillRect(this.x, this.y, this.width, this.height); } ... }; var GameObject2 = { x: Math.floor((Math.random() * myCanvasWidth) + 1), y: Math.floor((Math.random() * myCanvasHeight) + 1), width: 10, height: 10, draw: function(){ myCanvasContext.fillRect(this.x, this.y, this.width, this.height); } ... };
以上在重复 98 次. 会有很多个对象在内存里被创建 --- 所有的方法都分开定义, 为方便被其它接口调用. 很明显这不是一个好办法 做为游戏这会导致JavaScript的内存溢出/甚至非常慢/或Down掉.
当然 仅100个对象是不太有可能会发生的, 但它还是会影响一部分性能, 因为它需要'遍历'100个不同的对象, 而不是一个prototype对象.
How to Use Prototype?
要想使应用程序最快, 最好的实践就是我们要定义一个 prototype 属性 GameObject; 每个 GameObject 的实例都将会引用这些方法(GameObject.prototype)来当作自己的方法.
// define the GameObject constructor function var GameObject = function(width, height) { this.x = Math.floor((Math.random() * myCanvasWidth) + 1); this.y = Math.floor((Math.random() * myCanvasHeight) + 1); this.width = width; this.height = height; return this; }; // (re)define the GameObject prototype object GameObject.prototype = { x: 0, y: 0, width: 5, width: 5, draw: function() { myCanvasContext.fillRect(this.x, this.y, this.width, this.height); } };
我们可以初始化 GameObject 100次:
var x = 100, arrayOfGameObjects = []; do { arrayOfGameObjects.push(new GameObject(10, 10)); } while(x--);
现在我们有一个数组里有100个GameObject对象, 所有的对象都共享prototype.draw方法, 这样大大减少了内存. 在我们调用 draw 方法的时候, 我们引用的其实是同一个function.
var GameLoop = function() { for(gameObject in arrayOfGameObjects) { gameObject.draw(); } };
Prototype is a Live Object
Prototype是一个灵活的对象, 举例来说: 我想改变这之前的draw 我要画个长方形或一个圆. 我只要改变一下:
GameObject.prototype.draw = function() { myCanvasContext.arc(this.x, this.y, this.width, 0, Math.PI*2, true); }
那么我上面的100Object都会画圆, 之后的实例也会.
Updating Native Objects Prototype
这是可能的, 如果你熟悉 Javascript库比如 Prototype, 你就会知道怎么利用好这一点.
让我们看一下简单的实例:
String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ‘’); };
现在我们任何的string都可以调用该方法:
“ foo bar “.trim(); // “foo bar”
这里存在一个小缺陷, 就是随着浏览器时代的发展,有的JavaScript引擎的 trim已经是String的 prototype 属性了, 如果我们这样写会覆盖native方法, 所以我们要在之前加入个判断.
if(!String.prototype.trim) { String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ‘’); }; }
"正常情况下, 最好不要重写原生态的方法, 但是在一些特殊的情况下, 规则是可以被破坏的"
Conclusion
这篇文章希望是有所帮助对您, 介绍的主要还是Javascript的骨干属性prototype , 如果理解了prototype 你就可以写出更有效的应用程序.
如果你还有对prototype的问题, 请写在 comments 里我很高兴为您解答.
======================Enein翻译并学习着...=======================