该系列文章将带你全面理解js对象和原型链,并用es5去实现类以及认识es6中class
的美妙。该系列一共有3篇文章:javascript
__proto__
和prototype
来深刻理解JS对象和原型链javascript
实现类与继承ES6
的class
此篇为第一篇从__proto__
和prototype
来深刻理解JS对象和原型链java
prototype
和__proto__
引用《JavaScript权威指南》的一段描述es6
Every JavaScript object has a second JavaScript object (or null ,
but this is rare) associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.
复制代码
翻译过来就是:每个JS对象必定关联着另一个JS对象(也许是null
,可是它必定是惟一的)。这个另外的对象就是所谓的原型对象。每一个对象从它的原型对象中继承属性和方法。浏览器
若是你是初学者,这句话确定很绕吧(不过它确实描述得很精辟)。不要紧,你只须要先把握如下两点就行了:bash
在JS里,万物皆对象。方法(Function
)是一个对象,方法的原型(Function.prototype
)是对象。函数
JS有三种构造对象的方式ui
经过对象字面量this
var person1 = {
name: 'Jzin',
sex: 'male'
}
复制代码
经过构造函数es5
function Person(name, sex) {
this.name = name;
this.sex = sex;
}
var person1 = new Person('Jzin', 'male');
复制代码
所谓构造函数就是:能够经过它来new出一个对象实例的函数。一般构造函数里都用了this
,由于这样子才会给调用它的对象绑定属性。spa
由函数Object.create
构造
var person1 = {
name: 'Jzin',
sex: 'male'
}
var person2 = Object.create(person1);
复制代码
这三种方法的异同到后面还会继续分析,如今你只需掌握如何构建对象就好啦。
以上两点就是这小结你要掌握的东西:
至此,我还没介绍什么是原型,不过不要紧,咱们先看看原型的分类,慢慢你就会理解了。
JS的原型分红两类:隐式原型和显示原型
当你建立一个函数时,JS会为这个函数(别忘了:JS一切皆对象)自动添加prototype
属性,这个属性的值是一个对象,也就是原型对象(即函数名.prototype
)。这个对象并非空对象,它拥有constructor
属性,属性值就是原函数。固然,你也能够本身在原型对象中添加你须要的属性(即函数名.prototype.属性名=属性值
).
那么原型对象(prototype
)的做用是什么呢?能够用原型对象来实现继承,即经过函数构造出来的实例能够直接访问其构造函数的原型对象中的属性。可能有点绕,可是读到后面你就会理解啦。
须要注意的是:
prototype
)只有函数才拥有。咱们后面讲的隐式原型则是全部对象都有。Function.prototype.bind
方法构造出来的函数是个例外,它没有prototype
属性。JavaScript中任意对象都有一个内置属性[[prototype]],在ES5以前没有标准的方法访问这个内置属性,可是大多数浏览器都支持经过__proto__
来访问。如今,所谓的隐式原型就是__proto__
了。
隐式原型的指向
隐式原型指向建立这个对象的函数的prototype
(Object.create
函数构造出来的实例有点例为,后面会说明。其实也不是例为,只是它通过了必定的封装)。看下面的例子
function person(name) {
this.name = name;
}
person.prototype.class = 'Human';
var person1 = new person('Jzin');
console.log(person1.__proto__); //person { class: 'Human' }
console.log(person.__proto__); //[Function]
复制代码
person1
的__proto__
很容易理解:它是由person
方法构造的实例,它的__proto
天然就是person.prototype
person
的__proto__
呢?其实每个方法的构造方法都是Function
方法,也就是全部方法的__proto__
都是Function.prototype
。若是如今还不理解也不要紧,后面会有一副图帮你理解。
隐式原型的做用
__proto__
依次查找。这也是protorype
能够实现继承的缘由。L.__proto__.__proto__ ..... === R.prototype
这个是否为真就好了。这也是instanceof
运算符的原理。后面会讲到。__proto__
和prototype
先上图,若是图片显示不了能够点击这里:传送门。
咱们来理解一下这幅图:
Foo()
Foo
的原型属性prototype
指向了它的原型对象Foo.prototype
。原型对象Foo.protoype
中有默认属性constructor
指向了原函数Foo
。Foo
建立的实例f2,f1
的__proto__
指向了其构造函数的原型对象Foo.prototype
,因此Foo
的全部实例均可以共享其原型对象的属性。Foo
实际上是Function
函数建立的实例对象,因此它的__proto__
就是Function
函数的原型对象Function.prototype
。Foo
的原型对象实际上是Object
函数建立的实例对象,因此它的__proto__
就是Object
函数的原型对象Object.prototype
。Function
函数构造的实例对象。因此全部函数的__proto__
都指向Fucntion.prototype
。Function
函数对象是由它自己建立(姑且能够这么理解),因此Function.__proto__d等
于Function_prototype
Function
函数的原型对象实际上是Object
函数建立的实例对象,因此它的__proto__
就是Object
函数的原型对象Object.prototype
。Object
函数实际上是Function
函数建立的实例对象,因此它的__proto__
就是Function
函数的原型对象Function.prototype
。Object.prototype
的__proto__
是指向null
的!!!相信你经过这幅图,已经对原型有本身的理解了,咱们来总结一下:
__proto__
属性,指向该对象的构造函数的原型对象。__proto__
属性,还有prototype
属性,prototype
指向该方法的原型对象。__proto__
的指向相信通过上面的介绍,你已经能很好地掌握__proto__
的指向了。本节经过一些实际的例子让你更加深刻地理解__proto__
的指向。
在一开始,咱们了解了构造对象的三种方式:(1)对象字面量的方式 (2)new的方式 (3)ES5中的Object.create()。其实,这三种方式在我看来都是一种的,即经过new来构建。为何这么说呢?咱们来仔细分析分析:
经过字面量构造对象
var person1 = {
name: 'Jzin',
sex: 'male'
}
复制代码
其实这种方式只是为了开发人员更方便建立对象的一个语法糖(语法糖:顾名思义,就是很甜的糖,通过代码封装,让语法更加人性化,实际的内部实现是同样的)。
上面也就等价于:
var person1 = new Object();
person1.name = 'Jzin';
person1.sex = 'male';
复制代码
person1
是Object
函数构造的对象,因此person1.__ptoto__
就指向Object.prototype
。
也就是说,经过对象字面量构造出来的对象,其__proto__
都是指向Object.prototype
经过构造函数
function Person(name, sex) {
this.name = name;
this.sex = sex;
}
var person1 = new Person('Jzin', 'male');
复制代码
经过new操做符调用的函数就是构造函数。由构造函数构造的对象,其__proto__
指向其构造函数的原型对象。
在本例中,person1.__proto__
就指向Person.prototype
。
由函数Object.create
构造
var person1 = {
name: 'Jzin',
sex: 'male'
}
var person2 = Object.create(person1);
复制代码
由函数Object.create(obj)
构造出来的对象,其隐式原型有点特殊:指向obj.prototype
。在本例中,person2.__proto__
指向person1
。
这是为何呢?咱们来分析一下。在没有Object.create
函数的日子里,为了实现这一功能,咱们须要这样子作:
Object.create = function(p) {
function F(){}
F.prototype = p;
return new F();
}
var f = Object.create(p);
复制代码
这样子也就是实现了其功能,分析以下:
// 如下是用于验证的伪代码
var f = new F(); //var f = Object.create(p);
// 因而有
f.__proto__ === F.prototype //true
// 又由于
F.prototype === p; //true
// 因此
f.__proto__ === o //true
复制代码
所以由Object.create(p)
建立出来的对象它的隐式原型指向p。
经过上面的分析,相信你对原型又进一步理解啦。咱们再来几题玩玩。
构造函数的显式原型的隐式原型
内建对象(built-in object)的的隐式原型
好比Array()
,Array.prototype.__proto__
指向什么?
Array.prototype.__proto__ === Object.prototype //true
复制代码
好比Function()
,Function.prototype.__proto__
指向什么?
Function.prototype.__proto__ === Object.prototype //true
复制代码
根据上面那幅图,这些也很简单啦。
自定义对象
默认状况下
function Foo(){}
var foo = new Foo()
Foo.prototype.__proto__ === Object.prototype //true
foo.prototype.__proto__ === Foo.prototype //true
复制代码
理由,就没必要解释了吧
其余状况
function Bar(){}
function Foo(){}
//这时咱们想让Foo继承Bar
Foo.prototype = new Bar()
Foo.prototype.__proto__ === Bar.prototype //true
console.log(Foo.prototype.constructor); //[Function: Bar]
复制代码
function Foo(){}
//咱们不想让Foo继承谁,可是咱们要本身从新定义Foo.prototype
Foo.prototype = {
a:10,
b:-10
}
//这种方式就是用了对象字面量的方式来建立一个对象,根据前文所述
Foo.prototype.__proto__ === Object.prototype
console.log(Foo.prototype.constructor); //[Function: Object]
复制代码
注意:以上两种状况都等于彻底重写了Foo.prototype
,因此Foo.prototype.constructor
也跟着改变了,因而constructor
这个属性和原来的构造函数Foo
也就切断了联系。
instanceof
的左值通常是一个对象,右值通常是一个构造函数,用来判断左值是不是右值的实例。instanceof
操做符的内部实现机制和隐式原型、显式原型有直接的关系,它的内部实现原理是这样的:
//设 L instanceof R
//经过判断
L.__proto__.__proto__ ..... === R.prototype ?
//最终返回true or false
复制代码
也就是沿着L的__proto__
一直寻找到原型链末端,直到等于R.prototype
为止。知道了这个也就知道为何如下这些奇怪的表达式为何会获得相应的值了
Function instanceof Function //true
Function instanceof Object // true
Object instanceof Function // true
Object instanceof Object // true
Number instanceof Number //false
Number instanceof Function //true
Number instanceof Object //true
复制代码
你发现没有:这就是原型链啊!!!
L1.__proto__
指向R1.prototype
,
R1.prototype.__proto__
指向R2.prototype
...
Rn.prototype.__proto__
指向Object.prototype
Object.prototype.__proto__
指向null
这样子就把原型串起来啦,也就是实现了继承。也就是为何全部对象都要toString
方法,由于这个方法在Object.prototype
上面啊啊啊啊。
至此,相信你已经彻底理解原型和原型链了。固然,只是理解不实践是没用的。在下一篇,咱们将利用原型来实现类与继承。