本文中心:ide
这篇文章比较难懂,因此读起来比较晦涩。因此,我简单列一下提纲:函数
在第一部分,从函数原型开始谈起,目的是想搞明白,这个属性是什么,为何存在,在建立对象的的时候起到了什么做用!测试
在第二部分,阅读的时候,请分清楚__proto__和内置对象的区别;搞清楚这点。而后,咱们再一点点分析__proto__属性。ui
第三部分,原本不在我写做的范围,可是看到网上的不少文章在继承的时候,使用的方法五花八门。因此来谈一下,Object.create()这个方法的好处。this
标题中所谓的特有,指的是只有函数才具备prototype属性,ECMAScript标准规定每一个函数都拥有一个属于本身的原型(prototype)。spa
那么这个函数的原型究竟是什么,它又有什么用呢?firefox
console.log(typeof Object.prototype); //"object", 这里用到了Object()函数。 console.log(Object.prototype instanceof Object) //true
从上面的输出结果中,咱们得出函数的原型是一个对象。那么,这个对象自己有什么属性呢?
咱们知道,任何一个对象都具备最基本的方法,好比 toString().valueof()...既然函数原型是对象类型,那么它确定也具备这些基本的方法...
因此这些方法是从哪里来的呢?要想搞清楚这些,那么咱们就必需要从Object()的原型谈起!
上面这幅图片,帮咱们认清楚了Object()函数的原型,这个函数原型自己不具备任何属性,可是其具备一些很基本的方法,这些方法有什么用,这里暂且不论。可是到目前为此,请记住一点:函数原型是一个对象。由于只有知道了最基本的这一点,咱们下面的讨论才具备意义。prototype
var object = new Object(); //new 一个对象 console.log(object.toString()); //输出这个对象,firefox控制台下输出结果 [obejct object]
var object = new Object(); console.log(object.__proto__==Object.prototype); //true.
事实再一次证实,世上没那么多的巧合!!object.__ptoto__和Object.prototype真的指向的是同一个对象。设计
如今咱们解决了一个问题,就是object.toSring()这个函数,真正的来源是Object.prototype。那么object对象为何能访问Object.prototype中的方法呢...要回答这个问题,须要弄清楚两件事情:var object=new Object();
var object = new Object(); var proto1 = {}; proto1.name = '张三' object.__proto__ = proto1; console.log(object.name); //张三 var proto2 = {}; proto2.name = '李四' object.__proto__ = proto2; console.log(object.name); //李四
在建立一个新的函数的时候,这个函数的原型中会有一个constructor属性,那么这个属性是否有存在的意义呢?3d
看下面的一段代码:
var Person=function(){}; console.log(Person.prototype.constructor); //function constructor是一个函数 console.log(Person.prototype.constructor===Person);//true Person.prototype.constructor===Person
上述代码,证实了constructor这个属性是真实存在的,且这个属性的值初始化为构造函数自己。那么这个属性有什么很重要的意义吗? 再看下面的码:
var Person = function () { }; var xiaoming = new Person(); console.log(xiaoming instanceof Person); //true Person.prototype.constructor = null; console.log(xiaoming instanceof Person); //true
由上面例子能够得出,constructor属性只是标识原型是归属于哪一个函数的,这个属性虽然是解释器给咱们默认的,可是相对来讲没有那么重要,甚至提及来能够是一点用处都没有。对于一个函数,在刚建立的时候老是这个样子的。
有些事情在你出生的那一刻就已经注定要发生。prototype在出生之初就已经注定其宿命。下面让咱们来谈谈这所谓的宿命吧!!
根据1.1部分,咱们知道函数的原型,在函数实例化的时候会被赋值给实例的内置属性的。假设有两个类A和B,代码以下:
//A函数以下 var A = function (a) { this.a = a; } A.prototype.getA = function () { return this.a; } // B函数以下 var B = function (a, b) { A.call(this, a); //借用A类构造函数,很重要的一点!!! this.b = b; } B.prototype.getB = function () { return this.b; }
A和B分别是两个类的构造函数,他们此时在内存中的结构以下图所示:
如今若是咱们想让B类成为A的子类,该如何作呢? 首先,咱们应该认识到一点,若是B是A的子类,那么B就应该能访问A中的属性和方法。父类A中有属性a和方法getA(),那么子类B中也应该有属性a且能访问方法getA();若是咱们能实现以下图所示的结构是否就能作到B类继承A类呢?
与上图相比,仅仅修改了B.prototype中的【【__proto__】】.而后一切的一切都天然而然的发生了。总之,子类B为了继承A作了两样活: 子类B类经过A.call();这一步借用A的构造函数拥有的A类的变量,又经过修改原型中的【【__proto__】】才作到能访问A类中的函数..想到这里不得不说一句,好伟大的设计。若是只是为了实现继承,有N多种方法能实现,可是请注意,若是考虑内存中的分配状况以及效率和程序的健壮性,那么就只有一个函数可以完美的作到图中所示的那样。这个函数就是Object.create()函数,这个函数的宿命就是为了实现继承。
为何这么说呢,请看第二部分慢慢解析!!
若是你认为本身很了解这个属性,那么请思考如下几个问题?
1.这个属性是什么性质的属性? 访问器属性 or 数据属性?
2. 这个属性存在在哪里? 是每一个对象都有,仍是在整个内存中仅有一份。
3.这个属性与内置属性有什么关系?
若是你对上面的上个问题很困惑,或者你认为__proto__就是内置属性的话,那么咱们仍是花一点时间正正三观吧。
看下面一段代码:
var descriptor=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__"); console.log(descriptor); //输出结果:
configurable | true |
enumerable | false |
get | function() { [native code] } |
set | function() { [native code] } |
看到上面的输出结果,你是否已经接受了__proto__就是一个访问器属性呢....若是你还不相信..那么接着看,这只是践踏你世界观的开始!!!
从证实1的输出结果中,咱们知道configurable=true;这也就告诉咱们这个对象是能够被删除的...下面看一段代码:
var object={}; var result=delete object.__proto__; console.log(result); //true console.log(typeof object.__proto__) //object.
请回答? 为何显示删除成功了, typeof object.__proto__仍是输出 object呢?
ok!! 要理解透彻这些,咱们插入一些delete运算符的知识.
ECMAScript规定:delete 运算符删除对之前定义的对象属性或方法的引用,并且delete 运算符不能删除开发者未定义的属性和方法。
那么什么状况下,delete会起做用呢?
delelte运算若是想正常操做必须知足三个条件: 1,该属性是咱们写的,即该属性存在。2.删除的是一个对象的属性或方法。3.该属性在配置时是能够删除的,即(configurable=true)的状况下能够删除。
那么,上面的例题中,返回值为true.,它符合上面的三个条件吗?
对于1,该属性咱们是能够访问的,因此,证实该属性存在。
对于2,__proto__是某个对象的属性。
对于3:由于 configurable=true,因此也是符合的。
ok!上面的三点都符合,在返回值等于true的状况下,删除仍是失败了呢! 由于还有下面一种状况,就是在对象上删除一个根本不存在的于自身的属性也会返回true!
var object = { };
Object.prototype.x={}; var result = delete object.x; console.log(result); //true.console.log(object.x);//object
看到没有,这两个例子在输出结果上很类似呢?由于 __proto__属性存在于该对象上的原型上面,因此,该对象能够访问。可是不能删除该属性。若是想删除该属性,那么请在Object.prototype上删除。这个保证能删除成功。
为了证明这一点,咱们再看一下
var object={}; console.log(typeof object.__proto__); //object delete Object.prototype.__proto__; console.log(typeof object.__proto__); //undefined 删除成功。
咱们能够发现,在Object.prototype删除__proto__属性后。object上也没法访问了。这是由于,因此对象都有一个共同的原型Object.prototype.在这个上面删除__proto__,那么全部的对象也都不具备这个__proto__属性了。
这也就证实了,内存中仅存一份__proto__属性,且该属性定义在Object.prototype对象上。
从某种程度上来讲,__proto__若是存在,那么它老是等于该对象的内置属性。并且在上一篇文章中咱们也点出了一点,改变__proto__的指向也能改变内置属性的指向。因此,若是你执拗的把__proto__认为就是内置对象,那也无可厚非。
可是请记住两点:
1. 内置对象不可见,可是内置对象老是存在的。
2.__proto__若是存在,那么它的值就是内置对象,可是这个__proto__并不老是存在的。
若是你必定认为__proto__就是内置对象,也能够,可是请保证两点:不要在程序的任何地方用__proto__属性。或者,若是你必定要用__proto__这个属性的话,请保证永远不要修改Object.prototype中的__proto__!!
若是你不能保证这两点,请远离__proto__.由于,一旦有人不遵照约定的话,这个bug的危害代价太大。好比,下面这样...
var A = function () { } A.prototype.say = function () { return 'hello'; } var B = function () { } //子类继承父类 function extend(subClass, superClass) { var object = { }; object.__proto__ = superClass.prototype; subClass.prototype = object; subClass.prototype.constructor = subClass; } extend(B, A); //B继承A
var b = new B(); b.say();
上面是一段,毫无问题的代码...可是若是有一个小白用户,在某一天执行了下面一句代码,
var A = function () { } A.prototype.say = function () { return 'hello'; } var B = function () { } function extend(subClass, superClass) { var object = { }; object.__proto__ = superClass.prototype; subClass.prototype = object; subClass.prototype.constructor = subClass; } delete Object.prototype.__proto__; //或则其余的等等 extend(B, A); var b = new B(); b.say(); //TypeError: b.say is not a function 报错...若是是这种错误,调试起来确定会让你欲哭无泪的。因此,若是你想写出好的程序,请远离__proto__.
时无英雄,使竖子成名! JavaScript的今天的盛行,能够说就是这句话的写照。Object.create()也是这样,在继承时并非咱们非用它不可,只是在排除了使用__proto__以后,除了使用这个函数以外,咱们没有其余更好的选择。
这个函数在W3C中这个函数是怎么定义的呢?
这是这个函数在W3C中的定义,我来举个例子来讲明这个函数怎么用吧!!!
var A = function (name) { this.name = name; }; A.prototype.getName = function () { return this.name } var returnObject = Object.create(A.prototype, { name: { value: 'zhangsan', configurable: true, enumerable:false, writable:true } });
上述代码运行完毕以后,returnObject在内存中的结构如图所示:
看看上面这张图,在类比1.3中的最后一张图,以下:
发现是否是,惊人的类似...因此,知道Objece.create()的强大了吧!! 咱们分析过,下面这张图是实现继承的完美状态,而Object.create()就是为了作到这些,专业为继承而设计出来的函数。
下面是一段用Object.create()函数实现子类继承父类的代码;
//子类继承父类,这段代码在执行delete Object.prototype.__proto__;这段代码以后仍然能够正常运行。
function extend(subClass, superClass) { var prototype=Object.create(superClass.prototype); subClass.prototype =prototype;
subClass.prototype.constructor = subClass; }
var A = function () {
}
A.prototype.say = function () {
return 'hello';
}
var B = function () {
}
extend(B, A);
var b = new B();
b.say(); //hello
Ok! 我知道,你能用N多种方法实现继承,可是请记住,在继承的时候请不要用__proto__这个属性,由于它没你想象中俺么可靠。若是你想得到一个对象的原型,那么这个方法能够作到,Object.getPrototypeOf。与之对应的是Object.setPrototypeOf方法。
也许你也会说,Object.setPrototypeOf方法能够在远离__proto__的状况下实现继承啊啊...若是你在看到它的源代码你还会这么说吗?
Object.setPrototypeOf = Object.setPrototypeOf || function(obj, proto) { obj.__proto_ _ = proto; //也是用到了__proto__. return obj; }
总结:
整篇文章,从prototype谈起,分析了函数的prototype的类型与做用(这个你们都在谈)。
在第二部分,咱们分析了__proto__,获得的结果,内置属性和__proto__根本不是一回事。__proto__这个属性不可靠..撇开,这个属性是非标准属性不说,这个属性隐藏的bug就能致人于死地,因此,在写程序时,请谨记一点,珍爱生命,远离__proto__.
在最后,咱们浅谈了一下,用Object.create()实现继承的好处。这个部分,很难讲的清楚,须要慢慢去体会。
在下一篇中,咱们会分析,为何会说JS中一切皆是对象!。。。