首先,先认识下什么是原型?
原型是一个对象,其他对象可以通过它实现属性继承,而且任何一个对象都可以成为原型。这是为什么呢?请继续看。
首先,要弄明白什么是对象,在JavaScript中可以说一切都是对象,除了(undefined, number, string, boolean)这四种值类型,我们可以通过 typeof() 这个函数和instanceof进行检测,如何判断一个变量是不是对象,值类型的类型判断用typeof,引用类型的类型判断用instanceof。如下:
1 function show(x) { 2 3 console.log(typeof(x)); // undefined 4 console.log(typeof(10)); // number 5 console.log(typeof('abc')); // string 6 console.log(typeof(true)); // boolean 7
8 console.log(typeof(function () { })); //function 9 console.log((function () { }) instanceof Object); // true 10 11 console.log(typeof([1, 'a', true])); //object 12 console.log(typeof ({ a: 10, b: 20 })); //object 13 console.log(typeof (null)); //object 14 console.log(typeof (new Number(10))); //object 15 } 16 show();
其中上面的四种(undefined, number, string, boolean)属于简单的值类型,不是对象。剩下的几种情况——函数、数组、对象、null、new Number(10)都是对象。对象是属性的集合。
弄明白什么是对象后,我们再来理解下函数与对象的关系。对象都是通过函数创建的,为什么这么说呢,请看代码:
1 var obj = { a: 10, b: 20 }; 2 var arr = [5, 'x', true]; 3 //上面看起来不像是通过函数创建出一个对象,其实其本质如下 4 5 var obj = new Object(); 6 obj.a = 10; 7 obj.b = 20; 8 9 var arr = new Array(); 10 arr[0] = 5; 11 arr[1] = 'x'; 12 arr[2] = true; 13 14 console.log(typeof (Object)); // function 15 console.log(typeof (Array)); // function
通过上述代码理解还是非常的迷惑吧。对象是函数创建的,而函数却又是一种对象,函数和对象到底是什么关系啊?我们就通过prototyoe原型来加以理解。
每个函数都有一个属性叫做prototype(原型)。这个prototype的属性值是一个对象(属性的集合),默认的只有一个叫做constructor的属性,指向这个函数本身。如图(SuperType是一个函数,右侧的方框就是它的原型)
当然,原型作为一个对象,不可能只含有consrtuctor这一个属性而已,我们可以在其中添加一些我们自定义的属性,例如:
1 function Fn() { }; 2 Fn.prototype.name = 'hello'; 3 Fn.prototype.getYear = function () { 4 return 2015; 5 };
1 function Fn() { }; 2 Fn.prototype.name = 'hello'; 3 Fn.prototype.getYear = function () { 4 return 2015; 5 }; 6 7 var fn = new Fn(); 8 console.log(fn.name); //hello 9 console.log(fn.getYear()); //2015
Fn是一个函数,fn对象是从Fn函数new出来的,这样fn对象就可以调用Fn.prototype中的属性。因为每个对象都有一个隐藏的属性 ----“__proto__”,这个属性引用了创建这个对象的函数的prototype。即:fn.__proto__ === Fn.prototype.这里的"__proto__"成为“隐式原型”。
prototype与__proto__的区别
两者都是对象类型的属性,并非所有的对象类型都有prototype属性,一般只有function对象才有prototype属性(除非主动赋值),它指向的是一个对象,将来会被多个该function的实例所继承(或者说该对象处于多个实例的原型链上);__proto__才是真正的原型链的实际指针,然而许多浏览器并不对外公开这个属性,Firefox暴露出这一属性,仅供开发人员理解,但不推荐开发中使用,目前chrome也可以支持了。
小插曲:instanceof表示的就是一种继承关系,或者原型链的结构
typeof在判断到引用类型的时候,返回值只有object/function,你不知道它到底是一个object对象,还是数组,还是new Number等等。这个时候就需要用到instanceof.
instanceof的判断队则是:沿着A的__proto__这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。如下图:
所有构造器/函数的__proto__都指向Function.prototype,它是一个空函数(Empty function),JavaScript中有内置(build-in)构造器/对象共计12个(ES5中新加了JSON),这里列举了可访问的8个构造器。剩下如Global不能直接访问,Arguments仅在函数调用时由JS引擎创建,Math,JSON是以对象形式存在的,无需new。它们的__proto__是Object.prototype。如下:
1 Number.__proto__ === Function.prototype // true 2 Boolean.__proto__ === Function.prototype // true 3 String.__proto__ === Function.prototype // true 4 Object.__proto__ === Function.prototype // true 5 Function.__proto__ === Function.prototype // true 6 Array.__proto__ === Function.prototype // true 7 RegExp.__proto__ === Function.prototype // true 8 Error.__proto__ === Function.prototype // true 9 Date.__proto__ === Function.prototype // true 10 11 Math.__proto__ === Object.prototype // true 12 JSON.__proto__ === Object.prototype // true
注:上述代码中__proto__目前在IE6/7/8/9中都不支持,IE9中可以使用Object.getPrototypeOf(ES5)获取对象的内部原型。除了IE(IE11开始支持),其他的浏览器支持非标准的访问器__proto__。那么那些不支持__proto__属性的可以通过constructor间接得到,constructor属性不是对象自己的属性,而是顺着原型链向上从原型对象中获取的。这个属性指向的是这个原型对象所对应的构造函数。而构造函数的prototype属性指向了原型对象, 所以这样我们就可以间接得到了,例:
function Foo(){}; var foo = new Foo(); alert(foo.constructor.prototype == Foo.prototype); // true
每个对象都有一个__proto__属性,原型链上的对象正是依靠这个__proto__属性连结在一起的,__proto__是否指向实例对象的原型prototype对象的引用。什么是原型链呢?简单来说,访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。所有对象都继承于Object,原型链的顶端就是Object.prototype,Object.prototype.__proto__ = null;看图理解下:
对象的原型链是沿着__proto__这条线走的,因此在查找f1.hasOwnProperty属性时,就会顺着原型链一直查找到Object.prototype。由于所有的对象的原型链都会找到Object.prototype,因此所有的对象都会有Object.prototype的方法。这也是所谓的“继承”。 说一个函数的例子吧,我们都知道每个函数都有call,apply方法,都有length,arguments,caller等属性。为什么每个函数都有?这肯定是“继承”的。函数由Function函数创建,因此继承的Function.prototype中的方法。
理解了原型与原型链后,那么用原型有什么好处呢?
1.对象属性可以随时改动,对象或者函数,刚开始new出来之后,可能啥属性都没有。但是你可以根据你的需要继续添加,非常灵活。
2.如果继承的方法不合适,可以做出修改。例如在json2.js源码中,为Date、String、Number、Boolean方法添加一个toJSON的属性。
3.可以继续创建新的方法,不过如果你要添加内置方法的原型属性,最好做一步判断,如果该属性不存在,则添加。如果本来就存在,就没必要再添加了。
记:博文参考了网上很多关于原型的介绍,归纳得不足之处,请指正。