JS原型链
构造函数、实例对象
person是Person函数的实例对象
Person是函数person的构造函数
function Person()
let person = new Person()
person // Person {__proto__} 是一个对象,有一个__proto__属性
person.constructor === Person // true 指向Person的构造函数本身
实例对象的 constructor
属性指向构造函数本身
其实这么说不准确。后面会说明其实是 person 实例对象通过 __proto__
属性指向了构造函数的原型对象 prototype
,prototype
的 constructor
属性才指向了构造函数 Person 本身。所以打印结果上来看 person 上本身是没有 constructor 的,但是却能打印 person.constructor)
JS内置对象Object、Function
- Function 函数和 Object 函数是 JS 内置对象,也叫内部类。这些内置函数可以当作构造函数来使用。
- 为什么 “函数即对象”?关于 Function 对象的特殊性。
functino Person(){}
Person.construcotr === Function // true 构造函数的constructor属性指向内置函数Function
- 所有函数都是 Function 函数的实例对象,所以常说函数即对象,JS 中一切皆对象。
Function.constructor === Function // true
Object.constructor === Functino // true
- 说明Function函数也是是自己的构造函数
- 说明Function函数也是Object这类内置对象的构造函数
let obj = {}
obj.constructor === Object // true 普通对象的constructor 属性都是指向Object构造函数
整理一下 constructor 指向链
person.constructor -> Person构造函数,Person.constructor -> Function 函数, Function.constructor -> Function自己
obj.constructor -> Object,Object.constructor -> Function函数,Function.construtor -> Function自己
原型对象prototype
- 为了让实例对象能够共享一些属性和方法,将他们全部放在了 构造函数的原型对象
prototype
上。
Person.prototype.sayHello = function () {
console.log("Hello!");
};
let person1 = new Person();
let person2 = new Person();
person1.sayHello === person2.sayHello; // true
- 关于 Function 的特殊性:所有函数本身是 Function 函数的实例对象,所以 Function 函数中同样会有一个
prototype
对象放它自己实例对象的共享属性和方法。 - constructor 也是一个共享属性,存放在原型对象
prototype
中,作用依然是指向自己的构造函数。
function Person(){}
Person.prototype.constructor === Person // true
从示意图中看出实例对象
、构造函数
、原型对象 prototype
和 constructor
的存在关系。
实例对象的 constructor
属性指向该实例对象的构造函数,constructor
属性作为共享属性存在于构造函数的prototype
上。
__proto__
属性
原型链的核心公式: person.__proto__ === Person.prototype
即实例对象的 __proto__
属性指向构造函数的原型对象 prototype
。
- 通过在 persion 对象内部创建了一个属性
__proto__
直接指向自己的原型对象 prototype,通过原型对象就可以找到共享属性constructor
,此时 constructor 指向该实例对象的构造函数 Person 了。
function Person() {}
let person = new Person();
person.__proto__ == Person.prototype;
// constructor存在于prototype上,指向构造函数
Person.prototype.constructor == Person;
// 所以person.__proto__可以读取原型prototype上的constructor,指向构造函数
person.__proto__.constructor == Person;
person.constructor === Person; // true 也成立?
// 这就是因为原型链查找了,后面会详细说。因为person通过原型链查找到了constructor,并且指向了Person构造函数。
// 注意
Person.constructor == Function; // 因为Person的构造函数是Function
关于 Function 的特殊性:
-
因为 JS 内
所有函数
都是 Function 函数的实例对象,所以 Person 函数也有个__proto__ 属性指向自己的原型对象,即 Function 函数的prototype
。 -
Function 函数也有个
__proto__
属性指向自己,因为它拿自身作为自己的构造函数
由于原型对象 prototype 也是个对象,那它也有个__proto__
属性,指向自己的原型对象。那它的构造函数是谁呢
Person.prototype.__proto__ === Object.prototype
所以函数内的原型对象 prototype
跟所有普通对象一样,也都是内置Object
函数的实例对象。
总结: 实例对象的__proto__
属性指向构造函数的原型对象 prototype
; 而所有原型对象 prototype 的 __proto__
属性,都指向了Object
的原型对象!
什么是原型链
-
每个
实例对象
都有一个 私有属性__proto__
指向它的 构造函数 的 原型对象prototype
。该原型对象也有自己的私有属性__proto__
指向原型对象…层层向上直到一个对象(内置Object对象)的原型对象为null
,并作为这个原型链中的最后
一个环节。` -
将一个个
实例对象
和原型对象
关联在一起,关联的原型对象也是别人的实例对象,所以就形成了串连的形式,也就形成了我们所说的原型链
。 -
Object 函数
是所有对象通过原型链追溯到最根的构造函数
。即 “最后一个 prototype 对象”便是 Object 函数内的 prototype 对象了。 -
Object 函数内的 prototype 对象的
__proto__
指向 null。
为什么 Object 函数不能像 Function 函数一样让__proto__
属性指向自己的 prototype?答案就是如果指向自己的 prototype,那当找不到某一属性时沿着原型链寻找的时候就会进入死循环,所以必须指向 null,这个null
其实就是个跳出条件。
找出图中的原型链
-
第一条:
person.__proto__ -> Person.prototype -> Person.prototype.__proto__ -> Object.prototype -> Object.prototype.__proto__ -> null
-
第二条:
Person.__proto__ -> Function.prototype -> Functino.prototype.__proto__ -> Object.prototype -> Object.prototype.__proto__ -> null
-
第三条:
Function.__proto__ -> Function.prototype -> Function.prototype.__proto -> Object.prototype -> Object.prototype.__proto__ -> null
-
第四条:
Object.__proto__ -> Function.prototype -> Function.prototype.__proto__ -> Object.prototype -> Object.prototype.__proto__ -> null.
实例对象上都有什么
function P(){}
let p = new P();
// ↓↓↓
> P {}
> __proto__: Object // 实例对象p上有 __proto__ 属性,指向构造函数的原型对象prototype
> constructor: ƒ P() // prototype 上有公共方法 constructor
> __proto__: // prototype 上还有 __proto__ 属性,指向内置Object的原型对象
> constructor: ƒ Object() // 内置Object的原型对象上的constructor 以及hasOwnProperty等公共方法
> hasOwnProperty: ƒ hasOwnProperty()
...
构造函数的属性和方法放在this
上和放在prototype
上有什么区别
Case1:
function Person() {
this.age = 0;
this.list = [];
}
let p1 = new Person();
p1.list.push(1);
p1.list; // [1]
let p2 = new Person();
p2.list.push(2);
p2.list; // [2]
Case2:
function Person() {
this.age = 0;
}
Person.prototype.list = [];
let p1 = new Person();
p1.list.push(1);
p1.list; // [1]
let p2 = new Person();
p2.list.push(2);
p2.list; // [1,2]
总结: this 定义的属性和方法是生成的实例自己独有的属性和方法
;而定义在 prototype 上的属性和方法
,是每个实例所共有
的。那么定义在 prototype 上的属性和方法发生改变则每个实例对象
都会拿到。
总结:函数/对象上都有什么?
- 对象都有
__proto__属性
- 函数也有
__proto__属性
,还有原型对象prototype
- 原型对象 prototype上也有
__proto__
属性、还有 constructor 属性、以及一些共享属性和方法(挂在 prototype 上) __proto__
是浏览器实现的查看原型方案,也写成 [[prototype]]。__proto__
属性的作用就是,当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__
属性所指向的那个对象(父对象)里找,一直找,直到__proto__
属性的终点 null,再往上找就相当于在 null 上取值,会报错。通过__proto__
属性将对象连接起来的这条链路即我们所谓的原型链。constructor
属性其实就是一个拿来保存自己构造函数引用的属性。实例对象的constructor
属性指向构造函数
本身。原型对象 prototype
的作用就是,让该函数所实例化的对象们都可以找到公用的属性和方法