原型
在Javascript中每一个对象都有一个隐藏的属性--prototype,prototype的值要么为null要么指向一个叫做原型的对象,当我们要调用一个对象不存在的属性时,Javascript会默认从对象的原型获取该属性,这也叫做原型继承。
对象的prototype属性是内置且隐藏的,这里有多种方法去设置/获取它,其中的一种方法是使用__proto__,例如:
let animal = {
eats: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = animal;
要注意的是,__proto__和prototype是不同的,__proto__是prototype的getter和setter方法
当我们需要读取rabbit对象中不存在的属性时,Javascript会自动到rabbit的原型中寻找,因为我们设置了rabbit.__proto__ = animal,所以就到animal对象中寻找属性,若animal对象中没有该属性,则又到animal的原型中查找,沿着原型链寻找下去,例如:
let animal = {
eats: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = animal; // (*)
// we can find both properties in rabbit now:
alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true
这里,rabbit.eats就是从animal对象中获取的
同理,对象方法属性也是一样:
let animal = {
eats: true,
walk() {
alert("Animal walk");
}
};
let rabbit = {
jumps: true,
__proto__: animal
};
// walk is taken from the prototype
rabbit.walk(); // Animal walk
下面是一个原型链的例子:
let animal = {
eats: true,
walk() {
alert("Animal walk");
}
};
let rabbit = {
jumps: true,
__proto__: animal
};
let longEar = {
earLength: 10,
__proto__: rabbit
}
// walk is taken from the prototype chain
longEar.walk(); // Animal walk
alert(longEar.jumps); // true (from rabbit)
要注意的是,原型链有以下两个限制:
(1)原型链不能绕成一个圈,否则会出现循环引用的问题,编译器就会报错;
(2)__proto__的值要么是null,要么是原型对象的引用,其他的值都会被忽略;
读写规则
我们可以手动对原型进行读和写操作。
我们已经知道对象属性有数据属性和访问器属性这两种,对于数据属性,我们可以在该对象直接进行读写操作,例如:
let animal = {
eats: true,
walk() {
/* this method won't be used by rabbit */
}
};
let rabbit = {
__proto__: animal
}
rabbit.walk = function() {
alert("Rabbit! Bounce-bounce!");
};
rabbit.walk(); // Rabbit! Bounce-bounce!
这里,walk()方法就直接添加在rabbit对象里,并没有添加在rabbit的原型里
对于访问器属性也是一样:
let user = {
name: "John",
surname: "Smith",
set fullName(value) {
[this.name, this.surname] = value.split(" ");
},
get fullName() {
return `${this.name} ${this.surname}`;
}
};
let admin = {
__proto__: user,
isAdmin: true
};
alert(admin.fullName); // John Smith (*)
// setter triggers!
admin.fullName = "Alice Cooper"; // (**)
admin由于没有getter和setter方法,故它会在原型链进行寻找,这里admin调用的是user的getter和setter方法
this的值
原型继承还有一个问题就是,this所指的对象到底是哪一个?是admin?还是user?
问题的答案很简单,this跟原型继承没有关系,只要是谁调用属性方法,this的值就指向谁。也就是说点号“.”前的对象是哪个,this就指向哪个。例如:
// animal has methods
let animal = {
walk() {
if (!this.isSleeping) {
alert(`I walk`);
}
},
sleep() {
this.isSleeping = true;
}
};
let rabbit = {
name: "White Rabbit",
__proto__: animal
};
// modifies rabbit.isSleeping
rabbit.sleep();
alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined (no such property in the prototype)
这里,rabbit.sleep()虽然调用的是animal的方法,但是点号“.”前的对象是rabbit,所以this的值为rabbit对象,isSleeping这个属性就属于rabbit的了