《JavaScript高级程序设计》读书笔记-第六章(面向对象的

2019-06-24  本文已影响0人  极奏

理解对象

属性类型

有两种类型:数据属性和访问器属性

例如

var person = {
  name: 'Nicholas'
}

这个对象中[[Configurable]]、[[Enumerable]]、[[Writable]]都设置成true,[[Value]]设置为指定的值

要修改属性默认的值,则要用ES5的Objcet.defineProperty()方法
这个方法接受三个参数:属性所在的对象属性的名字和一个描述符对象,可以修改对应的特征值

var person = {}
Object.defineProperty(person,'name',{
  writable: false,
  value: 'Nicholes'
})

这个例子创建了一个name属性,他的值是只读,不可修改的

类似的设置configurable:false,表示不能从对象中删除属性
一旦把属性定义成不可配置的,就不能再把它变回可配置的了
此时,再调用Object.defineProperty修改除writable之外的属性,就会导致错误

访问器属性不能直接定义,必须使用Object.defineProperty()来定义

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

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

book.year = 2005
console.log(book.edition)    //2

支持Object.defineProperty()这个方法只有IE9+,Firefox4+,Safari5+,Opera12+和Chrome

有两个遗留的方法,同样也能实现上面的功能
__defineGetter__()__defineSetter__()
这两个方法最初是由Firefox引进的,后来Safari3、Chrome 1 和Opera9.5也给出了相同的实现

var book = {
  _year: 2004,
  edition: 1
}
book.__defineGetter__('year'){
    return this._year
}

book.__defineSetter__('year',function(newValue){
    if(this._year > 2004){
      this._year = newValue
      this.edition += newValue - 2004
    }
})

book.year = 2005
console.log(book.edition)    //2
定义多个属性

ES5Object.defineProperties()
接收两个参数,第一个对象是添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应。

用法:

var book = {}
Object.defineProperty(book,{
  _year: { 
    value: 2014
  },
  edition: {
    value: 1
  },
  year: {
    get: function(){
      return this._year
    },
    set: function(newValue){
      if(this._year > 2004){
        this._year = newValue
        this.edition += newValue - 2004
      }
    }
  }
})
读取属性的特性

ES5的Object.getOwnPropertyDescriptor()方法

创建对象

工厂模式
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 people1 = createPerson('Nicholas', 29, 'Software Engineer')
var people2 = createPerson('Greg', 27, 'Doctor')

工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题,即怎样知道一个对象的类型,随着JavaScript的发展,又一个新模式出现了

构造函数模式
function Person(name, age, job){
  this.name = name
  this.age = age
  this.job = job
  this.sayName = function(){
    console.log(this.name)
  }
}
var person1 = new Person('Nicholas', 29, 'Software Engineer')
var person2 = new Person('Greg', 27, 'Doctor')

要创建Person的新实例,必须用new操作符,以这种方式调用构造函数的对象会经历四个步骤
1、创建一个新对象
2、将构造函数的作用域赋给新对象( 因此this就指向了这个新对象 )
3、执行构造函数中的代码(为这个新对象添加属性)
4、返回新对象

person1和person2分别保存着Person的一个不同的实例,这两个对象都有一个constructor(构造函数)属性,这个属性指向Person

console.log(person1.constructor === Person)  //true
console.log(person2.constructor === Person)  //true

检测对象类型,instanceof操作符更为可靠

console.log(person1 instanceof Object)  //true
console.log(person1 instanceof Person)  //true
console.log(person2 instanceof Object)  //true
console.log(person2 instanceof Person)  //true
//当做构造函数来使用
var person1 = new Person('Nicholas', 29, 'Software Engineer')
person.sayName()

//当做普通函数来使用
Person('Greg',27,'Doctor')      //添加到window
window.sayName()               //'Greg'

//在另一个对象的作用域中调用
var o = new Object()
Person.call(o,'Nicholas', 29, 'Software Engineer')
o.sayName()                     //'Nicholas'

缺点:每个方法在每个实例上要重新创建一遍
从逻辑角度上来讲,此时的构造函数也可以这样定义:

function Person(name, age, job){
  this.name = name
  this.age = age
  this.job = job
  this.sayName = new Function(console.log(this.name))      //与声明函数等价
}

以这种方式创建函数,会导致不同的作用域链和标识符解析,但创建Function新实例的机制仍然是相同的。因此,不同实例上的同名函数是不同的。

这样创建没必要,况且有this对象在,可以将函数定义转移到构造函数之外,大可这么创建

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

function sayName() {
  console.log(this.name)
}

这样做确实是解决了两个函数做同一件事的问题。可是新问题又来了:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实,如果对象需要很多方法,那就要定义很多个全局函数,这样我们定义的引用类型就丝毫没有封装性可言

原型模式
function Person() {
}
Person.prototype.name = 'Nicholas'
Person.prototype.age = 29
Person.prototype.job = 'Software Engineer'
Person.prototype.sayName = function() {
  console.log(this.name)
}
var person1 = new Person()
person1.sayNmae()                                        // 'Nicholas'
var person2 = new Person()
person2.sayName()                                        // 'Nicholas'

console.log(person1.sayNmae() === person2.sayName())     //true
function Person() {

}
Person.prototype.name = 'Nicholas'
Person.prototype.age = 29
Person.prototype.job = 'Software Engineer'
Person.prototype.sayName = function() {
  console.log(this.name)
}
var person1 = new Person()
var person2 = new Person()

preson1.name = 'Greg'
console.log(person1.name)          //'Greg'
console.log(perison2.name)         //'Nicholas'

此时person1.name是一个实例属性

in
Object.keys()

要取得对象上所有可枚举的属性,Object.keys()方法,返回一个包含所有可枚举属性的字符串数组

Object.getOwnPropertyName()

取得对象上的所有实例属性

原型模式的另一种写法
function Person(){
}

Person.prototype = {
  name: 'Nicholas',
  age: 28,
  job: 'Software Engineer',
  sayName: function(){
    console.log(this.name)
  }
}
function Person(){
}

Person.prototype = {
  constructor: Person,
  name: 'Nicholas',
  age: 28,
  job: 'Software Engineer',
  sayName: function(){
    console.log(this.name)
  }
}

这种方式会让constructor的[[Enumerable]]特性设置为true
有另外一种方式,能还原constructor,用Object.defineProperty()

function Person(){
}

Person.prototype = {
  name: 'Nicholas',
  age: 28,
  job: 'Software Engineer',
  sayName: function(){
    console.log(this.name)
  }
}
Object.defineProperty(Person.prototype, 'constructor', {
  enumerable: false,
  value: Person
})
原型对象的问题
function Person(){
}

Person.prototype = {
  name: 'Nicholas',
  age: 28,
  job: 'Software Engineer',
  friends: ['Shelby', 'Court']
  sayName: function(){
    console.log(this.name)
  }
}

Object.defineProperty(Person.prototype, 'constructor', {
  enumerable: false,
  value: Person
})

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

person1.friends.push('Van')

console.log(person1.friends)                        //'Shelby', 'Court','Van'
console.log(person2.friends)                        //'Shelby', 'Court','Van'

console.log(person1.friends === person1.friends)    //true
组合使用原型模式和构造函数
function Person(name, age, job){
  this.name = name
  this.age = age
  this.job = job
  this.friends = ['Shelby','Court']
}

Person.propotype = {
  sayName: function(){
    console.log(this.name)
  }
}

Object.defineProperty(Person.prototype, 'constructor', {
  enumerable: false,
  value: Person
})

动态原型模式

可以通过检查某个应该存在的方法

function Person(name, age, job) {

  this.name = name
  this.age = age
  this.job = job

  if(typeof this.sayName != 'function') {
    Person.propotype.sayName = function() {
      console.log(this.name)
    }
  }
}
寄生构造函数模式

这个模式可以在特殊的情况下用来为对象创建构造函数。假设我们想创建一个具有额外方法的数组。

function SpecialArray() {
  var values = new Array()
  values.push.apply(values, arguments)
  values.toPipedString = function(){
    return this.join('|')
  }
  return values
}
var colors = SpecialArray('red','green','blue')
console.log(colors.toPipedString())                      //'red|blue|green'

跟工厂模式的缺陷一样,不能依赖instanceof操作符来确定对象类型,所以不建议使用这种模式

稳妥构造函数

所谓稳妥对象,指的是没有公共属性,而其方法也不引用this的对象

function Person(name, age, job) {
  var o = new Object()
  o.sayname = function(){
    console.log(name)
  }
  return o
}
var friend = Person('Nicholas', 29, 'Softwate Engineer')
friend.sayName() 
继承

ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链

原型链
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()
console.log(instance.getSuperValue())  //true
console.log(instance.getSubValue())   //fasle

此时打印instance会得到



书上的图

在上面的代码中,我们没有使用SubType默认提供的原型,而是给它换了一个新原型,这个新原型就是SuperType的实例。新原型不仅具有作为一个SuperType的实例所拥有的全部属性和方法,而且其内部还有一个指针,指向了SuperType的原型

image.png

给原型添加方法的代码一定要放在替换原型的语句之后

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
}
//重写SuperType中的方法
SubType.prototype.getSuperValue = function() {
  return false
}
var instance =  new SubType()
console.log(instance.getSuperValue())  //false
console.log(instance.getSubValue())   //fasle

这样做会重写原型链

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
  },
  someOtherMethod: function(){
    return false
  }
}

var instance =  new SubType()
console.log(instance.getSuperValue())  //undefined
原型链的问题

包含引用类型的原型属性会被所有的实例共享

function SuperType(){
  this.colors = ['red', 'blue']
}

function SubType(){
}

SubType.prototype = new SuperType()

var instance1 = new SubType()
var instance2 = new SubType()

instance1.colors.push('black')

console.log(instance1.colors)        //["red", "blue", "black"]
console.log(instance2.colors)        //["red", "blue", "black"]
借用构造函数

也被称为伪造对象或经典继承。
这种技术的基本思想相当简单,即在子类型构造函数的内部调用父类型的构造函数

function SuperType(){
  this.colors = ['red', 'blue', 'green']
}
function SubType(){
  //继承了SuperType
  SuperType.call(this)
}
var instance1 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors)           //["red", "blue", "green", "black"]

var instance2 = new SubType()
console.log(instance2.colors)          //["red", "blue", "green"]

通过调用call()方法或者apply()方法,我们实际上是在(未来将要)新创建的SubType实例的环境下调用了SuperType构造函数。这样一来,就会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码。

组合继承

也叫做伪经典继承,思路是使用原型链实现了对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承

function SuperType(name) {
  this.name = name
  this.colors = ['red','blue','yellow']
}

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.sayAge = function() {
  console.log(this.age)
}

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

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

以上是比较常用的继承方式

原型式继承

这种继承模式封装了原型链继承,是原型链继承的升级版,原型链继承+工厂模式
首先我们得创建一个object函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。

缺点:会调用两次父类构造函数,第一次是创建子类型原型的时候

也就是ES5的Object.creat()简陋版的原理

const object = function(o){
  function F(){}
  F.prototype = o
  return new F()
}
const person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
}
const anotherPerson = object(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')

const yetAnotherPerson = object(person)
yetAnotherPerson.name = 'Linda'
yetAnotherPerson.friends.push('Barbie')

console.log(person.friends)
寄生式继承

增强原型式继承的功能的一种模式
缺点:使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。

const createAnother = function(original){
  const clone = object(original)
  clone.sayHi = function(){
    console.log('hi')
  }
  return clone
}
const person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
}
const anotherPerson = createAnother(person)
anotherPerson.sayHi() //hi
寄生组合式继承

所谓寄生组合式继承,就是借用构造函数来继承属性

const object = function(o){
  function F(){}
  F.prototype = o
  return new F()
}
const inheritPrototype = function(Child, Parent){
  const prototype = object(Parent.prototype)
  prototype.constructor = Child
  Child.prototype = prototype
  Child.__proto__ = Parent
}
function Parent(name) {
  this.name = name
}

Parent.sayHello = function (){
    console.log('hello')
}

Parent.prototype.sayName = function() {
    console.log('my name is ' + this.name)
    return this.name
}


function Child(name, age) {
    Parent.call(this, name)
    this.age = age
}

inheritPrototype(Child, Parent)

Child.prototype.sayAge = function () {
    console.log('my age is ' + this.age)
    return this.age
}
上一篇下一篇

猜你喜欢

热点阅读