您的位置:首页 > Web前端 > JavaScript

Javascript面向对象(三)——原型继承

2017-04-15 22:18 441 查看

Javascript面向对象(三)——原型继承

实际编程中,我们经常需要一些东西并扩展之。例如,我们有
user
对象,带有属性和方法,现在想要
admin
guest
,和其稍微有些变化,我们最好重用
user
对象,但不是复制/重新实现它的方法,而是在其基础上构建。原型继承是Javascript重要特性,可以实现之。

[[Prototype]]

在Javascript中,对象有个特殊的隐藏属性
[[Prototype]]
(规范中的名称),其可以为null或引用其他对象,该对象称为原型:



这个
[[Prototype]]
有个魔力的意思,当我们从对象中读取属性,如果没有找到,Javascript自动从其原型中查找。在编程中,这样机制被称为“原型继承”。很多酷的语言和编程技术是基于该机制。

属性
[[Prototype]]
是内在的且隐藏的,但是有很多方式去设置它。

其一是使用
__proto__
方式,代码如下:

let animal = {
eats: true
};
let rabbit = {
jumps: true
};

rabbit.__proto__ = animal;


请注意,
__proto__
[[Prototype]]
不同,后者是前者的getter/setter访问器,后面我们讨论其他方式,现在使用
__proto__
够用。

如果我们在
rabbit
中查找属性,没有发现,Javascript自动从
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




这里,我们说
animal
rabbit
的原型,或
rabbit
原型继承自
animal
对象。

所以如果
animal
有许多有用的属性和方法,那么自动成为
rabbit
对象的属性和方法,这些是继承的。

如果
animal
有一个方法,可以在
rabbit
中调用:

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、不能循环引用。Javascript抛出错误,如果
__proto__
循环引用。

2、
__proto__
的值,只能赋值为对象或null,所有其他值(原始值)被忽略。

另外显而易见,只有有一个
[[Prototype]]
,不支持多继承。

读/写规则

原型仅用于reading属性。

对于数据属性(不是getter/setter访问器),写/删除操作直接通过对象实现。下面的例子,我们给
rabbit
自己的
walk
方法赋值:

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!


现在,
rabbit.walk()
在自己内部查找方法并立刻调用,没有使用原型方法。



对于getter/setter访问器,如果我们读写属性,他们在原型中查找并执行。示例,留意代码中的
admin.fullName
属性。

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.fullName
,在原型
user
中有getter访问器,所以他可以调用,(*)行属性在原型中有setter访问器,所以也可以调用。

this的值

上面的示例可能提出有趣的问题,在
setfullName(value)
内部this的值是什么?
this.name
this.surname
是那个对象的属性,
user
admin


答案是简单的:this根本不受原型影响。

无论方法出现在哪里,对象或原型。调用方法时,this总是“.”号前面的那个对象。

所以,setter是有admin调用,this是admin,不是user。

这实际是超级重要的事情,因为我们可能有一个大对象,带有很多方法,从它继承。那么我们能调用它的方法在子对象上,并修改子对象,而不是那个大对象。举例,这里
animal
代表方法库,
rabbit
使用他们。

调用
rabbit.sleep()
在rabbit对象上,通过设置了
this.isSleeping


// 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)


结果图示如下:



如果我们有其他对象
bird
snake
等继承自
animal
,他们也获得animal的方法。但this在每个方法中和调用其对象一致,是运行时确定(.前面的对象),不是animal。所以当我们写数据至
this
,它实际存在在那些调用的子对象中。

结论是:方法是共享的,但对象状态不是。

总结

在 JavaScript, 所有对象有个隐藏
[[Prototype]]
属性,其值只能是其他对象或null.

我们能通过 obj.proto 访问它 (也有其他方法,后继续说明).

被[[Prototype]]引用的对象称为原型.

如果我们想对
obj
的属性或调用方法,它不存在,那么JavaScript尝试去原型中查找. Write/delete 属性直接在对象上运行, 他们不使用原型 (除非属性确实是setter访问器).

如果我们调用obj.method(), 并且方法来自原型, this仍然代表当前调用obj.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: