关于JS的原型与继承相关概念
在ES6后,JavaScript已经可以通过class、extends等语法糖实现面向对象的写法,但是以前的原型对象也是必须得了解的,看文章的话记不清楚,自己总结一下。
构造函数
在说原型前,有必要了解一下以前的面向对象的写法,JavaScript的对象是通过构造函数实例获得的:
function Fo(msg) {
this.msg = msg
}
Person就是一个构造函数,一般首字母大写。并且有两个形参:name和sex,赋值给了this对象。
此时,可以通过new的操作获取一个Person的实例:
const f = new Foo('Hello')
console.log(f.msg) // Hello
这里有一个问题,为什么通过man这个实例对象能访问到msg呢,这里就要说一下new的操作了,其实在new实例化构造函数时,内部做了以下操作:
var obj = {}
obj.__proto__ = Person.prototype
Person.call(obj)
return obj
上面看到了两个奇怪的东西,就是__proto__和prototype,那这两个又是啥呢?
原型对象和原型链
__proto__:每个实例对象都有这一个属性,与构造函数的原型函数对应;
prototype:原型对象,每一个构造函数都有这一个对象。
从下面这张图,我们可以看到两者的关系:
从图中可以看到,实例对象的__proto__是指向其构造函数的prototype的,并且有JavaScript在使用属性或方法时有一个特点就是,在当前实例对象寻找需要调用的属性或方法,如果没有,则向实例对象的proto**(也就是构造函数的prototype对象上找),直到找到或者到顶层的null为止。**
并且从代码中,可以看到是完全相等的:
console.log(f.__proto__ === Fo.prototype) // true
由f.__proto__ => Fo.prototype => Fo.prototype.__proto__ => Object.prototype => null,这就形成了一条链式的结构,这就是所谓的原型链。
继承
JavaScript的继承分为两种情况,第一种是继承属性,可以通过call方法来实现,第二种就是方法的继承,需要通过原型链来实现,看一下代码:
function Foo(msg) {
this.msg = msg
}
Foo.prototype.printMsg = function () {
console.log(this.msg)
}
function Fo(msg, greet) {
Foo.call(this, msg)
this.greet = greet
}
Fo.prototype = new Foo()
Fo.prototype.constructor = Fo
Fo.prototype.printGreet = function () {
console.log(this.greet)
}
const fo = new Fo('Hello', 'How are you?')
console.log(fo)
打印这个fo,可以看到下图:
明显的,greet属性和msg属性就是属于Fo实例对象的,然后看看printMsg和printGreet这两个方法:
printGreet此时是在fo的__proto__上的,即Fo.prototype上;
又因为Fo.prototype又指向了Foo的实例对象,所以Fo.prototype其实是指向Foo的实例对象的;
最后可以看到printMsg是挂在Foo.prototype原型对象上的。
所以,可以看出我们访问fo对象的属性时,本身就在实例对象上,可以直接访问到;
而当访问方法时,是通过访问fo.__proto__即Fo.prototype即Foo实例对象上找的,又根据原型链的特点,访问不到对应方法时,会往Foo实例对象的__proto__属性即Foo.prototype上找,所以就实现了方法的继承。
总结
其实,JavaScript的对象是通过new构造函数得到的,new的过程内部已经帮我们处理过了;
继承是通过原型链的特点来实现的,把“子类”的prototype指向“父类”的实例对象,这样通过原型链查找时,自然会找从子类找向父类。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!