# 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
中还提供了一些很有用的的方法:
hasOwnProperty
该方法可以判断属性是自己本身属性还是由原型链继承来的属性。(该方法位于Object.prototype
对象上)
// 还以上面的方法为例
xiaoMing.hasOwnProperty("name") // true
xiaoMing.hasOwnProperty("eacting") // false
isPrototypeOf
检测一个对象是否是另一个对象的原型
Object.prototype.isPrototypeOf(xiaoMing) // true
People.prototype.isPrototypeOf(xiaoMing) // true
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__
所以它有两条继承链
子类的
__proto__
属性,表示构造函数的继承,总是指向父类。子类
prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性。
class Fun{
}
class NewFun extends Fun{
}
NewFun.__proto__ === Fun // true
NewFun.prototype.__proto__ === Fun.prototype // true
NewFun.prototype.constructor === NewFun // true