一、一切皆为对象
JavaScript是一个面向对象(原型对象)语言,除了一些基础类型,一切皆为对象,所有的对象都是函数(Function也是对象)创建的。
面向对象的继承等一些特性,像极了人类的传承和繁衍。我们先解释下一切皆为对象:都是爹养娘生的
。对于JS原型链的鼻祖Object.prototype就像是人类的祖先一样,是其他物种进化而来的,或者是女娲捏出来的,就不是Function构造出来的。
1.1 原型对象
原型对象是面向对象中的一种。区别于面向对象,JavaScript中的原型对象是无类型(class)
的,虽然ES6中加入了class这一特性,但是这个class就只是一个语法糖,并不是一个面向对象类的实现。JavaScript通过原型链这一特性来实现对象的继承。
/**
* 通过构造函数创建对象
*/
function Obj() {}; // Obj.propotype 的构造函数
console.log(Obj.propotype.constructor); // function Obj
var obj = new Obj(); // Obj.propotype 的实例对象
console.log(obj.__proto__); // Obj.propotype
以上代码实现了用Obj.propotype原型对象的构造函数Obj创建了实例对象obj的一个示例,他们的关系如下图:
解释一下Obj.propotype示意图:
function Obj(); // 构造函数
Obj.propotype; // 原型对象
var obj = new Obj(); // 实例对象
1.2 原型对象与面向对象的区别
一些后端开发的同学对于面向对象的第一印象可能就是Object。像Java这样的语言中类都是继承Object的,恰好JavaScript也有Object,大家可能先入为主的认为JS中的Object就是Java中的Object,这种想法是错误
的。
非常典型的一个例子(反例)就是许多人在问:Function和Object到底是什么关系?为什么Object是Function构造出来的?为什么Function能够构造出它自己?
Object大兄弟,你的家庭关系有点乱哦。
Object.__proto__ === Function.prototype; // true
Function.__proto__ === Function.prototype; // true
说好的实例的__proto__
(隐式原型)等于对象的prototype
(原型)啊;说好的一切都是对象,函数也是对象啊;为什么Object反而是Function构造出来的?这不是逗我玩呢?
引起这样的误解是因为平时我们都以构造函数的名称来简称一个原型对象,其实Function和Object都只是构造函数
,JavaScript的所有对象都是继承Object.prototype的,并不是Object!不是Object!不是Object!Function和Object都是function
声明的函数,所以它们是继承Function.prototype的,是Function构造出来的。这里就不举人类起源的例子了,有点不可描述,留在第三节说。
不知道上面的反例是把你们掰直了还是掰得更弯了。。。。
举一反三这种能力对于学习是很重要的,但是非常忌讳先入为主的思想,虚心使人进步。
二、继承
继承的意思不难理解,就是子承父业或者是子承祖业。在ES6之前,JavaScript没有明确规定继承的概念,ES6中才使用了extentds来规范继承的方式。
2.1 new 的实现原理
看了很多同行实现js继承的方案,很多都是基于new和Object.create。其实在构造函数没有返回值的情况下,new和Ojbect.create(Obj.prototype)实现的效果是差不多的,都能够继承Obj.prototype,大家可以跑一下下面的代码对比效果:
function Obj() {
console.log('I am Obj constructor...');
this.name = 'Obj';
}
// 这里的参数绝对不是Obj,如过是Obj,则是继承了Function
var obj2 = Object.create(Obj.prototype);
var obj3 = new Obj();
我们可以看到,对比于Object.create,new不仅继承了Obj.prototype,还调用了构造函数Obj。由此我们可以试着推理下new的实现:
function fnew(constructor) {
var newObj = Object.create(constructor.prototype); // 继承Obj.prototype
constructor.call(newObj); // 调用父对象的构造函数
return newObj;
}
var obj4 = fnew(Obj);
我们通过fnew得到的obj4和new实现的obj3是一样的!
2.2 extends 的实现原理
按照我们对于原型链的理解,new出来的实例对象是不是也是js的继承呢?我理解的继承是祖传父,父传子,子传孙。但是new出来的实例对象因为缺少构造函数而失去了再继承和实例化的能力,就是说子不能传孙了,这不是我想要的继承!我要的继承是像Function这样继承Object后还具备‘生育能力’的,像extends这样的。
结合我们人类的行为来思考下,要想生孩子得有爹和娘啊。现在new出来的实例对象再嫁给一个构造函数就可以愉快的生孩子了,让我们改造下fnew:
function fextends(protoConstructor, constructor) {
var newObj = Object.create(protoConstructor.prototype); // 继承Obj.prototype
protoConstructor.call(newObj); // 调用父对象的构造函数
constructor.prototype = newObj;
constructor.prototype.constructor = constructor;
return constructor;
}
var ObjChild = fextends(Obj, function() {});
这样ObjChild就实现了Function extend Object的效果啦。那我们以此分析下ES6的class和extends其实就是类似于fextends(Object, function() {})的语法糖,class A extends B 就是类似于 var A = fextends(B, function A(){})的效果咯。
2.3 instansof原理和实现
MDN对instansof的解释是:instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置。比如Function instanceof Object
就是判断Object.prototype是否出现在Function的原型链( __proto__
)中。
// instansof
function finstansof(a, b) {
if(a.__proto__) {
if(a.__proto__ === b.prototype) {
return true;
} else {
return finstansof(a.__proto__, b);
}
}
return false;
}
看完instansof示意图和代码,相信大家都掌握了instansof这一特性的原理。下次再有人跟你说A instansof B
的意思是判断A是否继承B的这样不严谨的谬论,你告诉我,我给他寄一盒的刀片。
下面再给大家举一个反例:世界上是先有Object还是先有Function?
Function instanceof Object // true
Object instanceof Function // true
毋庸置疑,Function(Function.prototype的构造函数)和Object(Object.prototype的构造函数)都是函数,那就是继承Function.prototype的;结论就是先有Object.prototype的。