描述一下原型链
在JavaScript中,对象中,均有一个__proto__对象指向其原型对象,新创建的没指定原型对象的对象,他们的原型都是Object,而Object的__proto__则是null,对象可以通过Object.getPrototypeOf()来获取其原型对象。当为了访问某个对象的某个属性或者某个方法的时候,首先会在其自身的属性中找,如果没有找到,则会沿着原型链上的对象去找,如果找到,则返回,如果一直到null都没有找到,则不存在改属性或者方法。
在理解原型链的时候很容易受到影响的是方法中的prototype属性,prototype只是方法中才有的属性。
一、通过一个简单的例子来理解这个概念。
function F(){} // 先定义一个F方法
let obj = new F(); // 通过new的方式生成一个F的实例,obj
在这里例子中,先生成了一个方法F,这个方法会有一个它的原型对象。
①通过F去访问它的prototype,则会得到F.prototype,是F的原型对象。
②而这个原型对象的构造函数就是F本身,F.prototype.constructor指向方法F。
③在生成的obj对象中,有一个__proto__属性,指向F.prototype,则obj是属于F.prototype的实例。表示obj的原型是F.prototype
④F.prototype这个本身是一个对象,则它也有原型对象,即Object.prototype,因为没有显式将方法F从哪个对象继承而来,所以F将继承自Object。
⑤Object.prototype的__proto__则是null。
⑥这里的Object其实是指的方法,这里的原型和构造函数的逻辑跟方法F的一样,可以参照一下。Object本身可以使用new进行实例化。
二、接下来理解一下更深一点的内容。可能会有点绕,但是仔细理解能理解得来。
但是我们不再关注obj了,而是关注方法F、方法Function、方法Object。在JavaScript中,方法也是对象,此图中灰色的概念可以通过上方的内容了解。在接下来的内容中,将会把这些所有的方法主要当做对象来看待。
①这里先理解一下方法Function。
let F = new Function(); // 通过new 实例化Function,
F(); // undefined
new F(); // {}
使用new对Function进行实例化可以生成一个方法F,此处执行为空,可以通过参数传入方法中的逻辑,不是这里重点讨论不做演示。
②也即是方法F是Function实例化的对象(也是方法),那这里的方法F的__proto__将会指向Function.protptype。
③很自然地想到了,方法Function本身也是一个对象,那么它本身也有__proto__属性,也将指向Function.protptype
Function.__proto__ === Function.prototype // true
④接下来是方法Object,很自然的,这是一个方法,也是对象,其__proto__属性也自然是指向了Function.prototype。
⑤因为Function.prototype本身是属于一个对象,所以它本质上也是继承自Object的,所以其__proto__属性将会是Object.prototype
三、ES6中使用了class,则是ES5中原型链的一个语法糖。
ES5继承的实现,需要注意构造器的重新指向,以及父类构造方法的调用
function Person(name, gender, age){ // 定义一个方法,即类
this.name = name;
this.gender = gender;
this.age = age;
}
Person.prototype.greeting = function(){ // 将方法挂在方法的原型上,在实例方法的时候会拿到这里的方法去执行
console.log(`Hi, I am ${this.name}`);
}
Person.prototype.working = function(){
console.log(`I have no work now`);
}
function Programmer(name, gender, age, language){
Person.call(this, name, gender, age); // 此处需要先执行父类的方法,才能拿到父类中定义的属性
this.language = language;
}
Programmer.prototype = Object.create(Person.prototype); // 创建一个父类的对象,让Programmer
Programmer.constructor = Programer;
Programmer.prototype.working = function(){
console.log(`I am a programmer, I use ${this.language}`)
}
let programmer = new Programmer('Jerry', 'female', 22, 'JavaScript');
programmer.greeting() // Hi, I am Jerry
programmer.working() // I am a programmer, I use JavaScript
ES6的继承方式,两种方式得到的结果一致。
class Person{
constructor(name, gender, age){
this.name = name;
this.gender = gender;
this.age = age;
}
greeting = function(){
console.log(`Hi, I am ${this.name}`);
}
working = function(){
console.log(`I have no work now`);
}
}
class Programmer extends Person{
constructor(name, gender, age, language){
super(name, gender, age);
this.language = language;
}
working = function(){
console.log(`I am a programmer, I use ${this.language}`)
}
}