abbshr.github.io
abbshr.github.io copied to clipboard
Little JavaScript Book『拾扒』———Chain
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
属性改写为指向构造函数。
从一个测试开始
我曾经为了弄懂原型链而做过一个测试,然后得出这么一个结论(别喷我。。):
- 向Object.prototype中添加的成员可以被全部构造函数直接或通过原型调用。
- 向Function.prototype中添加的成员可以通过Function或Function.prototype调用而其他构造函数只能直接调用,若用原型调用返回
undefined
- 向其它内置构造函数的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.prototype
,Function.prototype
的内容(也就是Object.prototype)便能被Function
等构造函数对象直接访问。
由于Object.__proto__
继承了Function.prototype
,所以Object
可以直接调用Function.prototype
的成员。并且从上面的分析可知,Function.prototype.__proto__
继承了Object.prototype
,所以Object不仅可以通过prototype调用Object.prototype
的成员,还可以直接访问。