一文带你深入理解 JavaScript 中的原型与原型链
先使用构造函数创建一个对象:
function Person() {}
let person = new Person()
person.name = 'Janny'
在上述例子中,我们使用构造函数 Person,通过 new 创建了一个实例对象 person。
原型 prototype 上的方法是给实例对象使用的。
每个函数(只有函数才会有)都有一个 prototype 属性,其指向一个对象,这个对象就是该构造函数创建的实例对象的原型(通过 p.__proto__ 指向,后续会详细讲解)。
function Person() {}
Person.prototype.name = 'Janny'
let person1 = new Person()
let person2 = new Person()
console.log(person1.name)
console.log(person2.name)
Person.prototype === person1.__proto__
Person.prototype === person2.__proto__
prototype 指向的通常是一个 Object 空对象,我们可以手动为其添加属性和方法,例如:
Person.prototype.name = 'Danny'
Person.prototype.doing = function() {}
构造函数的 prototype 属性和实例对象的原型的关系为:
prototype
构造函数 Person
实例对象person 的原型
每个 JavaScript 对象(除了 null)都具有 __proto__ 属性,这个属性会指向该对象的原型。
function Person() {}
let person = new Person()
person.__proto__ === Person.prototype
关系如下:
prototype
通过 new 创建了
_proto_
构造函数 Person
实例对象person 的原型
实例对象person
实例对象的原型上有一个 constructor 属性,它指向关联的构造函数本身。
function Person() {}
Person.prototype.constructor === Person
let person = new Person()
person.__proto__.constructor = Person
Object.getPrototypeOf(person) === Person.prototype
关系如下:
prototype
constructor
通过 new 创建了
_proto_
构造函数 Person
实例对象person 的原型
实例对象person
每个构造函数都有一个 prototype,即显式原型(属性) 每个实例对象都有一个 __proto__,可称为隐式原型(属性) 显式原型属性与隐式原型属性都是一个地址值,二者均指向了同一个对象,就是上面说的那个实例对象的原型。
function Person() {}
console.log(Person.prototype)
let person = new Person()
console.log(person.__proto__)
console.log(Person.prototype === person.__proto__)
原型属性都是 JS 引擎自动添加的
显式原型的创建过程:
- 当构造函数创建的时候会自动添加一个
prototype属性 - 在内存中创建一个空对象(实例对象的原型)
- 让
prototype属性指向这个(原型)空对象
隐式原型的创建过程:
- 当通过构造函数创建实例对象的时候自动添加了一个
__proto__属性 - 将
prototype中存的地址值赋值给__proto__,此时二者指向了同一个对象
prototype
_proto_
构造函数 Person
一个JS对象
该对象为实例的原型
实例对象person
function Person() {
this.doing = function() {
console.log('方法doing')
}
}
Person.prototype.todo = function() {
console.log('方法todo')
}
let person = new Person()
person.ownFun = function() {
console.log('自身方法ownFun')
}
person.ownFun()
person.doing()
person.todo()
person.toString()
person.done()
原型链访问:原型链是沿着隐式原型来查找的 原型链作用:查找对象的属性(方法)
访问一个对象属性的时候:
- 先在自身属性中查找,找到了就返回
- 如果没有找到,再沿着
__proto__隐式原型链向上查找,找到了就返回 - 如果最终没有找到,返回
undefined
_proto_
prototype
_proto_
_proto_
实例对象person
方法ownFun
实例对象的原型
方法todo 方法doing
Object构造函数
JS中所有对象的原型
Object相关的实例方法
原型链的尽头 null
所有对象都有一个 __proto__ 隐式原型属性,指向了构造函数 Function 的 prototype 显式原型属性,即==实例对象的隐式原型等于其构造函数的显式原型==。
由于 JavaScript 中一切皆对象,所以 Function 既是构造函数又是实例对象,也就是说,Function 也是通过 new Function() 创建而来的。
所有构造函数(构造函数也是对象)的 __proto__ 属性都是一样的,因为所有构造函数都是 Funciton 通过 new 创建而来的,所以都指向了 Function 的显式原型 prototype 属性。
function Person() {}
function Car() {}
Person.__proto__ === Car.__proto__
Person.__proto__ === Function.prototype
Car.__proto__ === Function.prototype
因为构造函数 Object 是 Function 通过 new Function() 创建而来的,所以 Object 相当于实例对象,Function 是该实例对象的构造函数。
所以:
Object.__proto__ === Function.prototype
这和原型链的尽头是不一样的
Object.prototype.__proto__ === null

构造函数的显示原型 prototype 指向的对象默认是空的 Object的实例对象,但是构造函数 Object 的显示原型 prototype 并不满足这一点,通过下述代码对比可以发现:普通构造函数的 prototype 属性指向的对象和 Object 的 prototype 属性指向的对象是不一样的。
function Person() {}
Person.prototype instanceof Object
null instanceof Object
Object.prototype instanceof Object
所有的函数都是 Function 的实例(包括 Function 自身)
Function instanceof Function
所有的函数都是 Function 的实例,而 Function 的实例的原型都是构造函数 Object 的实例。
Object instanceof Object
下面四条语句均是正确的,通过上面的原型链示意图就可以很轻松地理解了。
Function instanceof Function
Function instanceof Object
Object instanceof Object
Object instanceof Function
instanceof 是如何判断的?
- 表达式:
obj instanceof Fun 其中obj 是实例对象,Fun 是构造函数 - 如果
Fun构造函数的显式原型对象在obj 实例对象的隐式原型链上,返回true,否则返回false 即Fun只走到Fun.prototype就不走了,而obj要沿着隐式原型链obj.__proto__.__proto__......一直走下去,直到终点。
实例对象的隐式原型属性对象等于构造函数的显式原型属性对象。
Function 是通过 new 自身产生的实例。
function Person(){}
let person = new Person()
person instanceof Person
person instanceof Object
Object instanceof Function
Object instanceof Object
Function instanceof Function
Function instanceof Object
function Foo(){}
Object instanceof Foo

例题一
function A(){}
A.prototype.n = 1
let b = new A()
A.prototype = {
n:2,
m:3
}
let c = new A()
b.n
b.m
c.n
c.m
实例一旦创建,其原型就已经确定了,后续再修改原型不会影响到已经创建的实例。
因为当创建 b 之后,b.__proto__ 已经指向了 A.prototype 这个实例对象,即此时这个实例对象有两个指针指向着,分别是 b.__proto__ 和 A.prototype,后续进行了 A.prototype={n:2,m:3} 操作,让 A.prototype 指向了一个新的实例对象。但是这个操作并没有修改原来的实例对象,此时原来的实例对象还有一个指针指着呢,那就是 b.__proto__。所以实例对象 b 的原型还是原来的。 而新创建的实例对象 c ,其在创建前原型已经进行了修改,故创建 c 的时候,c.__proto__ 指向的是修改后的原型对象。
例题二
let Foo = function(){}
Object.prototype.a = function(){
console.log('obj_a()')
}
Function.prototype.b = function(){
console.log('fun_b()')
}
let f = new Foo()
f.a()
f.b()
Foo.a()
Foo.b()
如果您觉得这篇文章有帮助,请点个赞吧~
评论
请登录后发表评论
去登录