扩展javascript原生对象-prototype原型链彻底掌握
原文地址: http://javascript.info/tutorial/native-prototypes
prototype 原型是什么?
和传统语言对象创建有些不同,比如java,php都是通过new一个class类来创建对应的对象的。
而javascript则提供了利用prototype来新建对象的机制。也就是从实体对象创建新的实体对象的方法。
从prototype创建新对象的两种方法Object.create, new ClassConstructorFunction()
以及
原生的javascript 对象在prototypes里面保存他们可用的方法。
比如,当我们创建一个新的对象时, 它并未包含任何东西,它怎么知道能够有个toString方法可供使用呢?
var obj={}; alert(obj.toString())
那是因为obj={}是下面语句
obj = new Object()
的缩写,而Object是原生的javascript 构造函数,而当我们使用new Object()来创建一个新的对象时,有一个重要的特性就是:产出对象的__proto__指针将指向构造函数的prototype对象,这里就是Object.prototype对象。也就是说obj.__proto__ == Object.prototype,而Object.prototype又是一个包含toString方法的原生对象
。注意: __proto__是随浏览器的,有可能命名并不相同。
而根据javascript的原型链查找特性,所有在这个链上的属性和方法都是可读的(注意当写一个在原型链上的属性时,会自动创建一个ownProperty,而不会去更改原型上的属性值!!),也正因为此,所以toString方法自然可用了。
上面的故事同样适用于Array,Function和其他对象。他们的方法分别在Function.prototype, Array.prototype上面定义的、
再看以下原型链的图(来自网易云课堂)
当一个对象的属性被访问或者方法被调用时,javascript引擎通过以下算法和顺序来检索:
1. 在对象自身上面找;
2. 如果1.找不到,就到对象的__proto__上面找;
3. 如果2.找不到,就到__proto__.__proto__上面找
上面这个原型链从对象本身开始持续查找,直到属性被找到(注意这时this为子对象,而非原型),或者直到下一个__proto__ == null(也就是到链底)了而返回undefined. 唯一一个其__proto__属性为 null的对象是 Object.prototype,所有的链最终都指向Object.prototype,换句话说,所有的对象都从Object继承
更改原生prototypes添加新的方法
原生的prototypes是可以被修改的,比如我们可以创建新的方法到原型对象上去。
我们可以写一个方法来多次重复一个字符串
String.prototype.repeat = function(times) { return new Array(times+1).join(this) } alert( "a".repeat(3) ) // aaa
再例如,我们可以在Object.prototype对象上添加一个each(func)方法,以便我们在每一个属性上来应用func函数。
Object.prototype.each = function(f) { for(var prop in this) { if (Object.prototype.hasOwnProperty(prop)) continue // filter var value = this[prop] f.call(value, prop, value) } } // Now correct var obj = { name: 'John', age: 25 } obj.each(function(prop, val) { alert(prop) // name -> age })
通过上面的方法修改原生对象从一开始就招致激烈的评论,有人说不安全,暴露太多到global空间可能造成命名污染,有人说很好用。但无论如何,它在做兼容低版本浏览器的js功能时,用处广泛:
比如,如果没有Object.create支持,而我们又必须兼容,那么一个可行的方法是:
if (!Object.create) { Object.create = function(proto) { function F() {} F.prototype = proto return new F } }
从原生对象继承
原生对象可以用来被继承,比如,Array.prototype为new Array()创建的对象保持了所有的方法
如果我们希望这些方法可在我们的myArray中被访问,那么myArray.__proto__ == Array.prototype就有用武之地了。
// constructor function MyArray() { this.stringify = function() { return this.join(', ') } } // Make sure that (new MyArray) has the right __proto__ MyArray.prototype = Array.prototype // Test var myArr = new MyArray() myArr.push(1) myArr.push(2) alert( myArr.stringify() ) // 1, 2 alert( myArr.length ) // 2 in all except IE
方法借用
如果你仅仅想用Array的几个方法的功能,那么是不必要继承Array的,你可以borrow a method,然后apply it without inhereting, 比如
var join = Array.prototype.join 或者更短一点 var join = [].join
随后使用一个非标准的this 来调用它:
var obj = { 0: 'first', 1: 'second', length: 2 } alert( [].join.call(obj, ', ') ) // first, second
上例中, Array.prototype.join在ES-5标准中描述,他并不检查对象type。它做的事情是一个对所有的属性来做joining loop。Array的方法常常用来被借用以便操作array-like对象。
更改原生prototypes“修改”已经存在的原生方法
很多时候,你希望通过修改原生的方法,获得你希望得到的特性,比如我们希望extend Array对象的join()方法,以便在将控制返回给原生join()方法之前增加一些log信息。
Array.prototype.join = (function(_super) { // return our new `join()` function return function() { console.log("Hey, you called join!"); return _super.apply(this, arguments); }; // Pass control back to the original join() // by using .apply on `_super` })(Array.prototype.join); // // Pass the original function into our // immediately invoked function as `_super` // which remains available to our new // function thanks to JavaScript closures.
借用原生代码简化重构自用的函数
有时候,我们会嫌直接使用原生函数方法太过啰嗦冗长,一个有效的办法是借鸡生蛋,重新包装。比如我们常常需要就正则表达式来对一个字符串做test,可行的办法就是借用Function.bind返回一个有待运行的函数,后面直接简单调用:
var testForA = RegExp.prototype.test.bind(/[aA]/); var testForDigit = RegExp.prototype.test.bind(/[0-9]/); // function () { [native code] } testForA('hello'); // false testForA('hella'); // true ['hello', 'hella', 'holla'].filter(testForA); // ['hella', 'holla'] testForDigit('this has no digits'); //false testForDigit('this has 123 digits'); //true
http://prototypejs.org/
http://sugarjs.com/native