编程技术文章分享与教程

网站首页 > 技术文章 正文

不会js中原型、原型链与constructor到底是什么?

hmc789 2024-11-10 10:33:49 技术文章 2 ℃

关注我:知码前端,获取更多前端知识~~~

前言

哇呀呀~我说寒山说哭 我带你出 我敬滴酒带你出 我欲成冰再也无退路 怎舍寒冰冰冻我心哭~~~

Hello,广大的前端小伙伴们,又到了写文章的时候,我们说一下在javascript中一个比较重要的知识点,也是一个比较难理解的知识点,可以这么说如果学javascript这门语言不学会这个知识,那只能说学的没点意思~~那我们来说一下到底要讲什么知识呢----原型

要讲原型我们得分几个点讲解一下:

  • 为什么要有原型
  • 原型是什么
  • 原型链是什么
  • __propo__、prototype、contstructor这三个属性是什么

该篇文章可能会比较长,也可能会比较难懂,希望小伙伴们耐心,多看几次,好好理解~相信大家一定会学会这个知识点。

为什么要有原型

我们知道js中对象是通过构造函数来创建是,有的小伙伴可能要开始怼了,我创建对象的时候就没有用构造函数:

// 我直接通过字面量的方法来创建一个对象,而没有构造函数
const person = {}
// 其实上面代码也可以写成下面的
const person = new Object()
// 这两个写法是等价的

那么我们通过构造函数来创建一个对象,就可以把对象的一些属性和方法直接写到函数里面:

function Person(name, age) {
  this.name = name
  this.age = age
}
const person = new Person('知码', 20)
console.log(person.name) // '知码'
console.log(person.age) // '知码'

这样写是没有问题的,但是会有一个缺点:用一个构造函数来创建的实例之间,无法共享属性。从而导致系统的资源浪费。我们对每个实例添加一个方法:sayHello

function Person(name, age) {
  this.name = name
  this.age = age
  this.sayHello = function () {
    console.log('hello,' + this.name)
  }
}
const person1 = new Person('知码', 20)
const person2 = new Person('前端', 30)
console.log(person1.sayHello === person2.sayHello) // false

上面代码创建的两个实例都有相同的属性:name、age、sayHello。对于name和age来说是可以的,各自一份。但对于sayHello这个方法,有会浪费系统资源,因为没有必要两个实例都有这个方法。换句话说,每个实例之间应该共享这个方法。

为了解决这个问题,js就提供了原型对象(prototype)这个对象。

原型是什么

原型就是一个对象,然后用prototype这个变量来引用。每个function函数在被创建的时候都会有这个属性。(这里注意一下,只有用function声明的函数才会有这个属性,而用箭头函数是没有的。)当函数被普通调用的时候,这个对象是没有什么作用的。但当函数被当成构造函数使用的时候,这个属性就是每个实例的原型对象。在这个对象上创建的属性和方法都可以在每个实例之间共享。如:

function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.address = '北京'
const person1 = new Person('知码', 20)
const person2 = new Person('前端', 30)
person1.address // '北京'
person2.address // '北京'
// 可以看出`address`这个属性可以被两个实例共享

// 如果改变了这个`address`值,那么每个对象也会改变
Person.prototype.address = '上海'
person1.address // '上海'
person2.address // '上海'

可能有的小伙伴会问了,没有在函数中定义address这个属性呀,为什么实例对象会访问到呢?

如果实例对象身上有某个属性或者方法,那么会优先调用自身的,如果在自身上没有找到,就会到原型对象上去找。

对于address我们确定是没有在函数内定义,但是在Person函数的原型上定义了,所以实例对象就会按上述规则去找,最终找到address这个属性了。

原型链是什么

上面说道原型就是一个对象,在javascript中每个对象都可以充当一个原型对象,而每个对象都有自己的原型对象。就这样:

对象---->原型对象---->原型对象上的原型对象----> ...---->Object.prototype---->null

最终会到Object上的原型对象。这就是原型链。原型链的尽头就是null

当某个对象要访问某个属性或者方法的时候,就会按这个链去找,如果自身或者从某个原型上找到了属性,就不会再继续往下查找,直接返回。如果都没有所有的原型对象都没有找到这个属性,就返回undefined。举个例子说一下:

function Person(name, age){
  this.name = name
  this.age = age
}
Person.prototype.address = '北京'
const person = new Person('知码', 20)
person.name // '知码'
person.address // '北京'
person.toString() // [object Object]
person.height // undefined
  • 对于name属性来说,每个实例都有这个属性,所以会访问到,不会去原型或者原型链上查找。
  • 对于address属性来说,自身是没有这个属性,就会去函数的原型对象上去找,结果找到了,就直接返回
  • 对于toString方法来说,自身上没有这个方法,构造函数的原型对象上也没有,直到查找到Object.protptype的原型对象上,结果找到了,就会执行这个方法。
  • 对于height这个属性来说找了所有的原型对象也没有找到,就返回undefined
  • 或许又有小伙伴要问了,对于实例对象来说,它怎么知道去原型对象上去查找自身没有的属性或者方法呢?或者说他是通过什么样的机制去查找呢?这就用到下面要说的__proro__、prototype、constructor这三者的关系了。

    __propo__、prototype、contstructor这三个属性是什么

    如果你能看这到里说明你对原型和原型链有一个比较客观的认识。接下来的内容可能比较难懂,再坚持一下

    __proto__

    __proto__是前后两个下划线_ _,不是一个整体的下划线。

    当我们在创建一个对象的时候,系统都会给这个对象创建一个额外的属性__proto__,这个属性就是指向的构造函数中的原型对象。它是实例对象独有的。所以通过它就能访问到了函数原型对象。

    function Person(name, age){
      this.name = name
      this.age = age
    }
    const person = new Person('知码', 20)
    person.__proto__ === Person.prototype // true
    //两者都是指向同一个对象

    prototype

    前面也提过这个属性,它是function函数独有的。指向一个对象。

    constructor

    这个属性是prototype对象的一个属性,也就是说每个函数原型对象默认都会有这个属性,它指向了构造函数本身。

    用一个句话描述一下这三个对象之间的关系:

    每个实例对象都有__proto__属性,指向着构造函数的原型对象prototype,prototype对象里面有一个属性constructor属性,指向着构造函数本身。

    好了,今天先介绍这么多,下节我们细说下这三个属性。

    关注我:知码前端,获取更多前端知识~~~

    标签列表
    最新留言