简单易懂的JS原型链详解

书中仙
2019.02.15 13:54 字数 4956 阅读 90 评论 1 喜欢 0

在讲解原型链之前我们先来讲解几个概念。

一、普通对象与函数对象

javascript中万物皆对象,但是对象与对象之间也是有区别的,js中的对象分为普通对象和函数对象。那么什么是普通对象什么是函数对象呢?

            var oA = {}; 
var oB =new Object();
var oC = new fA();

function fA(){};
var fB = function(){};
var fC = new Function('hello','alert(hello world)');

console.log(typeof Object); function
console.log(typeof Function); function

console.log(typeof oA); object
console.log(typeof oB); object
console.log(typeof oC); object

console.log(typeof fA); function
console.log(typeof fB); function
console.log(typeof fC); function

从上面的例子中我们可以看出 fA fB fC都是函数对象,oA oB oC都是普通对象,其实区分起来很简单,凡是通过new Function()创建的对象就是函数对象,其它创建方式创建的就是普通对象,fA fB 归根结底都是通过new Function()创建的。值得注意的是,Object和Function都是函数对象,也就是说Object也是通过new Function()创建的,这一点需要牢记,在下面还有用到。

一定要分清楚普通对象和函数对象,本篇通篇都用到了这两个概念。

二、__proto__属性和prototype属性

很多人这两个属性搞不清楚,我这边来总结一下。任何对象都有__proto__属性,但是只有函数对象有prototype属性,一定要搞清楚,只有函数对象有prototype属性。__proto__属性指向其构造函数的prototype,现在需要记住这一个恒等式。

            function animal(name) {
this.name = name;
}
var Elephant = new animal('elephant');

console.log(Elephant.__proto__ === animal.prototype); //true

首先animal是一个构造函数,Elephant是animal的实例,Elephant.__proto__ ===  animal.prototype 记住这个恒等式原型链你就理解了一半。

不过要明确的真正一点是这个连接存在于实例(Elephant)与构造函数(animal)的原型对象之间(aniaml.prototype),而不是存在于实例(Elephant)与构造函数(animal)之间,也就是说原型链的连接是靠__proto__属性。

三、原型对象和constructor属性

原型对象属于普通对象(Function.prototype是特例,它是函数对象),它是普通对象下边一个更小的集合。原型对象和普通对象有没有区别呢?有区别,我个人理解的区别就是原型对象有一个constructor属性,且constructor属性只会出现在原型对象上。那可能有人会问了

var a = {};
console.log(a.constructor); //Objcet

我直接新建一个a对象,直接打印它的constructor属性,为什么打印出来有值呢?那你要知道,这个constructor属性并不在a对象上啊,访问a对象的constructor的时候发现a对象并没有这个属性于是去它原型上找,a对象的构造函数是Object,然后就找到了Object上,a.constructor其实就是Object.prototype.constructor。

var a = {};
console.log(a.constructor === Object.prototype.constructor); //true

这时候我们得到一个结论:constructor只存在于原型对象上,constructor指向其原型对象所在的构造函数,但是这个指向可以修改。

现在我们回过头来看上边那个animal的例子,我们知道Elephant.__proto__ ===  animal.prototype,那么animal.prototype是一个原型对象,凡是对象都有__proto__属性,那么animal.prototype.__proto__指向谁呢?

我们知道animal.prototype是一个原型对象,那么原型对象属于普通对象,普通对象的构造函数是Object,animal.prototype.__proto__当然指向Object.prototype了。

console.log(animal.prototype.__proto__ === Object.prototype); //true

现在我们根据上述所说可以得到以下结论

1、任何对象都有__proto__属性,__proto__属性指向其构造函数的prototype。

2、只有函数对象才有prototype属性,prototype属性指向其构造函数的原型对象。

3、只有原型对象才有constructor属性,constructor指向其构造函数本身。

四、Object和Function

这两个是比较特殊的对象,我们拿出来单独说。还是上述animal的例子,我们知道animal.prototype.__proto__ === Object.prototype,那么Object.prototype.__proto__指向谁?答案是指向null,没了,到头了,因为不可能这样无限的指下去。原型链就是这样在__proto__上找来找去,在当前对象上找不到就去上一级找,还找不到就去再上一级,最后找到Object.prototype.__proto__还找不到,那就没了。

那么Object.__proto__指向谁呢?我们思考一下,上述第一节所说,Object也是函数对象,那么函数对象都是通过new Function()来的,也就是说Object的构造函数就是Function,那么Object.__proto__ === Function.prototype。

从上述第三节我们得知绝大部分的原型对象都是普通对象,只有一个例外,Function.prototype是函数对象,那么为什么他是函数对象呢?我们可以这么理解,函数对象的原型对象其实可以理解为函数对象的实例,比如

var A = new Function ();
Function.prototype = A;

虽然确切的说函数对象的原型对象不是函数对象的实例,但是我们平时可以这么理解,A是函数对象,Function.prototype = A,那么Function.prototype就是函数对象。

那么Function.__proto__又指向谁呢?我们知道__proto__属性指向它构造函数的prototype,Function的构造函数就是Function,所以Function.__proto__ === Function.prototype。

最后一个Function.prototype.__proto__指向谁?按照我们之间的结论,__proto__属性指向它构造函数的prototype,Function.prototype是函数对象,那么它的构造函数就是Function.prototype,那么Function.prototype.__proto__就应该指向 Function.prototype,但是我们打印会发现

console.log(Function.prototype.__proto__ === Function.prototype);  //false

然后

console.log(Function.prototype.__proto__ === Object.prototype);  //true

Function.prototype.__proto__ 实际上是指向 Object.prototype的,这个我也不知道为什么,只能先这样记住吧。

最后总结一下Function和Object

Object.__proto__ === Function.prototype;
Object.prototype.__proto__ === null;
Function.__proto__ === Function.prototype;
Function.prototype.__proto__ === Object.prototype;

看似好像乱七八糟的,其实只要你明白原理,这些都能够推导出来的。

五、原型链

明白上边几个概念之后我们现在可以看原型链到底是什么了

首先  1、原型和原型链是JS实现继承的一种模型

         2、原型链的形成是靠__proto__而非prototype

要深入的理解这句话我们再举一个例子,看看你是否真的理解

var animal = function(){};
var cat = function(){};

animal.price = 2000;
cat.prototype = animal;
var theChinaCat = new cat();
console.log(cat.price) undefined;
console.log(theChinaCat.price) 2000;

我们来看一下,首先我们访问cat.price,cat上肯定没有啊,然后就去cat.__proto__上找,cat.__proto__指向谁呢?指向它的构造函数的prototype,cat是一个函数,它的构造函数就是Fucntion,所以cat.__proto__指向Function.prototype,Function.prototype是一个空函数啊,那么再去Function.prototype.__proto__上找,那么根据上文我们知道Function.prototype.__proto__指向Object.prototype,Object.prototype也没有price属性啊,再去Object.prototype__proto__上找,Object.prototype__proto__ === null,没了,所以cat.price 输出 undefined。

然后我们来看theChinaCat.price,theChinaCat上也没有price属性啊,然后顺着原型链找,theChinaCat.__proto__ === cat.prototype,然后发现我们在上一步指定了cat.prototype = animal,再上一步指定了animal.price = 2000,找到了,所以theChinaCat.price 输出 2000。

原型链就是这样子,顺着__proto__属性一级一级的往上找,只要你能搞清楚它的构造函数是谁,那你的原型链没有任何问题。

看到这里你应该能明白原型链到底是个什么东西了,希望我的文章对你有帮助。


支付二维码
登录 后发表评论
${comment_count}条评论 评论

智慧如你,不想发表一点想法咩~

推荐阅读
更多精彩内容