【JavaScript】原型链、继承、静态方法、实例方法、原型方法

1. 开局一张图


原型链

2. 为什么要用原型链?解决了什么问题?
简单来讲,就是为了实现继承,简化代码。

function Person(name, age) {
  this.name = name
  this.age = age
  this.sayHi = function () {
    console.log('hello')
  }
}
// 创造实例对象
var p1 = new Person('jack', 20)
var p2 = new Person('rose', 18)
// 调用方法
p1.sayHi()
p2.sayHi()
如上代码,创造了两个实例p1和p2,其中的数据name和age是不相同的,理应单独存放,这很河狸。但,Person类中的方法sayHi()是p1和p2共用的,也就是说没必要让p1和p2都保存sayHi()的完整代码。现在要做的就是将公用的方法提取出来,供实例调用。

提取到哪里?怎么去调用?

如上图所示,我们将公用的方法提取到Person原型对象中,每个实例都有一个属性__proto__指向Person原型,这样一来,当下层实例p1、p2需要调用sayHi()方法时,就能够通过__proto__找到上层Person原型对象,从而调用sayHi()方法。并且不管有多少实例,始终只会有一个Person原型对象,一个sayHi()方法。

公用方法已经存放到原型对象中去了,如何让原型对象和构造方法相互关联起来呢?毕竟,如果他们之间没有关联的话,当我想要修改Person中的sayHi()方法或添加方法时,我又该到哪去找呢?

如上图所示,prototype和constructor属性能够实现Person原型对象和Person()构造函数的相互引用。如下代码通过prototype实现了修改和添加方法。

// 修改
Person.prototype.sayHi = function () {
  console.log('你好')
}
// 添加
Person.prototype.show = function () {
  console.log('name: ' + this.name + ', age: ' + this.age)
}
3.  静态方法、实例方法、原型方法
静态方法

静态方法是直接定义在构造函数上的方法,只能通过类名调用。

// 构造函数
function Person() {

}
// 静态方法
Person.sayHi = function () {
  console.log('hello')
}
// 调用
Person.sayHi() // hello
实例方法

实例方法是定义在实例上的方法,是单个实例所独有的。

// 构造函数
function Person(name, age) {
  this.name = name
  this.age = age
  // 实例方法
  this.show = function () {
    console.log('name: ' + this.name + ', age: ' + this.age)
  }
}
// 创造实例对象
var p1 = new Person('jack', 20)
var p2 = new Person('rose', 18)
// 实例方法
p1.sayHi = function () {
  console.log('hello')
}
// p1可以调用,p2是无法调用的
p1.sayHi() // hello
// 构造函数中定义的方法会在每个实例中复制一份
p1.show() // name: jack, age: 20
p2.show() // name: rose, age: 18
原型方法

实例方法是定义在原型上的方法,每个实例都可以调用。

// 定义构造函数
function Person() {

}
// 添加原型方法
Person.prototype.sayHi = function () {
  console.log('hello')
}
// 创造实例对象
var p = new Person()
// 调用方法
p.sayHi() // hello
4. 继承的原理
现有父类Father,如下代码,怎样才能让子类Son继承Father的方法呢?即让Son的实例能够调用sayHi()方法。(call、apply、bind的用法)

function Father() {

}
Father.prototype.sayHi = function () {
  console.log('hello')
}
function Son() {
  // 调用父类构造方法,类似面向对象编程中的super()方法
  Father.call(this)
}
首先想到的便是让Son的原型改变方向指向Father的原型,这样不就能调用sayHi()方法了嘛。

/* 错误的案例 */
Son.prototype = Father.prototype
// 创建实例
var son = new Son()
// 调用方法
son.sayHi() // hello
嗯,效果似乎不错,但,如果我想在Son中重写sayHi()方法呢?

/* 错误的案例 */
Son.prototype = Father.prototype
// 重写sayHi()
Son.prototype.sayHi = function () {
  console.log('son: hello')
}
// 创建实例
var son = new Son()
var father = new Father()
// 调用方法
son.sayHi() // son: hello
father.sayHi() // son: hello
这下可好,父类Father中的sayHi()方法也被修改了。

我先提一句,实例在调用方法时是会通过__proto__沿着原型链不断向上查找的,最上层是Object的原型对象Object.prototype,而Object.prototype.__proto__值为null,也就是说,向上查找某个方法直到__proto__为null了还没找到,就会报错。

怎么样,有没有一点思路。其实,原型对象也是一个对象,一个保存公共方法的仓库,而方法会沿着原型链向上查找。有意思的来了,我们可以让Son的原型指向Father的实例,让Father的实例来保存Son的公共方法,而当son想要调用父类的方法时,就可以通过Father实例的__proto__找到Father的原型对象,调用其中的方法。实现代码如下。

// 将Father的实例作为Son的原型对象
Son.prototype = new Father()
// Father实例中没有constructor属性,需要手动指定实现Son构造函数和原型对象的双向关联
Son.prototype.constructor = Son
// 重写sayHi()
Son.prototype.sayHi = function () {
  console.log('son: hello')
}
// 创建实例
var son = new Son()
var father = new Father()
// 调用方法
son.sayHi()    // son: hello
father.sayHi() // hello
值得一提的是,当通过prototype改变构造函数指向的原型对象时,也需要将原型对象的constructor指向构造函数(双向指针)。

5. 总结
如开头的图所示,可以这样简单的理解为,以__proto__连接的两个对象,是存在继承关系的。所有的构造函数都继承自Function原型对象。原型链的最顶层是Object,Object.prototype.__proto__ === null,而这一点也是instance的原理。

就说这么多吧,有什么遗漏的以后再补充啦。 作者:赋宫樱梦 https://www.bilibili.com/read/cv11991753 出处:bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值