第06章 - 面向对象的程序设计

对象就是键值对

6.1 理解对象

可以用 new 创建一个对象

var person = new Object();

person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";

person.sayName = function(){
    alert(this.name);
};

现在更流行用字面量创建对象

var person = {
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",

    sayName: function(){
        alert(this.name);
    }
};

6.1.1 属性的特性

属性特性只供 Javascript 解释器内部使用,用户无法直接访问。有两种特性:数据特性和访问器特性

数据特性

  • [[Configurable]] - 能否通过 delete 删除或者改变成访问器特性,默认值为 True
  • [[Enumerable]] - 是否可以使用 for - in 循环,默认值为 True
  • [[Writable]] - 能否修改属性的值,默认值为 True
  • [[Value]] - 保存属性的值,默认值为 undefined

可以用 Object.defineProperty 方法来设置这几个特征

var person = {};

Object.defineProperty(person, "name", {
    writable: false, //设置为只读属性
    value: "Nicholas"
});

alert(person.name); //"Nicholas"

//写入新值,没反应
person.name = "Greg";
alert(person.name); //"Nicholas"

一个对象一旦被设置为不可配置,就不能再做任何修改

var person = {};

Object.defineProperty(person, "name", {
    configurable: false,//设置为不可配置
    value: "Nicholas"
});

alert(person.name); //"Nicholas"

//想删除该属性,但是不起作用
delete person.name;
alert(person.name); //"Nicholas"


//也无法改回成可配置,会抛出错误
Object.defineProperty(person, "name", {
    configurable: true,
    value: "Nicholas"
});

访问器特性

  • [[Configurable]] - 能否通过 delete 删除或者改变成访问器特性,默认值为 True
  • [[Enumerable]] - 是否可以使用 for - in 循环,默认值为 True
  • [[Get]] - 读取时调用的函数,默认值为 undefined
  • [[Set]] - 写入时调用的函数,默认值为 undefined

访问器特性也用 defineProperty 设置

var book = {
    _year: 2004,
    edition: 1
};

Object.defineProperty(book, "year", {
    get: function(){
        return this._year;
    },

    set: function(newValue){
        if (newValue > 2004) {
            this._year = newValue;
            this.edition += newValue - 2004;
        }
    }
});

book.year = 2005;
alert(book.edition); //2

6.1.2 定义多个属性特性

可以一下定义多个属性

var book = {};
Object.defineProperties(book, {
    _year: {
        value: 2004
    },

    edition: {
        value: 1
    },

    year: {
        get: function(){
            return this._year;
        },

        set: function(newValue){
            if (newValue > 2004) {
                this._year = newValue;
                this.edition += newValue - 2004;
            }
        }
    }
});

6.1.3 读取特性值

属性的特性也可以读取出来

var book = {};
Object.defineProperties(book, {
    _year: {
        value: 2004
    },

    edition: {
        value: 1
    },

    year: {
        get: function(){
            return this._year;
        },
        set: function(newValue){
            if (newValue > 2004) {
                this._year = newValue;
                this.edition += newValue - 2004;
            }
        }
    }
});

//读取 "_year" 的特性
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
alert(descriptor.value); //2004
alert(descriptor.configurable); //false
alert(typeof descriptor.get); //"undefined"

//读取 year 的特性
var descriptor = Object.getOwnPropertyDescriptor(book, "year");
alert(descriptor.value); //undefined
alert(descriptor.enumerable); //false
alert(typeof descriptor.get); //"function"

6.2 创建对象

6.2.1 工厂模式

工厂模式的缺点是:创建的对象无法判断类型

function createPerson(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;

    o.sayName = function(){
        alert(this.name);
    };

    return o;
}

var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

6.2.2 构造函数模式

用 new 关键字创建对象,创建的对象会被绑定到构造函数中的 this 上

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;

    this.sayName = function(){
        alert(this.name);
    };
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

用构造函数创建的对象可以检测类型

alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true

alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true

使用普通函数创建对象

构造函数就是一个普通函数,使用 new 关键字就变成了构造函数。使用普通函数也可以创建对象,但是需要绑定 this 指针

//使用构造函数
var person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); //"Nicholas"

//使用普通函数创建对象,this 被绑定到 window 上
Person("Greg", 27, "Doctor"); //adds to window
window.sayName(); //"Greg"

//使用普通函数创建对象,自己绑定 this 
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); //"Kristen"

使用构造函数的问题

正常情况下,同一类型的所有实例共享相同的方法代码。但是在 Javascript 中函数是一种对象,因此每个对象都会创建自己的成员方法实例。


function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = new Function("alert(this.name)"); //这种写法更能看清楚,每次创建 Person 对象,同时也会创建一个新的 sayName 实例
}

可以写一个全局方法,然后设置到对象内部,这样所有对象共享同样的方法,但是会破坏了封装性。

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    //使用全局方法
    this.sayName = sayName;
}

//全局方法
function sayName(){
    alert(this.name);
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

6.2.2 原型模式

当定义函数时,系统在后台自动为其创建一个原型对象。可以通过函数的 prototype 属性访问原型对象;此外,可以通过 prototype 属性向原型对象中添加方法和属性。

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function(){
    alert(this.name);
};

var person1 = new Person();
person1.sayName(); //"Nicholas"

var person2 = new Person();
person2.sayName(); //"Nicholas"

alert(person1.sayName == person2.sayName); //true

访问和检测原型对象

  • 每个原型对象都有一个 constructor 属性,它反过来指向函数
  • 每个实例都有一个 proto 属性,它也指向原型对象
  • 利用 Object.getPrototypeOf 方法也可以从实例得到原型对象,这种方法比 proto 属性的兼容性更好
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
  • 可以用 Person.prototype.isPrototypeOf 方法检测一个类型是不是某个对象的原型
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true

原型对象的工作方式

访问一个对象的属性或方法时,系统会先从对象本身查找,如果找不到再从原型对象中查找。

例如,虽然对象中不包括 constructor 属性,但是原型对象中包括 constructor 属性,因此当我们直接用对象访问这个属性时,也可以访问到他。

如果对象的属性和原型对象中的属性名字相同,则使用对象的属性。

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function(){
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.name = "Greg";
alert(person1.name); //"Greg" - 来自实例
alert(person2.name); //"Nicholas" - 来自原型

如果将实例中的同名属性删除,则又可以使用原型中的属性了

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function(){
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.name = "Greg";
alert(person1.name); //"Greg" - 来自实例
alert(person2.name); //"Nicholas" - 来自原型

//删除实例中的名字
delete person1.name;
alert(person1.name); //"Nicholas" - 来自原型

hasOwnProperty 方法

hasOwnProperty 方法可以检测一个名字是来自实例还是来自原型

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function(){
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

alert(person1.hasOwnProperty("name")); //false

person1.name = "Greg";
alert(person1.name); //"Greg" - 来自实例
alert(person1.hasOwnProperty("name")); //true

alert(person2.name); //"Nicholas" - 来自原型
alert(person2.hasOwnProperty("name")); //false

delete person1.name;
alert(person1.name); //"Nicholas" - 来自原型
alert(person1.hasOwnProperty("name")); //false

in 操作符

只要通过对象能访问到名字,in 就返回 true

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function(){
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true

person1.name = "Greg";
alert(person1.name); //"Greg" - 来自实例
alert(person1.hasOwnProperty("name")); //true
alert("name" in person1); //true

alert(person2.name); //"Nicholas" - 来自原型
alert(person2.hasOwnProperty("name")); //false
alert("name" in person2); //true

delete person1.name;
alert(person1.name); //"Nicholas" - 来自原型
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true

如果 in 操作符返回 true ,而 hasOwnProperty 方法返回 false ,则名字在原型中


//这个函数检测名字是不是在原型中
function hasPrototypeProperty(object, name){
    return !object.hasOwnProperty(name) && (name in object);
}

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function(){
    alert(this.name);
};

var person = new Person();

alert(hasPrototypeProperty(person, "name")); //true

person.name = "Greg";
alert(hasPrototypeProperty(person, "name")); //false

返回所有实例中和原型中可枚举的名字

可以用 for in 语法枚举出所有可枚举到的名字,但是 [[Enumerable]] 设置为 false 的名字枚举不出来

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function(){
    alert(this.name);
};

var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"

var p1 = new Person();
p1.name = "Rob";
p1.age = 31;

var p1keys = Object.keys(p1);
alert(p1keys); //"name,age"

仅返回所有实例中的名字

Object.keys 方法仅仅返回所有实例中的名字,并不返回原型中的名字

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function(){
    alert(this.name);
};

var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"

var p1 = new Person();
p1.name = "Rob";
p1.age = 31;

var p1keys = Object.keys(p1);
alert(p1keys); //"name,age"

返回所有的名字

getOwnPropertyNames 方法可以返回所有的名字,不管它是在实例中还是在原型中,也不管它是否可枚举

var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"

更简单的原型语法

之前的原型语法会出现很多的 prototype 属性名字,可以用字面量来重写整个原型对象

function Person(){
}

Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",

    sayName : function () {
        alert(this.name);
    }
};

这样的写法会导致一个问题,prototype 中不再有 constructor 属性了

var friend = new Person();
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true

alert(friend.constructor == Person); //false

alert(friend.constructor == Object); //true

可以手工在原型字面量中加入一个 constructor 属性

function Person(){
}

Person.prototype = {

    constructor: Person,

    name : "Nicholas",
    age : 29,
    job : "Software Engineer",

    sayName : function () {
        alert(this.name);
    }
};

这样做会导致另外一个矛盾,标准中定义的 constructor 属性是不可枚举的,上面的写法确是可枚举的。

对于现代浏览器,可以用 Object.defineProperty 方法解决这个问题

function Person(){
}

Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",

    sayName : function () {
        alert(this.name);
    }
};

//ECMAScript 5 only – 定义一个 constructor 属性,将其设置为不可枚举
Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});

原型的动态性

创建一个对象之后,也可以修改原型对象,对象可以访问到修改后的属性和方法。

var friend= new Person();

Person.prototype.sayHi = function(){
    alert("hi");
};

friend.sayHi(); //"hi" - works!

但是,如果替换掉整个原型对象对象,则之前已经创建的对象不能访问新原型对象中的属性和方法,因为对象的 [[prototype]] 仍然指向原来的原型对象

function Person(){
}

var friend = new Person();

Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",

    sayName : function () {
        alert(this.name);
    }
};

friend.sayName(); //error

原生对象的原型

原生对象的属性和方法也是在原型中定义的

alert(typeof Array.prototype.sort); //"function"
alert(typeof String.prototype.substring); //"function"

因此,可以修改原生对象的方法,或者添加新方法,但是不建议这样做

String.prototype.startsWith = function (text) {
    return this.indexOf(text) == 0;
};

var msg = "Hello world!";
alert(msg.startsWith("Hello")); //true

原型模式的缺点

因为所有对象实例都共享原型对象中的属性,如果属性是引用类型,修改任意一个对象的属性,都会对其他对象造成影响,因此很少单独使用原型模式

function Person(){
}

Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    friends : ["Shelby", "Court"],

    sayName : function () {
        alert(this.name);
    }
};

var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");

alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true

6.2.4 组合使用构造函数模式和原型模式

用构造函数来设置属性,用原型对象来设置方法,克服两种模式各自的缺点

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}

Person.prototype = {
    constructor: Person,
    sayName : function () {
        alert(this.name);
    }
};

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");

alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

6.2.5 动态原型模式

同样是用构造函数来设置属性,用原型对象来设置方法,但是设置方法的代码不是放置在全局部分,而是也放在构造函数中。

为了防止每次构造对象的时候都设置方法,这里增加了一个检测方法是否已经存在的条件语句,这样定义方法着部分代码只是在创建第一个对象的时候执行

function Person(name, age, job){

    //properties
    this.name = name;
    this.age = age;
    this.job = job;

    //methods
    if (typeof this.sayName != "function"){
        Person.prototype.sayName = function(){
            alert(this.name);
        };
    }
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

6.2.5 寄生构造函数模式

正常情况下,构造函数(即用 new 关键字调用的函数)不显式写明返回值,系统默认返回一个新对象。

可以改变这种默认情况,在构造函数内创建一个对象,然后返回,作为 new 获得的对象。

缺点是,通过这种模式获得的对象和构造函数以及构造函数的原型都没什么关系,因此 instanceof 关键字无法正常使用。

function Person(name, age, job){
    var o = new Object();

    o.name = name;
    o.age = age;
    o.job = job;

    o.sayName = function(){
        alert(this.name);
    };

    return o;
}

var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"

利用这种模式可以改写一些内置对象

function SpecialArray(){
    //create the array
    var values = new Array();

    //add the values
    values.push.apply(values, arguments);

    //assign the method
    values.toPipedString = function(){
        return this.join("|");
    };

    //return it
    return values;
}

var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); //"red|blue|green"

6.2.7 稳妥构造函数模式

寄生构造函数模式的简化安全版,构造函数中创建的对象不设置任何属性,只有方法,同时不引用 this 指针,因此更安全。缺点和寄生构造模式也一样。

function Person(name, age, job){

    //这里可以定义其他私有变量

    //创建对象
    var o = new Object();

    //添加方法
    o.sayName = function(){
        alert(name);
    };

    //返回对象
    return o;
}

6.3 继承

6.3.1 原型链

正常情况下,构造函数都有自己的原型对象,new 实例也包含一个指针指向这个原型对象,在对象中找不到属性和方法,会到原型对象中去查找。

如果将构造函数的默认原型对象,用某个手动创建的对象替换,则构造函数的类型继承了手动创建对象中的所有方法和属性。

查找属性和方法的流程如下:

  1. 先在自身实例中查找
  2. 再到原型对象(手动创建的某个对象)中查找
  3. 再到原型对象的原型对象中查找

上面这个流程即构成了原型链


//这里是父类
function SuperType(){
    this.property = true;
}

//向父类的原型对象中添加方法
SuperType.prototype.getSuperValue = function(){
    return this.property;
};

//--------------------

//这里是子类
function SubType(){
    this.subproperty = false;
}

//创建一个父类的实例替换子类的原型对象
SubType.prototype = new SuperType();

//向子类的原型对象中添加方法
SubType.prototype.getSubValue = function (){
return this.subproperty;
};

//子类的实例可以访问父类的方法
var instance = new SubType();
alert(instance.getSuperValue()); //true

需要注意的是,子类的原型对象中现在某有 constructor 这个属性,访问这个属性将从原型对象的原型对象中获得,即指向 SuperType

别忘记默认的原型

上面的原型链还少讲述了一个环节,那就是基类 Object,所有新建类型的默认原型对象就是一个 Object 对象。而 Object 也有自己的原型对象,Object 中的方法比如 toString ,都是在他自己的原型对象中定义的。一个对象如果使用 toString 方法,查找流程如下:

  1. 先在自身实例中查找
  2. 再到父类(手动创建的原型对象)中查找
  3. 再到父类的父类中查找
  4. 继续....
  5. 最后到 Object 的原型对象中查找

确定原型对象与实例的关系

instanceof 和 Object.prototype.isPrototypeOf 都可以确定原型对象与实例的关系

alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true

alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true

在子类中定义方法要小心

在子类中重写父类的方法或者定义一个新方法,给原型添加方法的代码一定要放在替换原型的语句之后,否则方法将添加到默认原型对象上去

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
};

//----------------------------------------------------

function SubType(){
    this.subproperty = false;
}

//inherit from SuperType
SubType.prototype = new SuperType();

//new method
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};

//override existing method
SubType.prototype.getSuperValue = function (){
    return false;
};

var instance = new SubType();
alert(instance.getSuperValue()); //false

另外,不要用字面量方式定义新方法,这样会创建一个新的原型对象

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
};

//---------------------------------------------

function SubType(){
    this.subproperty = false;
}

//inherit from SuperType
SubType.prototype = new SuperType();

//try to add new methods - this nullifies the previous line
SubType.prototype = {
    getSubValue : function (){
        return this.subproperty;
    },

    someOtherMethod : function (){
        return false;
    }
};

var instance = new SubType();
alert(instance.getSuperValue()); //error!

原型链的问题

和构造对象的原型模式一样,原型链也存在原型对象中包含引用类型属性的问题,如果有多个实例,其中一个实例修改了这个引用属性,会影响到其他实例

function SuperType(){
    this.colors = [“red”, “blue”, “green”];
}

function SubType(){
}

//inherit from SuperType
SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push(“black”);
alert(instance1.colors); //”red,blue,green,black”

var instance2 = new SubType();
alert(instance2.colors); //”red,blue,green,black”

6.3.2 借用构造函数

在子类构造函数中用 call 方法调用超类构造函数,这样超类中的 this 将指向子类实例,超类中在 this 上定义的属性和方法都变成了子类的属性和方法。这样解决了原型链中引用类型属性的问题

function SuperType(){
    this.colors = [“red”, “blue”, “green”];
}

function SubType(){
    //inherit from SuperType
    SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push(“black”);
alert(instance1.colors); //”red,blue,green,black”

var instance2 = new SubType();
alert(instance2.colors); //”red,blue,green”

向父类传递参数

借用构造函数还有一个好处可以向父类传递参数

function SuperType(name){
    this.name = name;
}

function SubType(){
    //inherit from SuperType passing in an argument
    SuperType.call(this, “Nicholas”);
    //instance property
    this.age = 29;
}

var instance = new SubType();
alert(instance.name); //”Nicholas”;
alert(instance.age); //29

借用构造函数的问题

借用构造函数模式创建的子类的实例,只能使用在父类中构造函数中定义的属性和方法,但是不能使用父类原型对象中的属性和方法,复用性不高

6.3.3 组合继承

结合原型链继承和借用构造函数继承两种模式的优点,即

  • 本来通过原型链模式就可以访问全部的父类属性和方法,但是会存在引用属性的缺点
  • 在此基础上,再在子类构造函数中用 call 方法调用超类构造函数,让每个子类实例独立的拥有父类在构造函数中定义的属性和方法
  • 本质:从父类构造函数继承的属性和方法也同时存在于从原型继承的属性和方法中,父类构造函数的属性和方法会覆盖原型的属性和方法
  • 缺点:存在一部分无用的属性和方法

function SuperType(name){
    this.name = name;
    this.colors = [“red”, “blue”, “green”];
}

SuperType.prototype.sayName = function(){
    alert(this.name);
};

function SubType(name, age){
    //inherit properties
    SuperType.call(this, name);
    this.age = age;
}

//inherit methods
SubType.prototype = new SuperType();

SubType.prototype.sayAge = function(){
    alert(this.age);
};

var instance1 = new SubType(“Nicholas”, 29);
instance1.colors.push(“black”);
alert(instance1.colors); //”red,blue,green,black”
instance1.sayName(); //”Nicholas”;
instance1.sayAge(); //29

var instance2 = new SubType(“Greg”, 27);
alert(instance2.colors); //”red,blue,green”
instance2.sayName(); //”Greg”;
instance2.sayAge(); //27

6.3.4 克隆式继承

方法:将一个已存在的对象作为一个空对象的原型对象,并返回这个空对象。

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

缺点:原型对象中的引用属性会被所有实例共享

var person = {
    name: “Nicholas”,
    friends: [“Shelby”, “Court”, “Van”]
};

var anotherPerson = object(person);
anotherPerson.name = “Greg”;
anotherPerson.friends.push(“Rob”);

var yetAnotherPerson = object(person);
yetAnotherPerson.name = “Linda”;
yetAnotherPerson.friends.push(“Barbie”);

alert(person.friends); //”Shelby,Court,Van,Rob,Barbie”

官方实现 Object.create 方法

var person = {
    name: “Nicholas”,
    friends: [“Shelby”, “Court”, “Van”]
};

var anotherPerson = Object.create(person);
anotherPerson.name = “Greg”;
anotherPerson.friends.push(“Rob”);

var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = “Linda”;
yetAnotherPerson.friends.push(“Barbie”);

alert(person.friends); //”Shelby,Court,Van,Rob,Barbie”

Object.create 方法有第二个参数,传入一个键值对,可以在原型的基础上添加自己的属性和方法

var person = {
    name: “Nicholas”,
    friends: [“Shelby”, “Court”, “Van”]
};

var anotherPerson = Object.create(person, {
    name: {
        value: “Greg”
    }
});

alert(anotherPerson.name); //”Greg”

有时候没有必要写一个构造函数,利用原型式继承是一个好方法

6.3.5 寄生式继承

利用别的函数来创建对象(大多属性框是克隆已有对象),然后在这个对象上添加另外一些方法和属性

function createAnother(original){
    var clone = object(original); //create a new object by calling a function

    clone.sayHi = function(){ //augment the object in some way
        alert(“hi”);
    };

    return clone; //return the object
}


//______________________________________________________

var person = {
    name: “Nicholas”,
    friends: [“Shelby”, “Court”, “Van”]
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //”hi”

6.3.6 寄生组合式继承

组合式继承的缺点前面已经说过,原型中会存在一些被覆盖的无用属性和方法

//父类中定义的属性
function SuperType(name){
    this.name = name;
    this.colors = [“red”, “blue”, “green”];
}
//父类中定义的方法
SuperType.prototype.sayName = function(){
    alert(this.name);
};

//通过 call 方法继承父类中的属性,这是第二次构造一个父类实例,会覆盖原型中的同名属性
function SubType(name, age){
    SuperType.call(this, name); 
    this.age = age;
}

//通过原型对象继承父类中的方法,这是第一次构造一个父类实例
SubType.prototype = new SuperType(); 

//在原型对象中添加子类自己的属性和方法
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
};

如果不是将子类的原型直接指向一个父类实例,而是指向父类原型对象的克隆对象,这样子类通过原型继承的仅仅是父类原型对象中的方法,而不会继承父类中的属性,克服了组合模式的缺点。


//通过这个方法处理,子类会继承父类中的方法,但是不会继承父类中的属性
function inheritPrototype(subType, superType){
    //克隆父类的原型对象
    var prototype = object(superType.prototype); //create object
    prototype.constructor = subType; //augment object

    //通过将子类的原型指向这个克隆对象,继承其中的方法,而不会继承其中的属性
    subType.prototype = prototype; //assign object
}


//-------------------------------

//定义父类
function SuperType(name){
    this.name = name;
    this.colors = [“red”, “blue”, “green”];
}

SuperType.prototype.sayName = function(){
    alert(this.name);
};

//-------------------------------

//定义子类,通过借用构造函数继承父类中的属性
function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}
//通过克隆方式继承父类中的方法
inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function(){
    alert(this.age);
};

这种模式是最常用的模式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容