笔记:《JavaScript面向对象精要》-第5章

五、继承
5.1原型对象链和Object.prototype
原型对象链:对象继承其原型对象,而原型对象继承他的原型对象,以此类推。
原型对象的属性可经由对象实例访问。
所有对象都继承自Object.prototype。任何以对象字面形式定义的对象,其[[Prototype]]的值都被设为Object.prototype。
5.1.1 继承自Object.prototype的方法
hasOwnProperty() 检查是否存在一个给定名字的自有属性

propertyIsEnumerable() 检查一个自有属性是否可枚举

isPrototypeOf() 检查一个对象是否是另一个对象的原型对象

valueOf() 返回一个对象的值表达
valueof()默认返回对象实例本身。原始封装类型重写了valueOf() 以使得它对String返回字符串,对Boolean返回一个布尔值,对Number返回一个数字。

toString() 返回一个对象的字符串表达
一旦valueOf返回的是一个引用而不是原始值的时候,就会回退调用toString() 方法。
//使用加号时,一边是字符串,另一边会转换成字符串
1+"2";    //12

//如果另外一边是原始值,会自动转换成字符串表达式
true+"2";   //true2
true+2;     //3


//如果另一边是引用类型,则会调用valueOf();如果valueOf()返回一个引用值,则调用toString()
//以下例子,book先调用了valueOf(),然后回退调用了toString();array只调用了valueOf()
var book = {
    title:"Hello world"
};
var array = [2,3,5];

book+"2";   // [object Object]2
array+"2";    //2,3,52
array+2;    //2,3,52

book.valueOf();    // {title: "Hello world"}
book.toString();    // [object Object]
  因为book是一个对象,其方法继承自Object.prototype,大部分JavaScript引擎返回默认值"[object Object]"。我们可以定义自己的toString()方法,来为此类字符串转换提供包含更多信息的值。
var book = {
    title: "Hello world",
    toString: function(){
        return "[Book: " +this.title + "]";
    }
};

var person = {
    name:"liang"
};

book+"2";        //[Book: Hello world]2
person+"2";        // [object Object]2

5.1.2 修改Object.prototype
由于所有对象都默认继承自Object.prototype,所以修改这个属性将影响到所有的对象。
而且,会给Object.prototype添加可枚举属性,随着for-in循环的频繁使用,影响到大量代码。
因此,最好不要修改或添加Object.prototype。
Object.prototype.add = function(value){
    return this + value;
};

var person = {
    name:"liang"
};

console.log(person.add(" cool"));    //[object Object] cool
console.log(document.add(true));    //[object HTMLDocument]true
console.log(window.add(5));    //[object Window]5
如果有添加比较多的Object.prototype属性,在for-in中应该使用hasOwnProperty()排除掉这些继承来的属性。
Object.prototype.add = function(value){
    return this + value;
};

var person = {
    name:"liang"
};

for(var property in person){
    if (person.hasOwnProperty(property)){
        console.log(property);
    }
}        //name

5.2 对象继承
对象字面形式会隐式指定Object.prototype为其[[Prototype]]。
可以用Object.create()方法显式指定要继承哪个原型对象。
Object.create() 方法接受两个参数:一个需要被设置为新对象的[[Prototype]]、一个属性描述对象(可选)。
var book = {
    title: "Hello world"
};

//上下两例效果相同

var book = Object.create(Object.prototype,{
    title: {
        configurable: true,
        enumerable: true,
        value: "Hello world",
        writable: true
    }
});

让一个对象继承另一个对象的属性。
var person1 = {
    name: "liang",
    sayName: function(){
        console.log(this.name);
    }
};

var person2 = Object.create(person1,{
    name:{
        configurable: true,
        enumerable: true,
        value: "zhu",
        writable: true
    }
});

person1.sayName();    //liang
person2.sayName();    //zhu

console.log(person1.isPrototypeOf(person2));    //true
console.log(person2.hasOwnProperty("sayName"));    //false
sayName() 依然只存在于person1并被person2继承。
person2继承自person1,person1继承自Object.prototype。
当访问一个对象的属性时,JavaScript引擎会执行一个搜索过程,先搜索对象实例(如person2),然后继续搜索[[Prototype]],直到继承链末端。末端的[[Prototype]]被置为null。

可创建[[Prototype]]为null的对象,创建出一个没有原型对象的对象。
var obj1= Object.create(null);
var obj2 = {};

console.log("toString" in obj1);    //false
console.log("toString" in obj2);    //true
obj1此时是一个没有任何预定义属性的白板,成为了一个完美的哈希容器,因为不会跟继承来的属性名字起冲突。

5.3 构造函数继承
对象继承是构造函数继承的基础。
几乎所有函数都有prototype属性,可以被修改。
泛用对象的prototype属性被自动设置为继承自Object.prototype,该对象有一个自有属性constructor。
可以改写prototype属性来改变原型对象链。
//创建长方形的构造函数,并添加、修改protype属性
function Rectangle(length,width){
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function(){
    return this.length * this.width;
};

Rectangle.prototype.toString = function(){
    return "[Rectangle " + this.length + "x" + this.width + "]";
};

//创建正方形的构造函数,并使之继承长方形的prototype属性
function Square(size){
    this.length = size;
    this.width = size;
}


Square.prototype = new Rectangle();  //此处将Square.prototype改写为一个Rectangle对象

//因为Square继承了Rectangle的属性constructor,添加下一行代码来恢复原值                                                                    
Square.prototype.constructor = Square;                                                                                                

var rect = new Rectangle(5,10);    
var squa = new Square(5);    

console.log(rect.getArea());    //50
console.log(squa.getArea());    //25

console.log(rect.toString());    // [Rectangle 5x10]
console.log(squa.toString());    // [Rectangle 5x5]

//Square继承来的toString要重新设定,使它符合需要;也可以用call()或apply()方法,具体见5.5访问父类方法
Square.prototype.toString = function(){
    return "[Square " + this.length + "x" + this.width + "]";
}

console.log( squa.toString());    //[Square 5x5]

//最后,检测继承的原型 注:instanceof写成instanceOf,会报错" missing ) after argument list"
console.log(rect instanceof Rectangle);    //true
console.log(rect instanceof Object);    //true

console.log(squa instanceof Square);    //true
console.log(squa instanceof Rectangle);    //true
console.log(squa instanceof Object);    //true
Square.prototype并不真的需要改写为一个Rectangle对象,要的只是把Square.prototype指向Rectangle.prototype。
因此,可以用Object.create()改写上述例子。
//...Rectangle的构造函数...
function Square(size){
    this.length = size;
    this.width = size;
}

//下面让Square.prototype继承自Rectangle.prototype。
Square.prototype = Object.create(Rectangle.prototype,{
    constructor:{
        configurable: true,
        enumerable: true,
        value: Square,
        writable: true
    }
});

Square.ptototype.toString = function(){
    return "[Square " + this.length + "x" + this.width + "]";
};

5.4 构造函数窃取
构造函数窃取就是用自己的对象窃取父类的构造函数。只需要在子类的构造函数中用call() 或者apply() 调用父类的构造函数,并将新创建的对象传进去即可。
function Rectangle(length,width){
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function(){
    return this.length * this.width;
};

Rectangle.prototype.toString = function(){
    return "[Rectangle " + this.length + "x" + this.width + "]";
};

//让Square窃取Rectangle的构造函数
function Square(size){
    Rectangle.call(this,size,size);     //传入两次size,一次作为lenth,一次作为width   
     //此处还可添加新的属性或覆盖已有的属性
}

Square.prototype = Object.create(Rectangle.prototype,{
    constructor:{
        configurable: true,
        enumerable: true,
        value: Square,
        writable: true
        }
});

Square.prototype.toString = function(){
    return "[Square " + this.length + "x" + this.width + "]";
};

var squa = new Square(6);

console.log(squa.length);    //6
console.log(squa.width);    //6
console.log(squa.getArea());    //36
你经常需要修改一个构造函数的原型对象,你也经常需要在子类的构造函数中调用父类的构造函数。
一般来说,需要修改prototype来继承方法并用构造函数窃取来设置属性。这种做法模仿了那些基于类的语言的类继承,所以被称为 伪类继承

伪类继承的方法:使用call()继承构造函数,使用create()继承其prototype原型属性并修改。

5.5 访问父类方法
5.3与5.4的代码中,通过设置toString() 方法隐藏了其原型对象的toString() 方法。是隐藏不是覆盖,因为在delete后,还能调用其原型对象的toString() 方法。
下面例子,通过call() 调用父类的方法,并替换要修改的值。这是唯一访问父类方法的手段。
//5.3与5.4中的代码
Square.prototype.toString = function(){
    return "[Square " + this.length + "x" + this.width + "]";
};

//可以替换为

Square.prototype.toString = function(){
    var text = Rectangle.prototype.toString.call(this);
    return text.replace("Rectangle","Square");
};

第五章总结:
JavaScript通过原型对象链支持继承。当将一个对象的[[Prototype]]设置为另一个对象时,就在这两个对象之间创建了一条原型对象链。

所有泛用对象都继承自Object.prototype。如要创建一个继承自其他对象的对象,使用Object.create()指定[[Prototype]]为一个新对象。

直接创建一条原型对象链在继承其他对象的方法时工作得十分好,但你不能用原型对象继承自有属性。为了正确继承自有属性,可以使用构造函数窃取。结合构造函数窃取和原型对象链,这种组合称为伪类继承,是JavaScript中最常见的继承手段


猜你喜欢

转载自blog.csdn.net/kjhz_liang/article/details/80447962