原型是JavaScript继承的基础,在早起版本中,JavaScript严重限制了原型的使用。随着语言逐渐成熟,开发者们也更加熟悉原型的运作方式,他们希望获得更多对于原型的控制力,并以更简单的方式来操作原型。于是,ECMAScript 6针对原型进行了改进。
改变对象的原型
正常情况下,无论是通过构造函数还是Object.create()方法创建对象,其原型都是在对象被创建时指定的。对象原型在实例化之后保持不变,直到ECMAScript 5都是Java编程最重要的设定之一,虽然在ECMAScript 5中添加了Object.getPrototypeOf()方法来返回任意指定对象的原型,但仍缺少对象在实例化后改变原型的标准方法。
所以,在ECMAScript 6中添加了Object.setPrototypeof()方法来改变这一现状,通过这个方法可以改变任意指定对象的原型,它接受两个参数:被改变原型的对象及替代第一个参数原型的对象。举个栗子:
let person = {
getGreeting() {
return "Hello";
}
}
let dog = {
getGreeting() {
return "Woof";
}
}
let friend = Object.create(person);
console.log(friend.getGreeting());
console.log(Object.getPrototypeOf(friend) === person);
Object.setPrototypeOf(friend,dog);
console.log(friend.getGreeting());
console.log(Object.getPrototypeOf(friend) === dog);
这段代码中定义了链各个基对象:person和dog。二者都有getGreeting()方法,且都返回一个字符串。firend对象先继承person对象,调用getGreeting()方法输出"Hello";当原型被替换为dog对象时,原先与person对象的关联被解除,调用friend.getGreeting()方法时输出的内容变为了"Woof"。 对象原型的真实值被储存在内部专用属性[[Prototype]]中,调用Object.getPrototypeOf()方法返回储存在其中的值,调用Object.setPrototypeOf()方法改变其中的值。然而,这不是操作[[Prototype]]值的唯一方法。
简化原型访问的Super引用
正如之前提及的,原型对于JavaScript而言非常重要,ECMAScript 6中许多改进的最终目标就是为了使其更易用。以此为目标,ECMAScript 6引入了Super引用的特性,使用它可以更便捷地访问对象原型。举个例子,如果你想重写对象实例的方法,又需要调用与它同名的原型方法,则在ECMAScript 5中可以这样实现:
let person = {
getGreeting() {
return "Hello";
}
}
let dog = {
getGreeting() {
return "Woof";
}
}
let friend = {
getGreeting() {
return Object.getPrototypeOf(this).getGreeting.call(this) + ",Hi !";
}
}
Object.setPrototypeOf(friend,person);
console.log(friend.getGreeting());
console.log(Object.getPrototypeOf(friend) === person);
Object.setPrototypeOf(friend,dog);
console.log(friend.getGreeting());
console.log(Object.getPrototypeOf(friend) === dog);
在这个示例中,friend对象的getGreeting()方法调用了同名的原型方法。Object.getPrototypeOf()方法可以确保调用正确的原型,并向输出字符串叠加了另一个字符串;后面的.call(this)可以确保正确设置原型方法中的this值。
要准确记得如何使用Object.getPrototypeOf()和.call(this)方法来调用Super引用相当于指向对象原型的指针,实际上也就是Object.getPrototypeOf(this)的值。于是,可以这样简化上面的getGreeting()方法:
let friend = {
getGreeting() {
return super.getGreeting() + ",Hi !";
}
}
调用super.getGreeting()方法相当于在当前上下文中调用Object.getPrototypeOf(this).getGreeting.call(this)。同样,可以通过Super引用调用对象原型上所有其他的方法。当然,必须要在使用简写方法的对象中使用Super引用,但如果在其他方法声明中使用会导致语法错误。就想这样
let friend = {
getGreeting: function() {
return super.getGreeting() + ",Hi !";
}
}
报错信息:Uncaught SyntaxError: ‘super’ keyword unexpected here (at prototype.js:25:16) 在这个示例中用匿名function定义了一个属性,由于在当前上下文中Super引用时非法的,因此调用super.getGreeting()方法会抛出语法错误。
另外,Super引用在多重继承的情况下非常有用。因为在这种情况下,使用Object.getPrototypeOf()方法将会出现问题。
let person = {
getGreeting() {
return "Hello";
}
}
let friend = {
getGreeting() {
return Object.getPrototypeOf(this).getGreeting.call(this) + ",Hi !";
}
}
Object.setPrototypeOf(friend,person);
let relative = Object.create(friend);
console.log(person.getGreeting());
console.log(friend.getGreeting());
console.log(relative.getGreeting());
prototype.js:10 Uncaught RangeError: Maximum call stack size exceeded
at Object.getGreeting (prototype.js:10:9)
at Object.getGreeting (prototype.js:10:56)
at Object.getGreeting (prototype.js:10:56)
at Object.getGreeting (prototype.js:10:56)
at Object.getGreeting (prototype.js:10:56)
at Object.getGreeting (prototype.js:10:56)
at Object.getGreeting (prototype.js:10:56)
at Object.getGreeting (prototype.js:10:56)
at Object.getGreeting (prototype.js:10:56)
at Object.getGreeting (prototype.js:10:56)
this时relative,relative的原型是friend对象,当执行relative的getGreeting方法时,会调动firend的getGreeting方法,而此时的this值为relative,Object.getPrototypeOf(this)又会返回friend对象。所以就会进入递归调用直到触发栈溢出报错。 在ECMAScript 5中很难解决这个问题,但在ECMAScript 6中,使用Super引用便可以迎刃而解
let person = {
getGreeting() {
return "Hello";
}
}
let friend = {
getGreeting() {
return super.getGreeting() + ",Hi !";
}
}
Object.setPrototypeOf(friend,person);
let relative = Object.create(friend);
console.log(person.getGreeting());
console.log(friend.getGreeting());
console.log(relative.getGreeting());
Super引用不是动态变化的,它总是指向正确的对象,在这个示例中,无论有多少其他方法继承了getGreeting方法,super.getGreeting始终都指向person.getGreeting()方法。
|