前端基础系列:JS原型&原型链详解

2,496 阅读5分钟

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

开始

最近,去复习了一下原型链相关的知识,我为什么突然要去复习原型链呢,说到底还是后期学习遇到卡点了,原型链真的很重要,不仅仅是深入学习JavaScript,还有像VueReact等框架多少都有用到原型链,想要学好框架,原型链一定要学好,另外,原型链在面试也是常考点,很受面试官青睐。总的来说,以前总觉得学了没什么用,现在觉得基础真的很重要。

构造函数与原型

构造函数

ES6之前 ,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征。为什么讲构造函数呢,主要是讲原型链需要,其实ES6当中的类可以看做语法糖,本质还是构造函数,并且我们用原型链可以更好地演示联系。

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。不过有值得注意的是:

  • 构造函数用于创建某一类对象,其首字母要大写
  • 构造函数是一个模板,可以通过new出来实例
  • 不能写return语句

new 的执行过程:

  1. 在内存中创建一个新的空对象
  2. 让 this 指向这个新的对象
  3. 执行构造函数里面的代码,给这个新对象添加属性和方法
  4. 返回这个新对象
// 定义了一个构造函数,不能写return
function Person(name, age){
  this.name=name
  this.age=age
  this.show=function(){ console.log(`我叫${this.name},年龄${this.age}`) }
}

实例

由构造函数new出来的对象被称为实例,也叫实例对象,每一个都是独一无二的。

const p1 = new Person("张三", 18)
const p2 = new Person("李四", 20)
p1.show()// 我叫张三,年龄18
console.log(p1 === p2)// false

原型

原型有两种,一种是在构造函数上的叫显式原型prototype,像上面定义的Person,可以通过Person.prototype拿到。另一种是在实例对象上的叫隐式原型__ proto__p1.__proto__可以拿到,需要注意的是:构造函数的prototype和其实例的__proto__是指向同一个地方的,这个地方就叫做原型对象

console.log(Person.prototype === p1.__proto__)// true

constructor

prototype__proto__都有一个constructor属性,它指回构造函数本身。一般来说,如果我们采用对象赋值形式给原型对象赋值,这会覆盖构造函数原型对象原来的内容,则必须手动的利用constructor指回原来的构造函数。

console.log(p1.__proto__.constructor === Person)// true

原型链

有了上面的基础后,我们就可以去画出Person构造函数的原型链了。Person.prototype也是有原型对象的,通过Person.prototype.__proto__拿到,再往上,Person.prototype.__proto__.__proto__就是null,此时,也就到了原型链的尽头了。

原型链流程图

console.log(Person.prototype.__proto__ === Object.prototype)// true
console.log(Person.prototype.__proto__.__proto__)// null

JavaScript的成员查找机制

原型链的一个重要体现就是JavaScript的成员查找机制。当某个实例使用了某个属性或调用了某个方法时会通过__proto__的路径(原型链)去链式查找,它的过程如下:

  1. 当访问一个对象的属性或时,首先查找这个对象自身有没有该属性或方法
  2. 如果自身没有就查找它的原型, __proto__指向的原型对象
  3. 还没有就查找原型对象的原型,Object的原型对象
  4. 依此类推一直找到 为止,一直到null
function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.name = "张三"
Person.prototype.sex = "男"
Object.prototype.type = "人"

const p1 = new Person("王五", 39)
console.log(p1)
console.log(p1.name)// 王五
console.log(p1.type)// 人

image-20220226194235787

原型继承

原型链还有一个重要的实践就是实现继承,在ES6之前,js是没有类的相关概念的,那么我们是怎么去实现继承的呢,答案就是通过原型,我们给构造函数的原型写入一些方法,可以让其子类构造函数通过原型链的方式使用到这些方法,进而达到了继承的目的。

接下来,我们通过构造函数方式去简单地实现一个的继承:

不知道call()或忘记了的可以去复习一下 Function.prototype.call()

function Father(name, age) {
    this.name = name
    this.age = age
}

function Son(name, age, sex) {
    Father.call(this, name, age)
    this.sex = sex
}

const s1 = new Son("张三", 17, "男")
console.log(s1)
console.log(s1.name)// 张三

在上面的代码基础上,我们再去实现方法的继承

Father.prototype.money = function () {
    console.log("挣钱")// 父类的方法
}
Son.prototype = new Father()// 此时constructor指向已经改了
Son.prototype.constructor = Son// 我们要改回来

Son.prototype.study = function () {
    console.log("学习")// 子类自己的方法
}


const s2 = new Son("李四", 27, "女")
console.log(s2)
s2.money()// 挣钱
s2.study()// 学习

注意:上面的代码执行顺序不能随意更改,否则会出错,这原理也比较好理解,自己可以去思考一下为什么。

到此,我们就已经实现了属性和方法的继承,可以看得出来,实现还是比较麻烦的。但随着ES6的普及,我们很少会再用构造函数去实现继承了,相比于构造函数,类去实现继承可谓是相当简单了,但为了去更好地去讲解原型链,理解原理,我们还是选择了构造函数。

结语

感谢读完本篇文章,希望对你有所帮助,如有错误还请各位指正❤️❤️。