JavaScript 改变原型指向及原型链关系

引出

请先看一段简单的代码,后面根据此代码扩展引出 是否可以改变原型指向问题

// 构造函数(Person)
function Person(name,age){
    this.name = name
    this.age = age
}

// 构造函数(Animal)
function Animal(name,age){
    this.name = name
    this.age = age
}

// 将 Person 的方法挂到原型对象
Person.prototype.test = function(){
    console.log('Hi,Person')
}

// 将 Animal 的方法挂到原型对象
Animal.prototype.print = function(){
    console.log('Hi,Animal')
}

// 实例化(Person)
var per = new Person('小王', 20)

// 实例化(Animal)
var ani = new Animal('羊驼', 2)

我们来画图解释以上运行代码后它们的关系,只有知道它们之间的关系才能继续往下走:
在这里插入图片描述
构造函数通过 prototype 属性指向原型对象,而原型对象则通过 constructor(构造器) 属性回指构造函数。通过 Person.prototype.X 往原型对象中挂方法,而实例对象则是直接指向构造函数,通过 __proto __ 指向原型对象,这也是为什么实例对象能访问到原型下的方法的原因。

根据图示与文字说明,请思考一个问题:

Person.prototype =

既然可以 “控制” 原型对象,那么可不可以将构造函数的原型属性(prototype)指向其他吗?可以!

请看以下代码,将构造函数的原型属性(prototype)指向一个字符串会发生什么

// 构造函数(Person)
function Person(name,age){
    this.name = name
    this.age = age
}

// 将 Person 的方法挂到原型对象
Person.prototype.test = function(){
    console.log('Hi,Person')
}

// 将构造函数的原型属性指向一个字符串(原本是Object)
Person.prototype = "hello"

// 实例化(Person)
var per = new Person('小王', 20)

console.log(per.name)//小王
console.log(per.age)//20
// 由于实例直接指向构造函数 且 name/age 属性挂在构造函数中 与原型对象无关 所以正常访问

per.test()//报错
// 为什么?因为此时原型对象已被覆盖(现在是'hello'而不是Object)
console.log(Person.prototype)//hello
// 由于实例直接指向构造函数 然后构造函数的 prototype 属性本该指向原型对象(现在指向字符串)

我要表达的意思是,可以手动改变原型对象,这座重要的桥梁决是关键。 但我们改变为字符串毫无意义,这无疑是拆掉原型链:
在这里插入图片描述

改变原型指向

既然我们知道可以改变原型指向,那么怎么改变能有意义呢?将指向改为其他实例对象,这样就能使用其他实例对象上原型的方法。

// 构造函数(Person)
function Person(name,age){
    this.name = name
    this.age = age
}

// 构造函数(Animal)
function Animal(name,age){
    this.name = name
    this.age = age
}

// 将 Person 的方法挂到原型对象
Person.prototype.test = function(){
    console.log('Hi,Person')
}

// 将 Animal 的方法挂到原型对象
Animal.prototype.print = function(){
    console.log('Hi,Animal')
}

// 实例化(Person)
var per = new Person('小王', 20)

// 实例化(Animal)
var ani = new Animal('羊驼', 2)

此时, 实例对象 per 无法访问 print 方法,这毫无疑问:

per.print() //per.print is not a function

原因很简单,per 与 ani 之间毫无关联,更别说原型对象上了。

那么重头戏来了,如何让 per 访问 print 方法?

改变实例对象 per 指向的构造函数的 prototype 指向,让其指向实例对象 ani 即可。

为什么要指向实例对象呢?因为实例对象是整个原型链过程的 起点

完成的代码如下:

// 构造函数(Person)
function Person(name,age){
    this.name = name
    this.age = age
}

// 构造函数(Animal)
function Animal(name,age){
    this.name = name
    this.age = age
}

// 将 Person 的方法挂到原型对象
Person.prototype.test = function(){
    console.log('Hi,Person')
}

// 将 Animal 的方法挂到原型对象
Animal.prototype.print = function(){
    console.log('Hi,Animal')
}

// 实例化(Animal)
var ani = new Animal('羊驼', 2)

// 更改指向并重新指向实例对象
Person.prototype = ani

// 实例化(Person)
var per = new Person('小王', 20)

// 测试
per.print()//Hi,Animal

// 但需要注意,此时 test 将无效
per.test()//Error

我画一个图,或许你就能明白了:

在这里插入图片描述
整个原型链的转折点就在于构造函数的 prototype 属性指向。


请再思考一个问题,如何同时访问 test() 与 print() ?

这看起来遥不可及,但是却依然可以实现,这是解释型语言的功劳,但我不认为这是好事。

// 构造函数(Person)
function Person(name,age){
    this.name = name
    this.age = age
}

// 构造函数(Animal)
function Animal(name,age){
    this.name = name
    this.age = age
}

// 将 Person 的方法挂到原型对象
Person.prototype.test = function(){
    console.log('Hi,Person')
}

// 更改指向并重新指向实例对象
Animal.prototype = new Person('小王', 20)

// 【重点】在改变指向后添加方法即可
Animal.prototype.print = function(){
    console.log('Hi,Animal')
}

// 实例化(Animal)
var ani = new Animal('羊驼', 2)

// 测试
ani.print()//Hi,Animal
ani.test()//Hi,Person

总结:如果改变了原型指向,你就应该在原型改变之后再添加原型方法,这样你可以访问原本方法与当前指向的原型方法,一切归功于解释型语言。

原型链

实例对象和原型对象之间的关系通过 __proto__(浏览器使用的原型,开发者使用 prototype 原型) 建立连接,整个关系指向形成了一个关系链,这个关系链就是原型链,它最总指向 null 。

发布了257 篇原创文章 · 获赞 403 · 访问量 81万+

猜你喜欢

转载自blog.csdn.net/weixin_44198965/article/details/104235513