原型和原型链

109 阅读6分钟

原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。

通过该构造函数产生的对象,可以继承该原型的属性和方法。

什么是构造函数?

构造函数是一种特殊的函数,主要用来初始化对象

通过构造函数来快速创建多个类似的对象。

构造函数的约定:

大驼峰命名规则(大写字母开头);

只能由“new”操作符来执行。

说明:

  1. 使用new关键字调用函数的行为被称为实例化
  2. 实例化构造函数时没有参数时可以省略()
  3. 构造函数内部不需要return,返回值即为新创建的对象
  4. new Object() , new Date() 也是实例化构造函数

内部原理

三段式(主要原理就是函数中的this到底是什么)

  1. 在函数体最前面隐式的创建this={}
  2. 执行this.xxx = xxx (this = {xxx:实参})
  3. 在函数最后隐式的返回 return this
function Person(name,sex,age){
    // 1. 在逻辑的最顶端生成一个 let this = {}
    // AO { this : {}}
    this.name = name
    this.sex = sex
    this.age = age
    // this  = {
    //  name:'tom',
    //  sex:'male',
    //  age:18
    // }

    // return this
}
let p = new Person('tom','male',18)

回到原型

原型本质上是一个对象,这个对象是在构造函数身上的,也称prototype为原型对象

//构造函数的特点:大驼峰。
//构造函数是为了生产对象的,使用new来生产对象
//构造函数通过不停的调用可以产生多个相似且独立的对象
//构造函数和原型的this都指向实例化对象

// Person.prototype   -->  原型
// Person.prototype = {}  是祖先
Person.prototype.LastName = 'Zhao'
Person.prototype.say = function(){
    console.log('hi');
}
function Person(){

}
let p1 = new Person()
let p2 = new Person()
console.log(p1.LastName);  //Zhao  会继承prototype上的属性

原型的应用

提取共有属性

利用原型的特点和概念,可以提取共有属性。

function Car(color,owner){
    this.owner = owner
    this.color = color
    this.height = 1400
    this.lang = 4900
    this.name = 'BMW'
}
let car = new Car('red','tom')
let car1 = new Car('green','tony')
console.log(car);  //Car {owner: 'tom', color: 'red', height: 1400, lang: 4900, name: 'BMW'}
console.log(car1); //Car {owner: 'tony', color: 'green', height: 1400, lang: 4900, name: 'BMW'}

但是每次实例化对象都要执行一次this.height,this.lang,this.name,就造成了代码的冗余。

就可以通过继承的规则,将它们放入原型上,给原型赋值就只执行了一遍

Car.prototype.height = 1400
Car.prototype.lang = 4900
Car.prototype.name = 'BMW'
function Car(color,owner){
    this.owner = owner
    this.color = color
}
let car = new Car('red','tom')
let car1 = new Car('green','tony')
console.log(car);  //Car {owner: 'tom', color: 'red'}
console.log(car1); //Car {owner: 'tony', color: 'green'}
console.log(car.height);  //1400
console.log(car1.name);  //'BMW'


//也可以这样写
Car.prototype = {
    height:1400,
    lang:4900,
    name:'BMW'
}
function Car(color,owner){
    this.owner = owner
    this.color = color
}
let car = new Car('red','tom')
let car1 = new Car('green','tony')
console.log(car);  //Car {owner: 'tom', color: 'red'}
console.log(car1); //Car {owner: 'tony', color: 'green'}
console.log(car.height);  //1400
console.log(car1.name);  //'BMW'

把不变的方法,直接定义在prototype对象上,这样所有的对象的实例可以共享这些方法。

constructor属性

每个原型对象里面都有一个constructor属性,也叫构造器

该属性指向原型对象的构造函数

function Car(){

}
let car = new Car()
console.log(Car.prototype);
console.log(car.constructor);

image.png

并且可以手动修改constructor的值

对象原型 proto

对象都有一个属性__proto__ 指向构造函数的prototype原型对象。

简单来说,对象原型指向原型对象

之所以对象能够使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在。

__proto__不是js标准属性,所有浏览器用[[prototype]]表示

[[prototype]]和__proto__意义相同

对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数。

Person.prototype.name = 'abc'
function Person(){

}
let person = new Person()
console.log(person.__proto__);  //{name: 'abc'}

__proto__可以被修改

Person.prototype.name = 'abc'
function Person(){
    /**
     * let this = {
     *   __proto__ : Person.prototype
     *  }
    */
}
let person = new Person()
let obj = {
    name:'cba'
}
person.__proto__ = obj
// person继承的对象就变成了obj
console.log(person.name);  //'cba'

原型继承

继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript中大多是借助原型对象实现继承的特性。

let Person = {
    eyes:2,
    mouse:1
}
// 想要Woman和Man两个构造函数都继承Person对象的属性
// 则可以把Person对象放到原型上,也就是说的公共的部分放到原型上,称之为原型继承

function Woman(){

}
//Woman通过原型继承
Woman.prototype = Person
let lily = new Woman()
function Man(){

}
//Man通过原型继承
Man.prototype = Person
let tom = new Man()

image.png

按道理prototype里有一个constructor属性,可是这两个原型都没有这个属性

image.png

那就需要让它指回构造函数

//Woman通过原型继承
Woman.prototype = Person
//指回原来的构造函数
Woman.prototype.constructor = Woman

//Man通过原型继承
Man.prototype = Person
//指回原来的构造函数
Man.prototype.constructor = Person

image.png

image.png

//给女人添加一个生孩子的方法
Woman.prototype.haveBaby = function (){
    console.log('生孩子');
}

image.png

发现lily有这个方法,而tom也有这个方法,为什么呢?

Woman和Man继承的是同一个原型,它们的原型是一模一样的

Woman.prototype === Man.prototype 为true

根据引用类型的特点,他们指向同一个对象,修改其中一个另一个就会受到影响。他们的原型对象都指向Person,修改就会直接修改到Person

怎么解决对象一样,而结构不一样呢?

可以使用深拷贝,让它们两个的原型都等于JSON.parse(JSON.stringify(Person))

可以使用构造函数,因为构造函数new出来的对象的特性就是结构一样,但是对象不一样

//用构造函数的形式
function Person(){
    this.eyes = 2
    this.mouse = 1
}
function Woman(){

}
//Woman通过原型继承
Woman.prototype = new Person()
//指回原来的构造函数
Woman.prototype.constructor = Woman
//给女人添加一个生孩子的方法
Woman.prototype.haveBaby = function (){
    console.log('生孩子');
}
let lily = new Woman()
function Man(){

}
//Man通过原型继承
Man.prototype = new Person()
//指回原来的构造函数
Man.prototype.constructor = Person
let tom = new Man()

image.png

原型链

基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,将原型对象的链状结构关系称为原型链。

在 JavaScript 中,每个对象都有一个原型对象。这个原型对象又有自己的原型对象,它们形成了一个原型链。

Object.prototype是绝大多数对象的最终原型对象

function Grand(){

}
let grand = new Grand()

Father.prototype = grand
function Father(){
   
}
let father = new Father()

Son.prototype = father
function Son(){
    
}
let son = new Son()

/*
son—__proto__指向Son.prototype

Son.prototype指向father

father.__proto__指向Father.prototype

Father.prototype指向grand

grand.__proto__指向Grand.prototype

Grand.prototype__proto__指向Object.prototype

Object.prototype.__proto__指向为null

原型链和作用域链一样,是一层一层进行查找的
*/

查找规则:

  1. 当访问一个对象的属性或方法时,首先查找对象本身有没有该属性或方法
  2. 如果对象本身没有这个属性或方法,则去它的原型对象中查找,也就是__proto__指向的prototype
  3. 如果原型对象中也没有这个属性或方法,它会继续查找原型对象的原型对象
  4. 依次类推直到到达原型链的末尾(Object.prototype)