一文让你对js的原型与原型链不再害怕、迷惑
原型与原型链的详细剖析
写在最前: 希望各位看完这篇文章后,再也不用害怕JS原型链部分的知识! — by Fitz
一起努力,加油吧!
原型
原型分为两种显式原型prototype
和隐式原型__proto__
显式原型prototype
显式原型prototype
存在于函数中,是函数的一个属性,它默认指向一个Object空对象(原型对象)
注意: Object空对象(原型对象)只是内容为空, 并不是真正意义上的空对象Object.create(null), 还是能够通过原型链看到Object.prototype上的各种属性、方法,例如: toString()
console.log(Object.prototype)
console.log(typeof Object.prototype) // object
函数原型对象上的constructor属性对应的函数对象自身
console.log(Object.prototype.constructor === Object) // true
/*
例如:
我定义了一个叫Test的函数
Test这个函数拥有它自己的prototype原型对象
原型对象上的constructor属性对应的就是Test函数自己
*/
console.log(test.prototype.constructor === test) // true
实例都会自动拥有其函数(构造函数)原型对象上的方法、属性
function Person () {}
Person.prototype.sayHello = function () {
console.log('Hello')
}
var fitz = new Person() // fitz是Person构造函数的一个实例
fitz.sayHello() // 'Hello'
隐式原型__proto__
每个实例对象都拥有隐式原型属性__proto__
function Student () {
// 构造函数Student
}
let fitz = new Student() // fitz是Student的实例对象
console.log(fitz.__proto__) // {constructor: ƒ}
显式原型prototype与隐式原型__proto__的关系
- 构造函数的显式原型
prototype
默认指向一个空的(没有我们自己定义的属性、方法)Object对象 - 构造函数的每个实例上都有的隐式原型
__proto__
, 都指向着构造函数的显式原型prototype
实例对象的隐式原型属性就是其构造函数的显式原型属性
function Student () {
// 构造函数Student
}
let fitz = new Student() // fitz是Student的实例对象
console.log(fitz.__proto__ === Student.prototype) // true
关于原型对象创建整体的流程
function Person () {} // 函数创建的时候,JS引擎为Person函数自动添加prototype对象属性, 属性指向一个空的Object对象
let fitz = new Person() // 实例对象创建的时候, JS引擎自动添加__proto__对象属性, 同时将这个__proto__指向该实例对象的构造函数的prototype
/*
JS引擎自动做了的事:
1. Person.prototype = new Object()
2. per1.__proto__ = Person.prototype
*/
原型链(隐式原型链)
原型链指的是: 在访问一个对象中的属性时,会先在自身中寻找,如果没有找到就会沿着__proto__
向上寻找,如果找到就返回属性,没有就返回undefined
function Student () {
this.sayName = function () {
console.log('Fitz')
}
}
// 向Student的显示原型对象上添加sayAge()方法
Student.prototype.sayAge = function () {
console.log(21)
}
var a = new Student()
a.sayName() // 'Fitz'
a.sayAge() // 21
console.log(a.toString()) // [Object object]
探寻原型链的尽头
首先,理清从自定义实例对象
到Object构造函数的prototype
的关系
// Object构造函数是JS引擎定义、生成的
console.log(Object)
// 查看Object的显示原型对象
console.log(Object.prototype) // 能够看到toString()等方法
// 自定义一个Student构造函数
function Student () {}
const stu = new Student() // 创建一个Student实例对象
/*
因为原型链就是隐式原型链,本质上是沿着隐式原型属性__proto__
向上寻找属性、方法的一个过程
*/
// 所以我们通过stu实例对象探寻原型链的尽头
console.log(stu.__proto__) // 实例stu的隐式原型
// 实例对象的__proto__ 指向 它构造函数的prototype
console.log(stu.__proto__ === Student.prototype) // true
// 构造函数的prototype默认是一个空的Object实例对象
console.log(Student.prototype)
// 空的Object实例对象的构造函数一定是Object构造函数
console.log(Student.prototype.__proto__ === Object.prototype) //true
/*
到这里,暂时总结一下此时的原型链状态:
stu.__proto__ => Student.prototype => object.__proto__ => Object.prototype
*/
然后就是最关键的部分: 着重理清Object构造函数的prototype
往后部分的所有内容
// Object构造函数是JS引擎定义、生成的
console.log(Object)
// 查看Object的显示原型对象
console.log(Object.prototype) // 能够看到toString()等方法
// 最为关键的一步, 这一步直接揭示了原型链的尽头在哪
console.log(Object.prototype.__proto__) // null
由此我们就能知道,原型链的尽头就是: Object.prototype
// Object构造函数是JS引擎定义、生成的
console.log(Object)
// 查看Object的显示原型对象
console.log(Object.prototype) // 能够看到toString()等方法
// 自定义一个Student构造函数
function Student() { }
const stu = new Student() // 创建一个Student实例对象
/*
因为原型链就是隐式原型链,本质上是沿着隐式原型属性__proto__
向上寻找属性、方法的一个过程
*/
// 所以我们通过stu实例对象探寻原型链的尽头
console.log(stu.__proto__) // 实例stu的隐式原型
// 实例对象的__proto__ 指向 它构造函数的prototype
console.log(stu.__proto__ === Student.prototype) // true
// 构造函数的prototype默认是一个空的Object实例对象
console.log(Student.prototype)
// 空的Object实例对象的构造函数一定是Object构造函数
console.log(Student.prototype.__proto__ === Object.prototype) //true
/*
到这里,暂时总结一下此时的原型链状态:
stu.__proto__ => Student.prototype => object.__proto__ => Object.prototype
*/
// 最为关键的一步, 这一步直接揭示了原型链的尽头在哪
console.log(Object.prototype.__proto__) // null
/*
到这里,我们就能总结出原型链的尽头就是Object.prototype的结论:
stu.__proto__ => Student.prototype => object.__proto__ => Object.prototype => null
*/
完整详尽的分析原型链
基于这一张图,我们就能够比较全面的掌握JavaScript中原型链的概念,在分析前,小伙伴们可以看这张图先自己思考一遍
接下来是全面总结、分析原型链知识的部分
/*
根据上面这张图由易入难,完整分析原型链
*/
// ===============第一部分: 自定义的构造函数及其实例============
function Foo () {} // 1. 构造函数Foo
var f1 = new Foo() // 2. 实例对象f1
// 3. 实例对象的隐式原型 指向 其构造函数的显示原型
console.log(f1.__proto__ === Foo.prototype) // true
// 4. 构造函数的显式原型是一个空的object对象
// 5. 这个空的object对象是Object构造函数的实例
console.log(Foo.prototype.__proto__ === Object.prototype) // true
// 6. 自定义构造函数是 Function构造函数的实例
// 换句话说: Foo这个构造函数,是new Function()出来的
console.log(Foo.__proto__ === Function.prototype) // true
// ===============第一部分: 自定义的构造函数及其实例============
// =============第二部分: Object构造函数及原型链的尽头============
console.log(Object) // 1. ƒ Object() { [native code] }
// 2. 实例对象o1、o2
var o1 = new Object()
var o2 = {}
// 3. Object构造函数也是Function构造函数的实例
// 换句话说: Object这个构造函数,也是new Function()出来的
console.log(Object.__proto__ === Function.prototype) // ture
// 4. Object构造函数的显式原型(Object.prototype)就是原型链的尽头
console.log(Object.prototype.__proto__) // 5. null
// =============第二部分: Object构造函数及原型链的尽头============
// =================第三部分: 特殊Function构造函数================
console.log(Function) // 1. ƒ Function() { [native code] }
// 2. Function构造函数的原型对象跟其他普通的构造函数一样 隐式原型指向空object对象
console.log(Function.prototype.__proto__ === Object.prototype) // true
// 3. 重点特殊的地方: Function构造函数是自己的实例
// 换句话说: Function构造函数,是new Function()自己出来的, 即我生出我自己
console.log(Function.__proto__ === Function.prototype) // true
// 4. Function.prototype是一个函数,而不是像其他函数一样是一个空的Object对象
console.log(typeof Function.prototype) // function
// =================第三部分: 特殊Function构造函数================
这张图配合上面的代码
关于原型链的补充总结
所有函数都是 Function构造函数 的实例对象,包括Function构造函数自己
标题换句话表达, 所有函数的__proto__
都指向Function.prototype
Function.__proto__
指向Function.prototype
// 所有函数都是 Function构造函数 的实例对象
/* 换句话说: 无论是
普通函数、方法
自定义构造函数
Object等一些JS引擎内置的构造函数
Function构造函数本身(我生我自己)
都是Function构造函数的实例对象
*/
const sayHello = function () {console.log('hello')} // 自定义函数
const Student = function (name) { // 自定义构造函数
this.name = name
}
console.log(sayHello.__proto__=== Function.prototype)
console.log(Student.__proto__=== Function.prototype)
console.log(Object.__proto__=== Function.prototype)
console.log(Date.__proto__=== Function.prototype)
// 最为特殊的Function(我生我自己)
console.log(Function.__proto__=== Function.prototype)
Function.prototype是一个函数对象
console.log(typeof Function.prototype) // function
console.dir(Function.prototype)
所有函数的显式原型prototype,都指向Object.prototype,Object构造函数的显式原型除外
console.log(Function.prototype instanceof Object) // true
console.log(Function.prototype.__proto__ === Object.prototype) // true
console.log(Date.prototype instanceof Object) // true
console.log(Date.prototype.__proto__ === Object.prototype) // true
// object构造函数的原型对象除外的理由, Object.prototype是原型链的尽头
console.log(Object.prototype instanceof Object) // false
console.log(Object.prototype.__proto__ === Object.prototype) // false
console.log(Object.prototype.__proto__) // null
原型链的应用
读取实例对象的属性值时,会先在自身中寻找,如果自身没有会到原型链中找
function Student () {}
Student.prototype.person = 'Fitz'
var f = new Student()
// person属性是原型对象上的,而不是实例本身的
console.log(f.person) // 'Fitz'
对实例的属性进行操作时,不会影响(查找)原型链,如果实例中没有当前属性,会自动添加
function Student () {}
Student.prototype.person = 'Fitz'
var f = new Student()
// 如果实例中没有当前属性,会自动添加
f.person = 'Lx'
f.age = 21
/*
属性只会在实例上,与原型链无关
可以运用前面的 引用数据类型的知识理解
*/
利用原型链,将实例的方法添加在原型对象上,实例的属性添加在实例自身
好处: 避免了每次实例化对象时,都创建出一模一样的方法,节省内存
function Person (name, age){
this.name = name
this.age = age
}
// 实例的方法统一放在构造函数的原型对象上
// 这样实例在调用方法时,可以通过原型链顺利找到该方法
Person.prototype.printInfo = function () {
console.log(`name: ${this.name}`)
console.log(`age: ${this.age}`)
}
var fitz = new Person('fitz', 21)
fitz.printInfo()
原型链继承
尝试使用原型链来模拟类的继承
实现的关键是: 子类的原型是父类的实例
思路来源于: 既然所有的实例对象都能调用toString()
方法那就看看为什么,
- toString()方法在Object.prototype显式原型对象上
- 实例对象的隐式原型__proto__ 指向其 构造函数的显式原型prototype
- 而关键就是,构造函数的显式原型是Object.prototype的实例对象
// 模拟父类
function Father() {
_Fathername = '我是父类'
this.name = 'Father'
}
Father.prototype.getFathername = function () {
console.log(_Fathername)
}
Father.prototype.getName = function () {
console.log(this.name)
}
// 模拟子类
function Son() {
_SonName = '我是子类'
this.name = 'Son'
}
// 实现子类继承父类
Son.prototype = new Father()
Son.prototype.getSonName = function () {
console.log(_SonName)
}
// 需要实现的目标
var son = new Son()
// 能在子类使用父类的方法
son.getFathername() // '我是父类'
son.getName() // 'Son'
console.log(son)