# JS-原型与原型链

🐴

JavaScript 函数都有一个prototype对象(除ES6箭头函数),大部分对象都有一个__proto__原型链指针(原型链最末端的对象不存在)。

# 继承

JavaScript中的继承和Java的继承实现是不一样的,在JavaScript中是通过原型和原型链来实现继承的。我们来看下面的例子:

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

People.prototype.eating = function(foods){
    var foods = foods || "没有食物"
    alert(foods)
}

// 使用new创建一个对象
var xiaoMing = new People("小明",24);

// 现在 xiaoMing这个对象就有下面属性了
xiaoMing.name // 小明
xiaoMing.age //  24

// xiaoMing同时也具有`eating` 方法了
xiaoMing.eating("屎")

我们可以看到通过构造函数创建出来的xiaoMing对象继承了People的原型上的eating方法。下面我们来看看原型和原型链。

# 原型

JavaScript中每一个函数都有一个属性prototype(ES6中的箭头函数没有原型属性),这个属性就是我们经常提及的原型。

// 函数声明形式
function fun(){}
console.dir(fun.prototype) // Object

// 函数表达式形式
var f = function(){}
console.dir(fun.prototype) // Object

// 箭头函数
let a = ()=>{}
console.dir(a.prototype) // undefined

既然原型是一个对象,那我们是可以在原型上添加方法和属性的。比如上面第一个例子中我们就在构造函数People的原型上添加了eating方法。

People.prototype.eating = function(foods){
    var foods = foods || "没有食物"
    alert(foods)
}

我们可以打印出构造函数People的原型来看看

function People(name,age){
    this.name = name
    this.age = age
}
People.prototype.eating = function(foods){
    var foods = foods || "没有食物"
    alert(foods)
}
console.dir(People.prototype)

/**
 * 结果如下:
 {
    eating: ƒ (foods)
    constructor: ƒ People(name,age)
    __proto__: Object
  
  }  
*/

我们从原型prototype上可以看到我们定义的eating方法,也会看到一个constructor方法,这个方法指向的是我们构造函数本身

People.prototype.constructor === People  // true  

我们从原型prototype对象上还看到了一个属性__proto__(前后各两个下划线),这个属性指向另一个对象的原型,下面我们来看看这个属性。

# 原型链

我们知道了属性__proto__可以指向另一个对象的原型,从而继承另一个对象原型上的方法或属性。我们接着来看我们第一个例子。

function People(name,age){
    this.name = name
    this.age = age
}
People.prototype.eating = function(foods){
    var foods = foods || "没有食物"
    alert(foods)
}
var xiaoMing = new People("小明",24);

console.log(xiaoMing) 

我们打印出来xiaoMing这个对象之后,会发现这个对象包含name属性、age属性、还有__proto__,但是并没有原型prototype, 但是我们也同样可以在xiaoMing这个对象上使用eating()方法。

xiaoMing.eating("屎");

这里就是__proto__原型链起到了作用,__proto__指向了构造函数的原型,使得创建出来的xiaoMing这个对象继承了构造函数的原型。我们从下面判断中可以得知

// true
xiaoMing.__proto__ === People.prototype  

所以在js中有了原型链,我们很好的实现了继承。我们可以用下面这个图来表示,构造函数函数(People)、构造函数的原型(People.prototype),对象(xiaoMing)、 原型链(__proto__):

我们很清楚的看到js的中的继承, 正是因为js的继承关系,当我们在访问xiaoMing.eating()方法时,在xiaoMing对象上没有找到,js会通过原型链__proto__去查找,直到找到原型链的最末端Object.prototype对象上为止,如果还没有找到该方法,就会返回undefined。上面例子中xiaoMing.__proto__指向的是构造函数的原型People.prototype, 那么构造函数上也存在__proto__, 并指向最终的对象Object.prototype,在该对象(Object.prototype)上就不再有__proto__了,因为该对象已经是原型链的最末端对象了,js中几乎所有的函数和对象都继承自该对象。

另外js中还提供了一些很有用的的方法:

  1. hasOwnProperty 该方法可以判断属性是自己本身属性还是由原型链继承来的属性。(该方法位于Object.prototype对象上)
// 还以上面的方法为例

xiaoMing.hasOwnProperty("name") // true
xiaoMing.hasOwnProperty("eacting") // false

  1. isPrototypeOf 检测一个对象是否是另一个对象的原型
Object.prototype.isPrototypeOf(xiaoMing) // true
People.prototype.isPrototypeOf(xiaoMing) // true
  1. instanceof 运算符左操作数是一个对象,右操作数标识对象的类。如果左侧对象是右侧类的实例,则表达式返回为true,否则返回false
// 对象xiaoMing 是People的实例
xiaoMing instanceof People  // ture

# 修改原型链

我们在创建对象时,可以使用Object.create()来创建对象,并设置原型链的指针__proto__指向。

var Obj = {
    name:"King",
    age:100
}
var createObj = Object.create(Obj)

createObj.__proto__ === Obj // true

# ES6继承

ES6中可以通过class来声明类了。

class fun{
}

并且通过class声明的类就是一个函数,它是构造函数的一个语法糖,并且通过class声明的类中既包含prototype属性也包含__proto__

typeof fun // function

ES6中实现继承可以使用extends关键字。因为类既包含prototype属性也包含__proto__所以它有两条继承链

  1. 子类的__proto__属性,表示构造函数的继承,总是指向父类。

  2. 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

class Fun{
}

class NewFun extends Fun{
}

NewFun.__proto__ === Fun  // true
NewFun.prototype.__proto__  === Fun.prototype // true
NewFun.prototype.constructor === NewFun // true
最近更新时间: 7/2/2021, 11:27:27 AM