# 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