abbshr / abbshr.github.io

人们往往接受流行,不是因为想要与众不同,而是因为害怕与众不同

Home Page:http://digitalpie.cf

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Little JavaScript Book『拾扒』———Chain

abbshr opened this issue · comments

Prototype

JS除了有函数式编程、事件驱动编程、命令式编程的泛式之外,更支持面向对象编程这一泛式。

谈到OOP,当然少不了“继承”这一特性。
在JavaScript中没有类的概念,这句话也是各种教程中常说但常常自相矛盾的一句。

没有类该如何继承?
这就是原型(prototype)出现的原因。JavaScript中继承的实现是通过设置原型完成的。原型是一个对象 ,同时也是每个JS对象的一个隐性的属性,也就是说在遍历对象的全部属性时不会显示这个原型。对象会自动获取原型中的属性并允许直接使用。

其实继承可以选择类式继承原型继承
对于熟悉Java/C++的人选择类式继承会感觉更容易理解。不过原型继承更容易实现。

var object = {};  //定义一个空对象
object._proto_ = {  //直接为其设置原型
    name: "Ran Aizen",
    age: 19
};
console.log(object.name); // "Ran Aizen"

var Foo = function () {};  //构造函数
Foo.prototype = { private: fasle };  //构造函数的原型
var repo = new Foo();   //实例化为一个对象
console.log(repo.private);  //false

请看以上两部分代码,第一部分是为单个对象设置了原型;第二部分是通过在构造函数中添加原型从而使实例化结果继承了原型。
这里不推荐使用_proto_ 属性,这个属性是存在于每个对象中的隐藏属性,代表了该对象的原型,如果对其进行修改不当可能会影响到constructor 的指向和 instanceof的返回结果的问题。
prototype属性时推荐使用的,他被用来给实例化对象设置原型。

对于_proto_prototype可以这么理解:
prototype属性是构造函数内使用的,用来设置原型继承的不可枚举属性。
_proto_属性是所有对象内使用的,用来指向其原型并构成原型链的不可枚举属性。

Prototype Chain

刚才上面提到了原型链一词。他是JavaScript实现面向对象的核心中的核心,同时也是公认的最难理解的特性之一。

这里给出一段官方的解释:

继承方面,JavaScript中的每个对象都有一个内部私有的链接指向另一个对象 (或者为 null),这个对象就是原对象的原型. 这个原型也有自己的原型, 直到对象的原型为null为止. 这种一级一级的链结构就称为原型链.

Object.prototype

他是原型链的最顶端,所有对象的基本属性(例如:toString()valueOf())全部继承自Object.prototype对象,而 Object.prototype的原型—— Object.prototype.__proto__对象则是整个原型链的终点,他的指向是关键字null。(还记得对null的描述吗?)

看清原型链

我们先声明一个空对象:

var obj = {};

并且obj相当于:

var obj = new Object();

所以obj是一个由Object构造函数实例化的一个普通对象,那么他应该直接继承自Object.prototye对象。因此:

obj.__proto__ === Object.prototype
//返回true

再比如说:

var arr = [];
//声明一个数组

arr.__proto__ === Array.prototype
//返回true
arr.__proto__ === Object.prototype
//返回false

var func = function () {};
//声明一个函数表达式

func.__proto__ === Function.prototype
//返回true
func.__proto__ === Object.prototype
//返回false

constructor与原型

上面我举了两个特殊对象(函数和数组)的例子。很明显间接的原型不被认可。虽然所有对象无论直接间接都继承自Object.prototype,但是对象的__proto__属性仅指它向直接继承的对象。

不知大家能否从中得出这个结论:
对象的constructor属性取决于对象的__proto__属性。
其实在运算符instanceof内部就是依据对象的__proto__属性来判断该对象是否是某个构造函数的实例。

实例化的背后

在引擎内部,每当构造函数实例化了一个对象时,构造函数内部返回的对象的__proto__就会自动指向构造函数的prototype,从而将构造函数的原型继承下来。然后对象的constructor属性改写为指向构造函数。

从一个测试开始

我曾经为了弄懂原型链而做过一个测试,然后得出这么一个结论(别喷我。。):

  1. 向Object.prototype中添加的成员可以被全部构造函数直接或通过原型调用。
  2. 向Function.prototype中添加的成员可以通过Function或Function.prototype调用而其他构造函数只能直接调用,若用原型调用返回undefined
  3. 向其它内置构造函数的prototype属性中添加成员,只能被所有构造函数以.prototype方式调用

总结的很潦草,也没抓住本质。我们已经知道,Object.prototype是原型链的最高级,不过为什么在Function.prototype中添加的成员能被Object访问?为什么Object和Function能够直接访问各自prototype中的成员?

事实

Object能够访问Function.prototype让我们百思不得解时,我们可能忽略了一个重要的因素:Object、Array、Date等等,还有Function,他们不仅仅是对象,还是一个函数,而函数的原型__proto__ 是来自Function.prototype的!
情况一下子明朗了~原来Object.__proto__是指向Function.protorype的。并且,Function也是一个函数,所以Function.__proto__也指向Function.prototype
而普通对象统统继承自Object.prototype,所以Function.prototype.__proto__就指向Object.prototype
于是,Function与Object之间的相互继承形成了一个环形的引用。

这样我们就能够解释上面提出的两个问题了:

由于Function.prototype.__proto__继承Object.prototype,来自Object.prototype的成员就能够被Function.prototype访问到;Function等构造函数继承了Function.prototypeFunction.prototype的内容(也就是Object.prototype)便能被Function等构造函数对象直接访问。
由于Object.__proto__继承了Function.prototype,所以Object可以直接调用Function.prototype的成员。并且从上面的分析可知,Function.prototype.__proto__继承了Object.prototype,所以Object不仅可以通过prototype调用Object.prototype的成员,还可以直接访问。

proto