原型链

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的方法?(继承覆写)

完整的原型图

results matching ""

    No results matching ""