JavaScript高级程序设计 第6章 面向对象程序设计


对象定义:无序属性的集合,属性可包括基本值、对象或者函数。对象的每个属性或方法都有一个名字(名值对)

6.1 理解对象

1、创建Object实例,再为其添加属性和方法
2、对象字面量

6.1.1 属性类型

数据属性和访问器属性
1、数据属性
描述数据行为的4个特性:
[[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,默认true
[[Enumrable]]:能否通过for-in循环返回属性,默认true
[[Writable]]:能否修改属性的值,默认true
[[Value]]:属性的数据值,默认undefined

Object.defineProperty(obj, propName, descriptor)
descriptor描述符对象包含Configurable、Enumrable、Writable、Value属性
【注】用该函数创建新属性时,前三个默认false;若调用该函数修改已定义的属性的特性,则无此限制
例:定义只读数据属性

var person = {};
Object.definedProperty(person, 'name', {
    writable: false, //只读
    value: 'Nicholas',
})
alert(person) //"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'
})
//Uncaught TypeError: Cannot redefine property: name
//at Function.defineProperty (<anonymous>)

2、访问器属性
访问器属性包含一对getter、setter函数(非必需)
读取访问器属性时会调用getter函数,返回有效值;写入访问器属性时会调用setter函数并传入新值
访问器属性的4个特性:
[[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,默认true
[[Enumrable]]:能否通过for-in循环返回属性,默认true
[[Get]]:读取属性时调用的函数,默认undefined
[[Set]]:写入属性时调用的函数,默认undefined
例:

var book = {
     _year: 2018,
     edition: 1
};
Object.defineProperty(book, 'year', {
     get: function() {
         return this._year
     },
     set: function(value) {
         if (value > 2018) {
             this._year = value
             this.edition += value - 2018
         }
     }
});
book.year = 2019;
alert(book.edition); //2

【注】1、book对象字面量定义中未year,后面也为this.year,则会报错(RangeError:超过堆栈最大值)
因为set属性中,this.year = value 相当于调用了一次set函数,即无限递归;
2、_year属性只能通过对象方法访问,访问器属性year包含getter、setter函数(改变了其它属性)
3、只指定了getter函数的属性无法写入,只指定setter函数的属性无法读取

非标准方法创建访问器
defineGetter(), defineSetter()
例:

var book = {
     _year: 2018,
     edition: 1
};
book.__defineGetter__('year', function() {
     return this._year
})
book.__defineSetter__('year', function(value) {
     if (value > 2018) {
         this._year = value
         this.edition += value - 2018
     }
});
book.year = 2019;
alert(book.edition); //2
6.1.2 定义多个属性

Object.defineProperties(obj, {props: descriptor})

var books = {}
Object.defineProperties(books, {
     _year: {
         writable: true,
         value: 2018
     },
     edition: {
         writable: true,
         value: 1
     },
     year: {
         get: function() {
         return this._year
     },
     set: function(value) {
         if (value > 2018) {
             this._year = value
             this.edition += value - 2018
         }
     }
})
6.1.3 读取属性的特性

Object.getOwnPropertyDescriptor(obj, prop) //return descriptor
参数是访问器属性时,descriptor包含属性:configurable、enumerable、get和set
参数是数据属性时,descriptor包含属性:configurable、enumerable、writable和value

var book = {}
Object.defineProperties(book, {
     _year: {
         writable: true,
         value: 2018
     },
     edition: {
         writable: true,
         value: 1
     },
     year: {
         get: function() {
         return this._year
     },
     set: function(value) {
         if (value > 2018) {
             this._year = value
             this.edition += value - 2018
         }
     }
})
var descriptor = Object.getOwnPropertyDescriptor(book, '_year')
alert(descriptor.value) //2018
alert(descriptor.configurable) //false
alert(typeof descriptor.get) //undefined
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() {
          console.log(this.name)
    };
    return o; //返回对象
}
var person1 = createPerson("Greg", 22, "Doctor")
person1 instanceof createPerson //false

工厂模式解决了创建多个相似对象的问题,但没有解决对象识别的问题(即怎样知道一个对象的类型)

6.2.2 构造函数模式

类似Object、Array原生构造函数,在运行时自动出现在执行环境中。
自定义构造函数可以将它的实例标示为一种特定类型(胜过工厂模式)

function Person(name, age, job) {
    this.name = name; //将属性赋给this对象
    this.age = age;
    this.job = job;
    this.sayName = function() {
          console.log(this.name)
    }
}
var person1 = new Person("Greg", 22, "Doctor")
person1 instanceof Object //true
person1 instanceof Person //true

【注】a. 构造函数应以大写字母开头,b. 创建Person实例需要使用new操作符,c. 实例对象包含constructor属性(值是该构造函数)
构造实际步骤:
(1)创建一个新对象
(2)将构造函数的作用域赋给新对象(this指向新对象)
(3)执行构造函数中代码
(4)返回新对象
将构造函数当做函数
a. 当做构造函数,使用new调用
b. 作为普通函数,直接调用。浏览器中,this为window(即给window添加属性和方法)
c. 在另一个对象的作用域中调用

var o = new Object()
Person.call(o, 'Jack', 24, 'Nurse')
o.sayName() //"Jack"

构造函数的问题
每个方法都要在每个实例上重新创建一遍,即构造出的不同对象person1、person2,其同名方法(sayName)并不是同一个Function实例

this.sayName = new Function("console.log(this.name)") //每个person实例都包含一个不同Function实例

实际并不需要在执行代码前就把函数绑定到特定对象上面。解决:将函数定义转移到构造函数外部

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, "Doctor")  
var person2 = new Person("Nicholas", 27, "Nurse") 
alert(person1.sayName == person2.sayName) //true

【注】person1和person2对象共享了在全局作用域定义的同一个 sayName() 函数;但是这样一来,自定义引用类型就失去了封装性。

6.2.3 原型模式

prototype:通过构造函数创建的那个对象实例的原型对象
每个函数都有一个prototype指针属性,指向一个包含由特定类型的所有实例共享的属性和方法
使用原型对象的好处:让所有对象实例共享它所包含的属性和方法(不必在构造函数中定义对象实例的信息)

function Person(name, age, job) {
     Person.prototype.name = 'Nicholas'; //将属性赋给this对象
     Person.prototype.age = 24;
     Person.prototype.job = 'Doctor';
     Person.prototype.sayName = function() {
         console.log(this.name)
     }
}
var person1 = new Person()
person1.sayName() //Nicholas
var person2 = new Person()
person2.sayName() //Nicholas
alert(person1.sayName == person2.sayName) //true

【注】此时构造函数变成了空函数,person1和person2访问的都是同一组属性和同一个sayName函数
1. 理解原型对象
每个函数都有一个prototype属性,它指向函数的原型对象。
默认情况下,所有原型对象都会自动获得一个constructor属性,其指向prototype属性所在函数的指针。前述Person.prototype.constructor指向Person函数。
每个构造函数创建的新实例都包含一个[[Prototype]]指针属性(浏览器中为__proto__),指向构造函数的原型对象。指针连接存在于实例与构造函数的原型对象之间(实例对象与构造函数没有直接关系)

person1
Person {}
  __proto__:
  age:24
  job:"Doctor"
  name:"Nicholas"
  sayName:ƒ ()
  constructor:ƒ Person(name, age, job)
  __proto__:Object
Person
ƒ Person(name, age, job) { Person.prototype.name = 'Nicholas'; //将属性赋给this对象 Person.prototype.age = 24;
Person.prototype.job = 'Doctor'; Person.prototype.sayName = function() { consol…

Person.prototype
{name: "Nicholas", age: 24, job: "Doctor", sayName: ƒ, constructor: ƒ}
age:24
job:"Doctor"
name:"Nicholas"
sayName:ƒ ()
constructor:ƒ Person(name, age, job)
__proto__:Object

确定实例对象与原型对象之间的关系:(通过实例对象获取[[Prototype]]指针指向的原型对象)
1、[[Prototype]]在浏览器中支持的__proto__属性
2、原型对象的 isPrototypeOf(obj) 方法(若参数的[[Prototype]]指向调用该方法的对象,则返回true)
3、Object.getPrototypeOf(obj)

Person.prototype == person1.__proto__ //true
Person.prototype.isPrototypeOf(person1) //true
Object.getPrototypeOf(person1) == Person.prototype //true

对象实例共享原型属性和方法的基本原理:
调用person1.sayName()时,会先后执行两次搜索。首先解析器搜索实例person1是否有sayName属性,结果是没有;然后搜索person1的原型是否有sayName属性,结果有;于是读取保存在原型对象中的函数。
重定义原型属性:
可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。
在实例中添加的属性与该原型中的属性同名时,该实例属性会被创建,并屏蔽原型中的同名属性。

var person1 = new Person()
var person2 = new Person()
person1.name = "Greg"
alert(person1.name) //"Greg" ——来自实例
alert(person2.name) //"Nicholas" ——来自原型

使用delete删除实例属性:

var person1 = new Person()
person1.name = "Greg"
alert(person1.name) //Greg
delete person1.name
alert(person1.name) //Nicholas

hasOwnProperty(prop)方法检测该属性是否在实例中:
便于知道访问的是实例属性还是原型属性

var person1 = new Person()
alert(person1.hasOwnProperty('name')) //false
person1.name = "Greg"
alert(person1.hasOwnProperty('name')) //true
delete person1.name
alert(person1.hasOwnProperty('name')) //false

2、原型与in操作符
in操作符的两种使用场景:单独使用、for-in循环
(1)单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中

var person1 = new Person()
alert('name' in person1) //true
person1.name = "Greg"
alert('name' in person1) //true

(2)使用for-in循环时,返回是是所有能通过对象访问的、可枚举的属性(既包括实例中的属性,也包括原型中的属性)

var person1 = new Person() //实例无属性
for (var prop in person1) {
     console.log(prop)
}
//name
//age
//job
//sayName

Object.keys(obj) 取得对象所有可枚举属性 //return [props]
参数为原型对象时,返回原型对象属性;参数为实例对象时,返回实例对象属性。

Object.keys(Person.prototype) //["name", "age", "job", "sayName"]
var person1 = new Person()
person1.name = "Rob"
person1.age = 24
Object.keys(person1) //["name", "age"]

Object.getOwnPropertyNames(obj) 获取所有实例属性(无论是否可枚举)

var person1 = new Person()
Object.getOwnPropertyNames(person1) //[]
Object.getOwnPropertyNames(Person.prototype) //["constructor", "name", "age", "job", "sayName"]

【注】constructor为不可枚举属性。Object.keys(person1), Object.getOwnPropertyNames(person1) 都可以替代 for-in 循环
3、更简单的原型语法
用一个包含所有属性和方法的对象字面量来重写整个原型对象

function Person() {}
Person.prototype = {
    name: "Nicholas",
    age: 25,
    sayName: function() {
          console.log(this.name)
    }
}
Person.prototype.constructor //ƒ Object() { [native code] }
new Person().constructor == Person //false
new Person() instanceof Person //true

【注】:constructor属性不在指向Person。当创建一个函数时,同时会创建其prototype对象,该对象自动获得constructor属性。而这里本质上是重写了默认的prototype对象,因此constructor属性指向Object构造函数。

设置恢复constructor:

function Person() {}
Person.prototype = {
     constructor: Person,
     name: "Nicholas",
     age: 25,
     sayName: function() {
         console.log(this.name)
     }
}
Person.prototype.constructor //ƒ Person() {}
new Person().constructor == Person //true
Person.prototype //{constructor: ƒ, name: "Nicholas", age: 25, sayName: ƒ}

【注】上述添加constructor属性,使得constructor的 [[Enumerable]] 特性被设置为true。默认情况下,原生的constructor属性是不可枚举的

Object.defineProperty(obj, prop, {descriptor}) 重设构造函数

function Person() {}
Person.prototype = {
     name: "Nicholas",
     age: 25,
     sayName: function() {
         console.log(this.name)
     }
}
Object.defineProperty(Person.prototype, 'consructor', {
     enumerable: false,
     value: Person
})
Person.prototype //{name: "Nicholas", age: 25, sayName: ƒ, constructor: ƒ}

4、原型的动态性
由于在原型中查找值是一次搜索,因此对原型所做的修改都可反映出来。允许先创建实例,再为原型对象添加方法,然后实例调用该方法

var person1 = new Person()
Person.prototype.sayHi = function() {
    alert('Hi')
}
person1.sayHi() //Hi

但是当重写整个原型对象时,等于切断了构造函数与最初原型之间的联系 [[Prototype]] (__proto__)
实例对象的__proto__属性引用的仍是重写前的原型

function Person() {}
var person1 = new Person()
Person.prototype = {
     constructor: Person,
     name: "Nicholas",
     age: 25,
     sayName: function() {
         console.log(this.name)
     }
}
person1.sayName() //Uncaught TypeError: person1.sayName is not a function
person1.__proto__  //{constructor: ƒ}

5、原生对象的原型
所有原生引用类型都在其构造函数上定义了方法:

typeof Array.prototype.sort //function
typeof String.prototype.substring //function

通过原生对象的原型,还可以定义新方法:
例:为String包装类型添加 startsWith() 方法

String.prototype.startsWith = function(text) {
    return this.indexOf(text) == 0
}
'Hello world'.startsWith('Hello') //true

【注】不推荐产品化的程序中修改原生对象的原型
6、原型对象的问题
原型模式的最大问题是由其共享的本性导致的:
对于包含引用类型的属性来说,实例1对原型引用类型属性的修改会同时表现在其他实例中(而无法有属于自己的属性)——所以一般很少单独使用

function Person() {}
var person1 = new Person()
Person.prototype = {
     constructor: Person,
     name: "Nicholas",
     age: 25,
     friends: ['Jack', 'Rob'],
     sayName: function() {
         console.log(this.name)
     }
}
var person1 = new Person()
var person2 = new Person()
person1.friends.push('Van')
alert(person1.friends) //Jack,Rob,Van
alert(person2.friends) //Jack,Rob,Van
6.2.4 组合使用构造函数和原型模式(广泛使用)

构造函数用于定义实例属性(支持传参),原型模式用于定义方法和共享属性

function Person(name, age, job) {
     this.name = name
     this.age = age
     this.job = job
     this.friends = ['Jack', 'Rob']
}
Person.prototype = {
     constructor: Person,
     sayName: function () {
         alert(this.name)
     }
}
var person1 = new Person('Nicholas', 25, 'Software Engineer')
var person2 = new Person('Greg', 22, 'Doctor')
person1.friends.push('Van')
alert(person1.friends) //Jack,Rob,Van
alert(person2.friends) //Jack,Rob
alert(person1.sayName === person2.sayName) //true
6.2.5 动态原型模式

把所有信息封装在构造函数中,通过在构造函数中初始化原型,检查某个应该存在的方法是否有效,来决定是否需要初始化原型方法

function Person(name, age, job) {
     //属性
     this.name = name;
     this.age = age;
     this.job = job;
     //方法
     if (typeof this.sayName != 'Function') {
         Person.prototype.sayName = function() {
         alert(this.name)
     }
}
var person1 = new Person('Greg', 28, 'Doctor')
person1.sayName() //Greg

【注】只在sayName()方法不存在情况下才会将它加到原型中。首次调用构造函数会执行if中的原型方法初始化语句,此后不需要在修改了。
使用动态原型模式时,不能使用对象字面量重写原型。(避免在已经创建了实例情况下重写原型,会切断现有实例与新原型之间的联系)

6.2.6 寄生构造函数模式

创建的函数仅仅是包装对象,然后返回新创建的对象,使用new操作符实例化(仅这一点区别于工厂模式)

function Person(name, age, job) {
     var o = new Object(); //显式创建对象
     o.name = name;
     o.age = age;
     o.job = job;
     o.sayName = function() {
         console.log(this.name)
     };
     return o; //返回对象
}
var person1 = new Person('Greg', 27, 'Doctor')
person1.sayName() //Greg

【注】返回对象与构造函数(原型)之间没有关系,函数返回的对象与在构造函数外部创建的对象没什么不同,所以不能依赖instanceof操作符确定对象类型

6.2.7 稳妥构造函数模式

稳妥对象(durable objects):没有公共属性,新创建对象的实例方法不引用this,不使用new操作符调用构造函数

function Person(name, age, job) {
     var o = new Object(); //显式创建对象
     //可定义私有变量和函数
     //公共方法
     o.sayName = function() {
         console.log(name)
     };
     return o; //返回对象
}
var person1 = Person('Greg', 27, 'Doctor')
person1.sayName() //Greg

【注】除了调用sayName()方法外,没有别的方法可以访问其数据成员,提供了安全性

6.3 继承

面向对象语言支持:接口继承(继承方法签名)、实现继承(继承实际方法)
ECMAScript只支持实现继承,依靠原型链来实现

6.3.1 原型链

原型链实现继承的思想:利用原型让一个引用类型继承另一个引用类型的属性和方法
让子类原型对象等于父类的实例,原型对象将包含一个指向另一个原型的指针;
继承:通过创建父类的实例,并将该实例赋给子类的原型实现。本质:重写子类原型对象,代之以一个父类的实例。即原来存在于父类的实例中的所有属性和方法,现在也存在于子类原型对象中了。

function SuperType() {
     this.property = true
}
SuperType.prototype.getSuperValue = function () {
     return this.property
}
function SubType() {
     this.subproperty = false
}
//继承了SuperType,子类原型包括父类实例属性和指向父类原型对象的指针
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function() {
     return this.subproperty
}
var instance = new SubType()
alert(instance.getSuperValue()) //true

SuperType.prototype
{getSuperValue: ƒ, constructor: ƒ}

SubType.prototype
SuperType {property: true, getSubValue: ƒ}
  getSubValue:ƒ ()
  property:true
  __proto__:
    getSuperValue:ƒ ()
    constructor:ƒ SuperType()
    __proto__:Object

SubType.prototype.__proto__
{getSuperValue: ƒ, constructor: ƒ}

SubType.prototype.constructor
ƒ SuperType() {
this.property = true
}

instance.__proto__
SuperType {property: true, getSubValue: ƒ}

instance.constructor
ƒ SuperType() {
this.property = true
}

【注】a. 子类原型不仅具有父类实例所拥有的全部属性和方法,还有一个指针,指向父类的原型。
b.子类实例的constructor属性等于子类原型的constructor属性。而子类原型又指向父类原型,而父类原型有constructor属性,于是子类实例的constructor属性等于父类原型的constructor属性。
1、别忘记默认的原型
所有引用类型默认都继承了Object,所有函数的默认原型都是Object的实例,因此默认原型都会有一个内部指针,指向Object.prototype。(所有自定义类型都会继承toString()、valueOf()等默认方法)完整的原型链如图:

2、确定原型和实例的关系
(1)instanceof 操作符测试实例与原型链中出现过的构造函数

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

(2)isPrototypeof() 方法,只要是原型链中出现过的原型,都可以说是该原型链所派生出的实例的原型

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

3、先替换原型再添方法
子类覆盖(重定义)父类方法或添加父类没有的方法,需要在替换原型之后进行。

function SuperType() {
     this.property = true
}
SuperType.prototype.getSuperValue = function () {
     return this.property
}
function SubType() {
     this.subproperty = false
}
//继承了SuperType
SubType.prototype = new SuperType()
//添加新方法
SubType.prototype.getSubValue = function() {
     return this.subproperty
}
//重写父类的方法
SubType.prototype.getSuperValue = function() {
     return false
}
var instance = new SubType()
alert(instance.getSuperValue()) //false

【注】在通过原型链实现继承时,不能使用对象字面量(Object实例)创建原型方法(会重写原型链)

function SuperType() {
     this.property = true
}
SuperType.prototype.getSuperValue = function () {
     return this.property
}
function SubType() {
     this.subproperty = false
}
SubType.prototype = new SuperType()
//使用对象字面量添加新方法,会导致上一行代码无效
SubType.prototype = {
    getSubType: function() {
          return this.subproperty
     }
}
var instance = new SubType()
alert(instance.getSuperValue()) //Uncaught TypeError: instance.getSuperValue is not a function

4、原型链的问题
(1)最主要问题来自包含引用类型的原型:在构造函数中定义引用类型属性,并通过原型实现继承,父类实例的引用类型属性变成了子类原型的引用类型属性。相当于为子类原型创建引用类型属性一样:SubType.prototype.colors = [xxx],所有子类实例都会共享这一引用属性。

function SuperType() {
     this.colors = ['red', 'blue', 'green']
}
function SubType() {}
//继承了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

(2)在创建子类实例时,不能向父类构造函数中传递参数(实践中很少单独使用原型链)

6.3.2 借用构造函数

借用构造函数(constructor stealing)(伪造对象或经典继承):在子类构造函数的内部使用父类构造函数。

函数是:在特定环境中执行代码的对象

方法:使用 apply() 和 call() 在(将来)新创建的对象上执行构造函数(在子类对象上执行父类构造函数中的初始化代码)

function SuperType() {
     this.colors = ['red', 'blue', 'green']
}
function SubType() {
     //继承了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

1、向父类构造函数传递参数

function SuperType(name) {
     this.name = name
}
function SubType() {
     //继承了SuperType,同时传递参数
     SuperType.call(this, 'Nicholas')
     //实例属性
     this.age = 29
}
var instance = new SubType()
console.log(instance.name, instance.age) //Nicholas 29

【注】为避免父类构造函数重写子类属性,可在调用父类构造函数后,添加子类自己的属性
2、借用构造函数的问题
方法都在构造函数中定义,函数复用无从谈起;父类原型中的方法对子类不可见。(?)

6.3.3 组合继承(combination inheritance)——最常用继承

又称伪经典继承:将原型链和借用构造函数组合到一块
思路:使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承

function SuperType(name) {
     this.name = name
     this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function() {
     console.log(this.name)
}
function SubType(name, age) {
     //继承属性
     SuperType.call(this, name)
     //实例属性
     this.age = age
}
//继承方法
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType //重写子类构造函数以便实例化传参
SubType.prototype.sayAge = function() {
     console.log(this.age)
}

var instance1 = new SubType('Nicholas', 29)
instance1.colors.push('black')
console.log(instance1.colors) //["red", "blue", "green", "black"]
instance1.sayName() //Nicholas
instance1.sayAge() //29

var instance2 = new SubType('Greg', 27)
console.log(instance2.colors) //["red", "blue", "green"]
instance2.sayName() //Greg
instance2.sayAge() //27

【注】组合继承最大问题:必须调用两次父类构造函数,一次是创建子类原型时,另一次是在子类构造函数内部。
即子类最终包含父类对象的全部实例属性,我们不得不在调用子类构造函数时重写这些属性。

instance1
SubType {name: "Nicholas", colors: Array(4), age: 29}
  age:29
  colors:(4) ["red", "blue", "green", "black"]
  name:"Nicholas"
  __proto__:SuperType
    colors:(3) ["red", "blue", "green"]
    constructor:ƒ SubType(name, age)
    name:undefined
    sayAge:ƒ ()
    __proto__:Object
6.3.4 原型式继承

借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型

function object(o) {
    function F() {} //临时构造函数
     F.prototype = o //将传入对象作为此构造函数的原型
    return new F() //返回临时构造函数新实例
}

本质:object() 对传入其中的对象执行一次浅复制
原型式继承要求必须有一个对象可以作为新对象的基础(原型),原型属性会被其他新对象所共享(相当于创建了初始对象的副本)

function object(o) {
function F() {} //临时构造函数
F.prototype = o //将传入对象作为此构造函数的原型
     return new F() //返回临时构造函数新实例
}
var person = {
     name: 'Nicholas',
     friends: ['Shelby', 'Court', 'Van']
}
var person1 = object(person)
person1.name = 'Greg'
person1.friends.push('Rob')
var person2 = object(person)
person2.name = 'Linda'
person2.friends.push('Jack')
alert(person.friends) //Shelby,Court,Van,Rob,Jack

ECMAScript 5 新增了Object.create(protoObj, [propObj]) 方法规范了原型式继承
方法接收两参数:用作新对象原型的对象、为新对象定义额外属性的对象(可选)
传入一个参数时,Object.create(protoObj) 等于 Object(protoObj)

var person = {
     name: 'Nicholas',
     friends: ['Shelby', 'Court', 'Van']
}
var person1 = Object.create(person)
person1.name = 'Greg'
person1.friends.push('Rob')
var person2 = Object(person)
person2.name = 'Linda'
person2.friends.push('Jack')
alert(person.friends)  //Shelby,Court,Van,Rob,Jack

Object.create() 方法的第二个参数与Object.defineProperties() 方法的第二个参数格式相同:每个属性都通过自己的描述符定义
(会覆盖原型对象上的同名属性)

var person = {
     name: 'Nicholas',
     friends: ['Shelby', 'Court', 'Van']
}
var person1 = Object.create(person, {
     name: {
         value: 'Greg'
     }
})
alert(person1.name) //Greg
6.3.5 寄生式继承

寄生式继承与原型式继承紧密相关,与寄生构造函数和工厂模式相似:即创建一个仅用于封装继承过程的函数,函数内部增强对象,最后返回对象

function object(o) {
     function F() {} //临时构造函数
     F.prototype = o //将传入对象作为此构造函数的原型
     return new F() //返回临时构造函数新实例
}
function createAnother(original) {
     var clone = object(original) //调用构造函数创建新对象
     clone.sayHi = function() { //增强对象
         alert('hi')
     }
     return clone
}
var person = {
     name: 'Nicholas',
     friends: ['Shelby', 'Court', 'Van']
}
var person1 = createAnother(person)
person1.sayHi() //hi

【注】使用寄生式继承为对象添加函数,不能做到函数复用,与构造函数模式类似

6.3.6 寄生组合式继承(最理想的继承范式)

组合继承最大问题:必须调用两次父类构造函数,一次是创建子类原型时(子类原型得到两属性),另一次是在子类构造函数内部(在新对象上创建两实例属性)。新对象创建的实例属性屏蔽了原型中的两同名属性。

instance1
SubType {name: "Nicholas", colors: Array(4), age: 29}
  age:29
  colors:(4) ["red", "blue", "green", "black"]
  name:"Nicholas"
  __proto__:SuperType
    colors:(3) ["red", "blue", "green"]
    constructor:ƒ SubType(name, age)
    name:undefined
    sayAge:ƒ ()
    __proto__:Object

寄生式组合继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
思路:不必为了指定子类的原型而调用父类的构造函数(避免了父类构造实例属性)。本质上,就是使用寄生式继承父类原型,再将结果指定给子类原型

function object(o) {
     function F() {} //临时构造函数
     F.prototype = o //将传入的原型对象作为此构造函数的原型
     return new F() //返回临时构造函数新实例
}
function inheritPrototype(subType, superType) {
    var prototype = object(superType.prototype); //创建子类原型对象,其原型为父类原型
    prototype.constructor = subType; //增强原型对象
    subType.prototype = prototype; //指定原型对象
}

改写前面组合继承的例子:

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)
}
var instance1 = new SubType('Jack', 33)
instance1
SubType {name: "Jack", colors: Array(3), age: 33}
  age:33
  colors:(3) ["red", "blue", "green"]
  name:"Jack"
  __proto__:SuperType
    constructor:ƒ SubType(name, age)
    sayAge:ƒ ()
    __proto__:
      sayName:ƒ ()
      constructor:ƒ SuperType(name)
      __proto__:Object

6.4 小结

创建对象的模式

  • 工厂模式:使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。被构造函数模式所取代。
  • 构造函数模式:可以使用new操作符,但其成员无法复用(包括函数)
  • 原型模式:使用构造函数的prototype属性来指定哪些应该共享的属性和方法。组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,使用原型定义共享的属性和方法。

继承

  • 原型链继承:通过将一个父类实例赋给子类原型实现,子类就能访问父类的所有属性和方法。
    原型链的问题:对象实例共享所有继承的属性和方法(不宜单独使用)
    解决原型链问题:借用构造函数。即在子类构造函数内部调用父类构造函数(call),每个实例都具有自己的属性,只使用构造函数模式来定义类型。组合继承用的最多,其使用原型链继承共享的属性和方法,通过借用构造函数继承实例属性。
  • 原型式继承:在不必预先定义构造函数情况下实现继承,本质是执行对给定对象的浅复制Object.create()。而复制得到的副本可再改造。
  • 寄生式继承:与原型式继承相似,结合了原型式继承和工厂模式,但对象函数无法复用。
  • 寄生组合式继承:集寄生式继承和组合继承的优点,最有效。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值