JS原型及原型链的理解

JS原型及原型链的理解

在Java或C语言中,本身都会提供一个class实现,在ES5/ES6的时候JavaScript引入了class关键字,但这种只是语法糖的形式,JavaScript本身还是依赖于原型的。

说到原型与原型链,我们首先要知道的几个点。

  • JavaScript中只有一个结构:对象,每个实例对象都有一个私有属性(proto),实例对象通过这个属性可以访问到它对应构造函数的原型对象prototype
  • 原型对象也是一个对象,所以它也有私有属性(proto),可以访问到它对应的构造函数的原型对象prototype
  • 函数(function)是允许拥有属性的,所有的函数会有一个特别的属性(prototype)

例子1(实例对象与构造函数):

var a = new A();
此时a为A的实例对象,A为a的构造函数
根据上面三点我们可以得出:
1、a有一个__proto__可以访问到构造函数A的原型对象
2、构造函数有一个属性prototype可以访问到自己的原型对象
所以:a.__proto__ === A.prototype

例子2(构造函数的prototype):

function Fa() {
   
   };
var Fb = function() {
   
   };
var Fc = new Function();
console.log(Fa.prototype);
console.log(Fb.prototype);
console.log(Fc.prototype);

上述代码分别打印:
在这里插入图片描述
我们可以看到这三个函数都由自己的原型对象,并且这个原型对象里有一个construcor和*proto*属性

  • 首先:construcor属性指向构造函数,构造函数通过prototype属性访问到自己的原型对象,原型对象通过construcor属性访问构造函数。
  • 其次:原型对象上也有__proto__属性,那这个是怎么来的呢,开篇的时候,提到过,原型对象也是一个对象,可以理解为原型对象也是一个被一个构造函数构造出来的实例,所以原型对象的__proto__属性指向构造它的构造函数的原型对象,这里需要提出第一个重点:
    所有原型对象都是由Object函数对象构造出来的

值得注意的是:

  • 上面打印出来的construct属性第一个是可以看的很明显是指向构造函数Fa(){}的,那么下面两种为什么没有名字呢?,这里不得不提到函数创建的几种方式和区别:
  • 第一种创建时函数的声明式,具名函数。
  • 第二种是表达式,不具名。
  • 第三种是构造式函数,不遵循典型作用域,会被一直当作顶级函数来执行,有兴趣的伙伴可以多做了解。

例子三(实例的__proto__与构造函数的prototype关系)

function Fa() {
   
   };
var Fb = function() {
   
   };
var Fc = new Function();
console.log(Fa.__proto__);
console.log(Fb.__proto__);
console.log(Fc.__proto__);

打印效果:
在这里插入图片描述
可以看到上述所有函数打印出来的都是一个函数,但是我们并不知道这个函数到底是谁,所以这里需要提出第二个重点:

  • 所有的函数都是由Function函数对象创建的

所以此时我们把上述三个函数都当作是Function的实例对象,那么实例对象Fa Fb Fc的__proto__就应当指向它们的构造函数Function的原型对象,也就是Function的prototype属性所指向的地方,那么这两个所指向的地址是一样的。

  • 结论:
Fa.__proto__ === Function.prototype;
Fb.__proto__ === Function.prototype;
Fc.__proto__ === Function.prototype;
毫无疑问的是,这三个都是true

代码结果:
在这里插入图片描述
由例二中重点可以得出

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

代码结果:
在这里插入图片描述
重点三:
Function函数的prototype属性可以访问到Function函数对象的__proto__属性
怎么理解这句话很重要,函数只有一个,但实例却有很多个,Function顶层函数构造出这个Function函数对象实例,所以:



Function.__proto__ === Function.prototype

代码结果:
在这里插入图片描述
所以

Function.prototype.__proto__ === Object.prototype; // true
Function.__proto__.__proto__ === Object.prototype; // true
Function.__proto__ === Function.prototype // true

例四(原型链):

var Fn = new Function();
Function.prototype.flag = '123';
Fn.__proto__ === Function.prototype; // true
Fn.__proto__.flag // '123'
Fn.flag // '123'

上述代码很明显理清了构造函数与实例对象之间的继承关系

  • 访问实例对象的某个属性时,如果这个属性没有,就会去实例对象.__proto__上去找

我们在下面的代码中再添加一层

var Fn = new Function();
var fn = new Fn();

Function.prototype.flag = '123';
Fn.prototype.flag = '456';

fn.__proto__ === Fn.prototype; // true
fn.__proto__.flag; // '456'
fn.flag; // '456'

Fn.__proto__ === Function.prototype; // true
Fn.__proto__.flag; // '123'
Fn.flag; // '123'

通过上述代码就会帮助理解:

  • 对象被谁构造,那么对象就会继承谁原型上的属性

此例中:

  1. fn由Fn构造,所以fn继承Fn原型上的flag属性,值为456
  2. Fn由Function构造,所以Fn继承Function原型上的flag属性,值为123

理解到这里的时候接下来再看一下的代码:
例五(函数):

var Fn = new Function();
var fn = new Fn();

Function.prototype.flag = '123';
Fn.prototype.flag = '456';
Object.prototype.flag = '789';

fn.__proto__ === Fn.prototype; // true
fn.__proto__.flag; // '456'
fn.flag; // '456'

// 因为所有的原型对象都是由Object函数对象创建的
Fn.prototype.__proto__ === Object.prototype; //true
// 相当于
fn.__proto__.__proto__ === Object.prototype; //true
// 换句话说:
// fn这个实例通过fn.__proto__访问到Fn.prototype的属性
// Fn.prototype(也就是fn.__proto__)通过Fn.prototype.__proto__也就是fn.__proto__.__proto__可以访问到Object.prototype的属性
// 这就是所谓的继承
Fn.__proto__ === Function.prototype; // true
Fn.__proto__.flag; // '123'
Fn.flag; // '123'

理解:

  1. 所有的原型对象都是由Object函数对象创建的
  2. fn这个实例通过fn.__proto__访问到Fn.prototype的属性
  3. Fn.prototype通过Fn.prototype.__proto__访问到Object.prototype的属性
  4. fn.__proto__通过fn.proto.__proto__访问到Object.prototype的属性
  5. 当把上例中Fn.prototype.flag = ‘456’;注释,此时访问fn.flag就会通过fn.proto.proto 或者 fn.proto 或者 fn都可以访问到Object.prototype上的flag属性,值为’789’

发现1(我自己的理解):

  • Fn是一个函数对象
  • 作为对象时,继承的是来自Function.prototype属性
  • 作为函数时,本身有prototype属性,继承给构造出来的新对象

发现2:

  • fn为Fn函数构造出来的对象
  • fn首先会继承来自Fn构造函数的原型对象上的属性,即:Fn.prototype
  • Fn.prototype会继承来自Object构造函数的原型对象上的属性,即:Object.prototype
  • 所以fn会继承Fn.prototype 以及 Object.prototype
  • 继承方式:
 fn.__proto__    对应 Fn.prototype  
 fn.__proto__.__proto__    对应 Object.prototype
  • 而Function的原型只会被函数对象继承,不会被对象继承

此例中只有Fn是一个函数对象,所以Fn会继承Function.prototype的属性flag ‘123’
而fn为一个对象,不是函数对象,不会继承Function.prototype的属性

Object.prototype呢?:

 Object.prototype对象也是被构造的。
 Object.prototype.__proto__  === null; // true
 fn.__proto__.__proto__.__proto__ === null; // true

例六(数组):

var arr = [];
// 数组继承于Array函数对象
arr.__proto__ === Array.prototype; // true
// Array原型对象继承于Object函数对象
Array.prototype.__proto__ === Object.prototype; // true
arr.__proto__.__proto__ === Object.prototype; // true
Object.prototype.__proto__  === null; // true
arr.__proto__.__proto__ .__proto_ === null;; // true

Array函数对象继承于Function函数对象
Fucntion.prototype.flag = '666';
Array.__proto__.flag; // '666'
Array.flag; // '666'

例七(对象):

var obj = {
   
   };
// 对象继承于Object函数对象
obj.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
obj.__proto__ .__proto__ === null; // true

// Object函数对象由Function函数创建的
Object.__proto__ === Function.prototype; //true
Function.prototype.flag; // '666'
Object.__proto__.flag; // '666'
Object.flag; // '666'

总结:

  • 所有的函数包括Object函数对象到最后的顶层都继承到了Function.prototype;
 Object.__proto__ === Function.prototype;
  • 所有的原型对象包括Function的原型对象到最后的顶层都继承到了Object.prototype;
Function.prototype.__proto__  === Object.prototype;

先写到这里…
下次再更…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章