JS-理解原型与原型链

2021-12-29  本文已影响0人  爱学习的小仙女早睡早起

构造函数

构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写。另外就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用

    function Person(name, age, gender) {
        this.name = name
        this.age = age
        this.gender = gender
        this.sayName = function () {
            alert(this.name);
        }
    }
    var per = new Person("孙悟空", 18, "男");
    function Dog(name, age, gender) {
        this.name = name
        this.age = age
        this.gender = gender
    }
    var dog = new Dog("旺财", 4, "雄")
    console.log(per);//当我们直接在页面中打印一个对象时,事件上是输出的对象的toString()方法的返回值
    console.log(dog);

每创建一个Person构造函数,在Person构造函数中,为每一个对象都添加了一个sayName方法,也就是说构造函数每执行一次就会创建一个新的sayName方法。这样就导致了构造函数执行一次就会创建一个新的方法,执行10000次就会创建10000个新的方法,而10000个方法都是一摸一样的,为什么不把这个方法单独放到一个地方,并让所有的实例都可以访问到呢?这就需要原型(prototype)

原型

在JavaScript中,每当定义一个函数数据类型 (普通函数、类)时候,都会天生自带一个prototype属性,这个属性指向函数的原型对象,并且这个属性是一个对象数据类型的值。
image.png

原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。

 function Person(name,age){
            this.name=name
            this.age=age
            this.sayName = function () {
                alert(this.name);
            }
        }

改造下

 function Person(name,age){
            this.name=name
            this.age=age
        }
 
        Person.prototype.year=2021   // 往原型上添加一点东西
        Person.prototype.sayName =function(){
            alert(this.name);
        }
        const personA=new Person()
        const personB=new Person()
        console.log('personA',personA , personB)
        console.log(personA.hasOwnProperty('year'), 'year' in personA ) // false true
        console.log(personB.hasOwnProperty('year'), 'year' in personB) // false true
        // hasOwnProperty只会从实例本身上找属性, in会从实例所属类的原型身上找
        console.log('personA.year',personA.year , personB.year )  // 2021 2021

原型的作用:
1:实现对象之间的数据共享。
2.在es6之前,没有class的情况下,模拟面向对象,构造函数中放私有属性,原型上放公有属性,一般放方法。

通过原型添加的方法,可以完美的解决属性与方法共享问题,从而节省了内存空间..

原型链

每一个对象数据类型(普通的对象、实例、prototype......)也天生自带一个属性__proto__,这个属性的值是当前实例所属类的原型对象(prototype)。
原型对象中有一个属性constructor, 它指向构造函数。

image.png
    function Person() {}   // 构造函数
    var person = new Person()  // 实例person
    console.log(person.__proto__ === Person.prototype)  // true
    console.log(Person.prototype.constructor===Person)  // true

    //顺便学习一个ES5的方法,可以获得对象的原型
    console.log(Object.getPrototypeOf(person) === Person.prototype)   // true

js实例对象的原型属性__proto__的属性值,指向这个实例对象所属类的原型

例如 new Array 出来的数组,属于Array这个类

const xArray=new Array()

那么

xArray.__proto__ === Array.prototype  // true

Array.prototype拥有的方法,array的每一个实例都会有,直接通过xArray.push()这样调用,而不需要xArray._ proto _.push,不需要这样去调用。

 Array.prototype.__proto__ === Object.prototype  // true
Object.prototype._proto_  // null

所谓原型链,指的就是这一条指针链!

原型链的顶层就是Object.prototype,而Object的原型对象的是没有原型属性的。 Object.prototype._proto_ ===null

何为原型链

在JavaScript中万物都是对象,对象和对象之间也有关系,并不是孤立存在的。对象之间的继承关系,在JavaScript中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链。

举例说明:person → Person → Object ,普通人继承人类,人类继承对象类

当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined。

我们可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性;使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true

function Person() {}
    Person.prototype.a = 123;
    Person.prototype.sayHello = function () {
      alert("hello");
 };
    var person = new Person()
    console.log(person.a)//123
    console.log(person.hasOwnProperty('a'));//false
    console.log('a'in person)//true

person实例中没有a这个属性,从 person 对象中找不到 a 属性就会从 person 的原型也就是 person.__proto__,也就是 Person.prototype中查找,很幸运地得到a的值为123。那假如 person.__proto__中也没有该属性,又该如何查找?

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层Object为止。Object是JS中所有对象数据类型的基类(最顶层的类),在Object.prototype上没有__proto__这个属性。

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

所谓原型链,指的就是图中的proto这一条指针链!

原型链的顶层就是Object.prototype,而Object的原型对象的是没有原型属性的。

先找自身,找不到就沿着__proto__一直往上找原型,直到找到最顶层的类Object,Object类没有原型了,如果还找不到就返回undefined


实战:用ES6的class定义一套对象/函数

ES6提供了class,但是这个并不是类,而是 Function 的语法糖。
目的是简化ES5里面,为了实现继承而采用的各种“神操作”。

用class来定义,结构和关系会非常清晰,再也不会看着头疼了,建议新手可以跳过ES5的实现方式,直接用ES6的方式。

我们先定义一个Base,然后定义一个Person继承Base,再定义一个Man继承Person。
也就是说,可以深层继承。

class Base {
  constructor (title) {
    this.title = '测试一下基类:' + title
  }

  baseFun1(info) {
    console.log('\n这是base的函数一,参数:', info, '\nthis:', this)
  }
}

class Person extends Base{
  constructor (title, age) {
    super(title)
    this.title = '人类:' + title
    this.age = age
  }

  personFun1(info) {
    console.log('\n这是base的函数一,参数:', info, '\nthis:', this)
  }
}


class Man extends Person {
  constructor (title, age, date) {
    super(title, age)
    this.title = '男人:' + title
    this.birthday = date
  }

  manFun3 (msg) {
    console.log('jim 的 this ===', this, msg)
  }
}

Man的实例 man1,可以通过这个“链条”,找到 baseFun1,
直接用 man1.baseFun1() 即可(✔),
而不需要使用__proto__
man1.__ proto__.__ proto__.__ proto__.baseFun1() (✘)

man1.__ proto__ === Man.prototype
Man.prototype .__proto__ === Person.prototype
Person.prototype .__proto__ === Base.prototype
Base.prototype .__proto__ === Object.prototype
Object.prototype .__proto__ === null

image.png

Object是js顶层类,原型链的顶端也就是Object.prototype ,Object.prototype没有__proto__属性



上一篇下一篇

猜你喜欢

热点阅读