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.__proto__ === Function.prototype
这就是说顶层函数Function是由自己构造的
代码结果:
所以
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'
通过上述代码就会帮助理解:
- 对象被谁构造,那么对象就会继承谁原型上的属性
此例中:
- fn由Fn构造,所以fn继承Fn原型上的flag属性,值为456
- 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'
理解:
- 所有的原型对象都是由Object函数对象创建的
- fn这个实例通过fn.__proto__访问到Fn.prototype的属性
- Fn.prototype通过Fn.prototype.__proto__访问到Object.prototype的属性
- fn.__proto__通过fn.proto.__proto__访问到Object.prototype的属性
- 当把上例中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;
先写到这里…
下次再更…