Photo by Mohammad Rahmani on Unsplash
Prototype 原型链的原理
JavaScript 并不像 Java、C++ 这些知名的物件导向语言具有「类别」(class)来区分概念与实体(instance)或天生具有继承的能力,而只有「物件」,因此只能利用设计模式来模拟这些功能
当初设计语言并没有 Class,所以只能用建构函式 function( 可以当作 constructor )
,所以就可以知道 prototype 的出现是为了达到所有 instance 都能共享 property & method。
function Person(name) {
this.name = name;
this.sayHi = function() {
// new 出几份 instance 就会有几份,佔用记忆体空间
console.log('Object', this.name, 'Hi');
}
}
Person.prototype.sayHi = function(){
// 用 prototype 实现共享 property & method
console.log('Object', this.name, 'Hi');
}
若没有写 prototype 的话,只要 Function 或物件被 new 出多少 instance 就会需要多少空间
而共同 prototype 的函式是共用同个记忆体
,所以 比较节省记忆体空间
当建立一个 Instance 时,JavaScript 会自动加上 __proto__
属性,告诉程式如果在原本的 Instance 找不到方法的时候,要去 __proto__
找
建构子
只要函式前有 new,这个函式就是建构子,只要函式前有 new 来做呼叫,就叫做建构子呼叫。
new 关键字做了哪些事情
- 建立一个新的物件。
设定原型
// 动物
let animal = {
// 会吃
eats: true
};
// 兔子
let rabbit = {
// 会跳
jumps: true
};
// 设定兔子原型是动物
rabbit.__proto__ = animal;
// 兔子会吃:true
console.log( rabbit.eats );
// 兔子会跳:true
console.log( rabbit.jumps );
// 动物不一定会跳:undefined
console.log( animal.jumps );
// 兔子会跳:true
console.log( 'eats' in rabbit );
// 动物不一定会跳:false
console.log( 'jumps' in animal );
除了直接在 __proto__
设定物件原型,也可以用使用物件方法设定原型
// 设定兔子原型是动物
rabbit.__proto__ = animal;
// 使用物件方法设定原型
Object.setPrototypeOf(rabbit, animal);
设定物件原型
Object.setPrototypeOf(obj, prototype)
加入新的原型方法
// 动物
let animal = {
// 会吃
eats: true,
walk : () => {
console.log(`animal walking`);
}
};
// 兔子
let rabbit = {
// 会跳
jumps: true
};
// 设定兔子原型是动物
Object.setPrototypeOf(rabbit, animal)
// 兔子会走:animal walking
rabbit.walk();
加入新的物件继承动物原型
// 动物
let animal = {
// 会吃
eats: true,
walk : () => {
console.log(`animal walking`);
}
};
// 兔子
let rabbit = {
// 会跳
jumps: true
};
// 鸟
let bird = {
// 会飞
fly: true
}
// 设定兔子原型是动物
Object.setPrototypeOf(rabbit, animal)
// 设定鸟原型是动物
Object.setPrototypeOf(bird, animal)
// 兔子会走:animal walking
rabbit.walk();
// 鸟会走:animal walking
bird.walk();
// 兔子与鸟的原型都是一样的 true
console.log(rabbit.__proto__ === bird.__proto__);
取得物件本身自己的键值 Object.keys
// 动物
let animal = {
// 会吃
eats: true,
walk : () => {
console.log(`animal walking`);
}
};
// 兔子
let rabbit = {
// 会跳
jumps: true
};
// 鸟
let bird = {
// 会飞
fly: true
}
// 设定兔子原型是动物
Object.setPrototypeOf(rabbit, animal);
// 设定鸟原型是动物
Object.setPrototypeOf(bird, animal);
// [ 'jumps' ]
console.log(Object.keys(rabbit));
// [ 'fly' ]
console.log(Object.keys(bird));
取得物件所有键值,包含原型资料 for..in
// 动物
let animal = {
// 会吃
eats: true,
walk : () => {
console.log(`animal walking`);
}
};
// 兔子
let rabbit = {
// 会跳
jumps: true
};
// 鸟
let bird = {
// 会飞
fly: true
}
// 设定兔子原型是动物
Object.setPrototypeOf(rabbit, animal);
// 设定鸟原型是动物
Object.setPrototypeOf(bird, animal);
// 取得兔子所有键值资料
// [rabbit] jumps
// [rabbit] eats
// [rabbit] walk
for(let prop in rabbit) {
console.log(`[rabbit] ${prop}`);
}
// 取得鸟所有键值资料
// [bird] fly
// [bird] eats
// [bird] walk
for(let prop in bird) {
console.log(`[bird] ${prop}`);
}
判断是否是物件自己的属性 hasOwnProperty
// 动物
let animal = {
// 会吃
eats: true,
walk : () => {
console.log(`animal walking`);
}
};
// 兔子
let rabbit = {
// 会跳
jumps: true
};
// 鸟
let bird = {
// 会飞
fly: true
}
// 设定兔子原型是动物
Object.setPrototypeOf(rabbit, animal);
// 设定鸟原型是动物
Object.setPrototypeOf(bird, animal);
// 取得兔子所有键值资料
// [rabbit] Our: jumps
// [rabbit] Inherited: eats
// [rabbit] Inherited: walk
for(let prop in rabbit) {
let isOwn = rabbit.hasOwnProperty(prop);
if (isOwn) {
console.log(`[rabbit] Our: ${prop}`);
} else {
console.log(`[rabbit] Inherited: ${prop}`);
}
}
// 取得鸟所有键值资料
// [bird] Our: fly
// [bird] Inherited: eats
// [bird] Inherited: walk
for(let prop in bird) {
let isOwn = bird.hasOwnProperty(prop);
if (isOwn) {
console.log(`[bird] Our: ${prop}`);
} else {
console.log(`[bird] Inherited: ${prop}`);
}
}
删除 prototype
let animal = {
jumps: null
};
let rabbit = {
__proto__: animal,
jumps: true
};
// true
console.log( rabbit.jumps );
// 删除兔子 jumps
delete rabbit.jumps;
// 显示动物 jump:null
console.log( rabbit.jumps );
// 删除动物 jumps
delete animal.jumps;
// undefined
console.log( rabbit.jumps );
prototype chain 原型链
prototype 可以持续不断地继承
建构函式有预设最底层的原型物件
// 兔子
let rabbit = {
// 会跳
jumps: true
};
// [Object: null prototype] {}
console.log(rabbit.__proto__);
修改 prototype 后,可以用 prototype chain 找到更底层的原型物件
// 动物
let animal = {
// 会吃
eats: true,
walk : () => {
console.log(`animal walking`);
}
};
// 兔子
let rabbit = {
// 会跳
jumps: true
};
Object.setPrototypeOf(rabbit, animal);
// { eats: true, walk: [Function: walk] }
console.log(rabbit.__proto__);
// [Object: null prototype] {}
console.log(rabbit.__proto__.__proto__);
// null
console.log(rabbit.__proto__.__proto__.__proto__);
JavaScript 就是透过 prototype chain 原型链
,去达到继承的目的
方法取得顺序
// 动物
let animal = {
// 会吃
eats: true,
walk : () => {
console.log(`animal walking`);
}
};
// 兔子
let rabbit = {
// 会跳
jumps: true
};
Object.setPrototypeOf(rabbit, animal)
// animal walking
rabbit.walk();
// [object Object]
console.log(rabbit.toString());
rabbit.walk()
顺序 | 寻找 | 所属原型 | 有没有找到 |
---|---|---|---|
1 | rabbit.walk() |
rabbit | X |
2 | rabbit.__proto__.walk() |
animal | V |
rabbit.toString()
顺序 | 寻找 | 所属原型 | 有没有找到 |
---|---|---|---|
1 | rabbit.toString() |
rabbit | X |
2 | rabbit.__proto__.toString() |
animal | X |
3 | rabbit.__proto__.__proto__.toString() |
Object | V |
__proto__
与 prototype
比较
function Animal(name) {
this.name = name;
this.walk = () => {
console.log(`[Animal] ${this.name} walking`);
}
}
// 设定动物函式的 Prototype
Animal.prototype.run = () => {
console.log(`[Animal Prototype] ${this.name} run`);
}
let MyAnimal = new Animal('Kitty');
// Animal { name: 'Kitty', walk: [Function (anonymous)] }
console.log(MyAnimal);
// [Animal] Kitty walking
MyAnimal.walk();
// [Animal Prototype] undefined run
MyAnimal.run();
// { run: [Function (anonymous)] }
console.log(MyAnimal.__proto__);
// [Object: null prototype] {}
console.log(MyAnimal.__proto__.__proto__);
// null
console.log(MyAnimal.__proto__.__proto__.__proto__);
在 ES 规范中,每个变数物件都有 __proto__
,只有 Function 函数
才有 prototype
属性
当建立 Function 函数
的时候,会自动加入 prototype
属性
所以使用 new FunctionName()
的方式建立物件时,在建立完 Instance 后,会继承 Function 函数
中所有的 prototype
属性和方法,将自己的 __proto__
指向 prototype
透过 prototype
储存要共享的属性和方法,也可以设定 prototype
去指向既有的物件
所以:
Function
本身就是函数,Function.__proto__
标准的建构目标是Function.prototype
Function.prototype.__proto__
标准的建构目标是Object.prototype
- 每个函式的
prototype
物件,会有一个constructor
属性,指回到这个函式。例如MyFunc.prototype
物件的constructor
属性,会指向MyFunc函式
- 每个物件都有一个
__proto__
内部属性,指向它的继承而来的原型prototype
物件
在 mollypages.org 可以看到这张图
Function.prototype
和Function.__proto__
都指向Function.prototype
Object.prototype
和Object.__proto__
都指向Object.prototype
Object.prototype.__proto__
=== null
结论
__proto__
是物件实际在使用的原型链,去使用prototype chain 原型链
解析方法prototype
是函式物件在使用new
建立物件时,用来建立__proto__
的
function Animal(name) {
this.name = name;
}
let myAnimal = new Animal('Kay');
// true
console.log(myAnimal.__proto__ === Animal.prototype);
// true
console.log(myAnimal.prototype === undefined);
prototype 原理
Object.prototype
是原型鍊的顶端物件
// true
console.log(Object instanceof Function);
// true
console.log(Function instanceof Object);
// true
console.log(Function.prototype.__proto__ == Object.prototype);
// false
console.log(Function.prototype == Object.prototype);
Function.prototype
和 Function.__proto__
为同一对象
// true
console.log(Function.prototype === Function.__proto__);
// true
console.log(Function.prototype === Object.__proto__);
// true
console.log(Function.prototype === Array.__proto__);
Object/Array/String
等等建构函式本质上和 Function
一样,都是继承 Function.prototype
Function.prototype
直接继承 root(Object.prototype)
// true
console.log(Object instanceof Function);
// true
console.log(Function instanceof Object);
// true
console.log(Function instanceof Function);
// true
console.log(Array instanceof Function);
// true
console.log(Array instanceof Object);
// false
console.log(Function instanceof Array);
// false
console.log(Object instanceof Array);
Object
和 Function
的鸡和蛋的问题
导致 Function instanceof Object
和 Object instanceof Function
都为 true
的原因
1. Function.prototype
不同于一般函式的函式
The Function prototype object is itself a Function object (its [[Class]] is “Function”) that, when invoked, accepts any arguments and returns undefined.
The value of the [[Prototype]] internal property of the Function prototype object is the standard built-in Object prototype object (15.2.4). The initial value of the [[Extensible]] internal property of the Function prototype object is true.
The Function prototype object does not have a valueOf property of its own; however, it inherits the valueOf property from the Object prototype Object.
Function.prototype
像一般函式
一样可以呼叫调用,接受任何的参数,且也可回传undefined
一般函式
是Function
的 Instance 实例,表示一般函式
继承Function.prototype
,CustomFunction.__proto__ == Function.prototype
Function.prototype
继承Object.prototype
Function.prototype.prototype
是null
所以 Function.prototype
是另类的函数,可以独立优先于 Function
产生
// true
console.log(Object instanceof Function);
// true
console.log(Function instanceof Object);
// {}
console.log(Function.__proto__);
// [Object: null prototype] {}
console.log(Function.__proto__.__proto__);
// null
console.log(Function.__proto__.__proto__.__proto__);
// {}
console.log(Function.prototype);
// [Object: null prototype] {}
console.log(Function.prototype.__proto__);
// null
console.log(Function.prototype.__proto__.__proto__);
// undefined
console.log(Function.prototype.prototype);
2. Object
本身是建构函式,是 Function
的实例
The value of the [[Prototype]] internal property of the Object constructor is the standard built-in Function prototype object.
The value of the [[Prototype]] internal property of the Object prototype object is null, the value of the [[Class]] internal property is “Object”, and the initial value of the [[Extensible]] internal property is true.
Object.__proto__
就是 Function.prototype
// true
console.log(Object instanceof Function);
// {}
console.log(Object.__proto__);
// [Object: null prototype] {}
console.log(Object.__proto__.__proto__);
// null
console.log(Object.__proto__.__proto__.__proto__);
// [Object: null prototype] {}
console.log(Object.prototype);
// null
console.log(Object.prototype.__proto__);
// {}
console.log(Function.prototype);
// {}
console.log(Function.__proto__);
// [Object: null prototype] {}
console.log(Function.__proto__.__proto__);
// true
console.log(Object.__proto__ == Function.prototype);
// true
console.log(Object.__proto__ == Function.__proto__);
// true
console.log(Object.__proto__.__proto__ == Function.__proto__.__proto__);
所以 Function 与 Object 关係是
- 先有
Object.prototype
(原型链顶端) Function.prototype
继承Object.prototype
而产生- 最后,
Function
和Object
和其它建构函数继承Function.prototype
而产生
Object prototype 研究
Object.prototype
不是 Object
的实例
// 取得物件 prototype
const root = Object.prototype;
// [Object: null prototype] {}
console.log(root);
// null : 取得 root prototype
console.log(Reflect.getPrototypeOf(root));
// {} : 取得 Object prototype
console.log(Reflect.getPrototypeOf(Object));
// object : 取得 root 类型
console.log(typeof root);
// false : root 是否为 Object 实例
console.log(root instanceof Object);
Object.prototype
是对象,但不是透过 Object
函数建立的
// Object(0) []
console.log(Array.prototype);
// Object [Map] {}
console.log(Map.prototype);
// [Object: null prototype] {}
console.log(Object.prototype);
// {}
console.log(Function.prototype);
Array.prototype
是阵列格式的Object
Map.prototype
是Map
格式的Object
Function.prototype
反过来继承{} (Object)
const funcPrototype = Function.prototype;
const func_prototype_ = Function.__proto__;
function test() {};
// true
console.log(test.__proto__ === funcPrototype);
// true
console.log(Array.__proto__ === funcPrototype);
// true
console.log(func_prototype_ === funcPrototype);
Function
本身也是function
Function.prototype
是所有function
的原型(包括Function
自己的原型)- 但反过来,
Function.prototype
和Function
没有反向关係(正向Function
继承Function.prototype
)
所以 Function.prototype
和 Function.__proto__
相同,不代表 Function
这个函数是由自己本身去建立的
是先有 Function.prototype
这个对象(也是函数),然后才有其他函数
Array.prototype 是阵列
// Object(0) []
console.log(Array.prototype);
// false
console.log(Array.prototype instanceof Array);
const arrayPrototype = Array.prototype;
arrayPrototype.push('Kay');
// Object(1) [ 'Kay' ]
console.log(arrayPrototype);
// true
console.log(Array.isArray(arrayPrototype));
ES6 使用类别实现继承
class Animal {
constructor (name){
this.name = name
}
walk = () => {
console.log(`[Animal] ${this.name} walking`);
}
}
class Rabbit extends Animal {
constructor (name){
super(name)
}
jump = () => {
console.log(`[Rabbit] ${this.name} jump`);
}
}
let myRabbit = new Rabbit('Kay');
// [Animal] Kay walking
myRabbit.walk();
// [Rabbit] Kay jump
myRabbit.jump();
// Rabbit { walk: [Function: walk], name: 'Kay', jump: [Function: jump] }
console.log(myRabbit);
// Animal {}
console.log(myRabbit.__proto__);
// {}
console.log(myRabbit.__proto__.__proto__);
// [Object: null prototype] {}
console.log(myRabbit.__proto__.__proto__.__proto__);
// null
console.log(myRabbit.__proto__.__proto__.__proto__.__proto__);
常见问题
为什麽不直接用 __proto__
取得原型物件?
__proto__
虽然被几乎所有的浏览器支援,但它仍是非标准属性;透过 Object.getPrototypeOf
取得物件的原型是比较正确的方法。
什麽是 __proto__
由 ES6 开始成为 Object 的原生属性,直接对 [[Prototype]]
进行读写。
什麽是 prototype
一个 Object
,当 new 一个 instance 时,会被用作指向 __proto__
作为 instance 继承的属性
prototype
只存在于 constructor functions,在 instance 上不存在。
相反的,__proto__
则出现在所有物件。
参考资料
- Prototypal inheritance
- 重新认识 JavaScript: Day 24 物件与原型链 - iT 邦帮忙::一起帮忙解决难题,拯救 IT 人的一天
- Object.setPrototypeOf() - JavaScript | MDN
- [第十七週] JavaScript 进阶:为什麽要有原形鍊 Prototype Chain? | Yakim shu
- 从__proto__和prototype来深入理解JS对象和原型链 · Issue #9 · creeperyang/blog · GitHub
- Javascript Object Hierarchy
- 你懂 JavaScript 吗?#19 原型(Prototype) | Summer。桑莫。夏天
- 15. [JS] 什麽是原型链? - iT 邦帮忙::一起帮忙解决难题,拯救 IT 人的一天
- JavaScript Prototype
- 原型基础物件导向 · 从ES6开始的JavaScript学习生活
- proto VS. prototype in JavaScript - Stack Overflow
Donate KJ 贊助作者喝咖啡
如果這篇文章對你有幫助的話,可以透過下面支付方式贊助作者喝咖啡,如果有什麼建議或想說的話可以贊助並留言給我
If this article has been helpful to you, you can support the author by treating them to a coffee through the payment options below. If you have any suggestions or comments, feel free to sponsor and leave a message for me!
方式 Method | 贊助 Donate |
PayPal | https://paypal.me/kejyun |
綠界 ECPay | https://p.ecpay.com.tw/AC218F1 |
歐付寶 OPay | https://payment.opay.tw/Broadcaster/Donate/BD2BD896029F2155041C8C8FAED3A6F8 |