原型链
JavaScript 不是传统的类继承模型,而是使用 prototype 原型模型。
其实基于原型的继承模型比传统的类继承还要强大。实现传统的类继承模型是很简单,但是实现 JavaScript 中的原型继承则要困难的多。
由于 JavaScript 是唯一一个被广泛使用的基于原型继承的语言,所以理解两种继承模式的差异是需要一定时间的,今天我们就来了解一下原型和原型链。
__proto__
属性
理解原型链前,我们首先需要理解__proto__
属性。
var obj={};
console.log(obj);
obj.__proto__.a=10;
console.log(obj.a); // 1
如上代码中,我们并没有给obj.a,但是console.log(obj.a)
却能打印出1
。
为了解释这个现象,需要引入js查找对象属性查找的机制,我们把上面的代码稍作修改
var obj={b:2};
console.log(obj);
obj.__proto__.a=1;
console.log(obj.a); // 1
console.log(obj.b); // 2
console.log(obj.hasOwnProperty("a")) // false
console.log(obj.hasOwnProperty("b")) // true
for(var i in obj) {
console.log(i); // a,b
}
obj.a=3;
console.log(obj.a) // 3
console.log(obj.__proto__.a) // 1
console.log(obj.__proto__.null) // 1
js属性查找首先查找自身属性,如果没有,是顺着__proto__
不断向上查找,直到查找到null
为止
下面是实现的仿真代码
findProperty(obj,property){
if(obj.hasOwnProperty(property)){
return obj[property];
}
var __proto__ = obj.__proto__;
while(__proto__){
if(__proto__.hasOwnProperty(property)){
return __proto__[property]
}
__proto__ = __proto__.__proto__;
}
return undefined
}
prototype
属性
我们可以通过prototype
实现,定义对象的成员函数。
function Book(name,writer,price){
this.name=name;
this.writer=writer;
this.price=price;
}
// 设置 Book 的 prototype 属性
Book.prototype.getRMB = function(){
return this.price+"元";
}
var book = new Book("book1","Tom","59.9")
var book2 = new Book("book2","jack","29.9")
// 调用成员方法
book.getRMB()
console.log(book.getRMB == book2.getRMB) // true, 所有对象共用一个 getRMB
如上代码之所以能成功运行,源于js在new
做了一些魔法
我们还是以上面的Book类为例,展示new
不为人知的一些事
function Book(name,writer,price){
this.name=name;
this.writer=writer;
this.price=price;
return this // 不return和return this 在使用 new 的时候行为一致
}
// 设置 Book 的 prototype 属性
Book.prototype.getRMB = function(){
return this.price+"元";
}
// var book = new Book("book1","Tom","59.9") 干的事
var book = Book.call(
{}, // 新建一个对象
"book1", // 传入参数
"Tom",
"59.9"
)
// 最关键一步,改变__proto__指向
book.__proto__ = Book.prototype;
由于__proto__
指向改变,当我们调用getRMB
的时候,会向上查找到__proto__.getRMB
完成调用。
原型链实现继承
function Goods(name){
this.name=name;
}
Goods.prototype.getName = function(){
return "商品名字:"+this.name;
}
Goods.prototype.getRMB = function(){
return "商品价格:"+this.price+"元";
}
function Book(name,writer,price){
// 调用父类构造函数
Goods.call(this,name);
this.name=name;
this.writer=writer;
this.price=price;
}
// 实现继承
Book.prototype.__proto__ = Goods.prototype
// 或者不想操作 __proto__ 属性,使用ES5的 Object.create
// Book.prototype = Object.create(Goods.prototype)
// 或者这样也行
// Book.prototype = new Goods()
// 思考这样做的不完美地方
// 1. 属性有多份
// 2. 无法给Goods传参数,因为参数只有在调用时才知道
// 设置 Book 的 prototype 属性
Book.prototype.getRMB = function(){
return "图书价格:"+this.price+"元";
}
var book = new Book("book1","Tom","59.9");
console.log(book.getName()) // 商品名字:book1
console.log(book.getRMB()) // 图书价格:59.9元
思考为什么 book.getRMB()
调用的是Book
的方法,而非Goods
的方法?(继承覆写)