读书笔记--对象、实例、原型、继承

创建对象的设计模式

  • 工厂模式
    • 抽象了创建具体对象的过程,用函数封装以特定接口创建对象的细节
    • 解决了创建多个相似对象的问题,没有解决对象识别的问题
function createPerson(name,age){
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.sayName = function(){
    alert(this.name)
  }

return obj;
}

var person = createPerson('aa',12);
  • 构造函数模式
    • 构造函数可以用来创建特定类型的对象,可以用instanceof检测类型
    • 使用new操作符会经历以下四个步骤
      • 创建一个新对象
      • 将构造函数的作用域赋给新对象(因此this指向了这个新对象)
      • 执行构造函数中的代码(为这个新对象添加属性)
      • 返回新对象
    • 缺点:
      • 每个方法都要在每个实例上重新创建一遍
      • 每个方法(函数)就是一个对象,不同实例上的同名函数是不相等的
  • 将函数抽离到外部使多个对象共共享全局作用域的函数虽然可以解决上面问题,但让this.sayName = sayName全局方法会破坏了对象的封装性
function Person(name,age){
  this.name = name;
  this.age = age;
  this.sayName = function(){
    alert(this.name)
  }
}

var person = new Person('aa',12);

person instanceof Person => true

原型模式

  • prototype
  • 只要创建了一个新函数,就会为函数创建一个prototype属性,这个属性指向函数的原型对象
  • 包含了特定类型的所有对象实例共享的方法和属性
  • prototype是通过调用构造函数而创建的那个对象实例的原型对象
  • 默认情况下所有原型对象会获得一个constructor(构造函数属性),这个属性指向prototype属性所在函数的指针
  • 当调用构造函数创建一个新实例时,内部将包含一个指针,指向原型对象,连接实例与原型对象
function Person(name,age){
 
}
Person.prototype.name = name;
Person.prototype.age = age;
Person.prototype.sayName = function(){
    alert(this.name)
  }
var person = new Person('aa',12);

person instanceof Person => true
  • 判断原型对象

    • Person.prototype.isPropertyOf(person) => true
    • Object.getPropertyOf(person) == Person.prototype => true (es5)
  • 多个对象实例共享原型所保存的属性和方法的基本原理

    • 每当代码读取某个对象的某个属性时,搜索从对象实例本身开始,如果在实例中找到具有给定名字的属性则返回该属性的值
    • 如果没有找到则继续搜索指针指向的原型对象,如果在原型对象中找到给定名字的属性,则返回这个值
  • 虽然可以通过对象实例访问保存在原型实例中的值,但是不能通过对象实例重写原型中的值,如果在实例中添加一个与原型实例中的一个属性同名,访问时实例属性会屏蔽原型中这个属性的值,只能访问到这个实例上属性的值。可以通过delete操作符删除实例属性,重新访问原型中的属性

  • 使用hasOwnProperty()可以检测一个属性是存在实例中还是原型中,只有存在实例中才会返回true;person.hasOwnProperty('name')

  • 'name' in person 无论name是存在实例中还是原型对象中都会返回true

  //判断对象原型上是否有这个属性
  function hasPrototypeProperty(obj, attr){
    return (attr in obj) && !obj.hasOwnProperty(attr);
  }
  • 在使用for-in 循环时,返回的是能够通过对象访问的,可枚举的属性,包括了实例上的属性和原型上的属性。原型上不可枚举的属性,但是在实例中定义了也可以获取得到,如在实例中定义toString

  • Object.keys() (es5)

    • 取得对象上所有可枚举的实例属性,返回一个包含所有可枚举的字符串数组
    • 也可以传入一个原型对象,Object.keys(Person.prototype),但不会沿着原型链往上寻找,只返回当前prototype下的属性
  • Object.getOwnPropertyNames()(ie9+)

    • 枚举所有实例属性,不管是否可枚举

原型的动态性

  • 在原型中查找值的过程是一次搜索,对原型 对象所做的任何修改都能立即从实例上反映出来--即使是先创建了实例后修改原型也照样如此
  var friend = new Person();
  Person.prototype.sayHi = function(){
      
    alert('hi')
  }
  friend.sayHi() //hi
  • 实例和原型之间松散的连接关系,可以随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来
  • 但是重写整个原型对象就不一样了,调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型的联系
  • 实例中的指针仅指向原型对象,而不指向构造函数
  var friend = new Person();
  Person.prototype = {
    sayHi:function(){
      alert('hi')
    }
  }
  friend.sayHi() //error
Paste_Image.png
  • 原型模式的缺点
    • 省略了为构造函数传递初始化参数,所有实例在默认情况下都将取得相同的值
  • 当使用原型属性时会只要在一个实例上修改都会影响到所有的实例,例如在一个实例上修改数组

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

  • 构造函数定义实例属性,原型模式定义方法和属性

动态原型模式

  • 通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型
 function Person(name,age){
  this.name = name;
  this.age = age;
  **if( typeof this.sayName != 'funcction'){
     Person.prototype.sayName = function(){
      alert('hi')
    }
  }**
}
  • 只有初次调用构造函数时才会执行将函数添加到原型中

寄生构造函数模式

  • 类似于工厂模式,封装创建对象的代码,然后返回新创建对象,return语句可以重写调用构造函数时返回的值
  function Person(name,age){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function(){
      alert('hi');
    }
  return o;
  }
  • 使用场景:特殊情况下用来为对象创造构造函数。

  • 假设创建一个具有额外方法的特殊数组,由于不能直接修改Array构造函数

    function SpecialArray(){
      var arr = new Array();
      arr.push.apply(values,arguments);
      arr.toPipedString = function(){
      return this.join('|');  
    }
    return arr;
    }
    

var colors = new SpecialArray('green','red');
colors.toPipedString()

- 缺点:返回的对象和构造函数或者与构造函数的原型属性之间没有什么关系,不能使用instanceof来确定对象类型

### 稳妥构造函数模式
- 特点
  - 新创建对象的实例方法不引用this
  - 不使用new操作符调用构造函数
  - 除了返回对象上的属性和方法,没有其他办法访问到构造函数内的数据

  ```js
  function Person(name,age){
    var o = new Object();
    //这里可以定义私有数据
    o.sayName = function(){
      alert(name)
  }
  return o;
  }
var person = Person('green',12);
person.sayName() //只能通过sayName()方法去访问name的值

继承

原型链继承
  • js以原型链作为实现继承的主要方法
  • 基本思想是利用原型链让一个引用类型继承另一个引用类型的属性和方法
  • 构造函数、原型、实例的关系
    • 每个构造函数都有一个原型对象

    • 原型对象都有一个指向构造函数的方法

    • 实例都包含一个指向原型对象的一个指针

function SuoperType(){
  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.subprototype;
}
var instance = new SubType()
instance.getSuperValue();//true
833879510766076138.jpg
  • SubType不仅具有作为一个SuperType的实例所拥有的全部属性和方法,而且拥有一个指向SuperType原型的指针

  • instance指向SubType原型,SubType原型又指向SuperType的原型

  • 确定实例与原型的关系

  • instanceof操作符 只要用这个操作符来测试实例与原型中出现过的构造函数,结果就会返回true

  • isPropertyOf() 只要是原型链中出现的原型,都可以说是该原型链所派生的实例的原型,都会返回true

  • 缺点:

    • 原型属性会被所有实例共享,如果在父构造函数中有一个this.colors=[]的数组,子构造函数继承后的实例可以修改这个存在于子构造函数原型对象上的原型属性
    • 在创建子类型的实例时,不能向超类型的构造函数传递参数。实际上是没有办法在不影响所有实例的情况下给超类型的构造函数传递参数
借用构造函数
  • 在子类型构造函数的内部调用超类型构造函数,通过call,apply 改变对象的this指向
function SuoperType(name){
  this.colors = ['red']; 
  this.name = name;
}

function SubType(name){
  SuperType.call(this,name);
}
SubType.prototype = new SuperType();
var instance = new SubType('1')
instance.colors.push('green') => ['red','green']

var instance2 = new SubType('2')
instance2.colors.push('black') => ['red','black']
组合继承
  • 将原型链和借用构造函数的技术组合到一起
  • 使用原型链实现对原型属性和方法的继承
  • 使用借用构造函数实现对实例属性的继承
function SuperType(name){
  this.colors = ['red']; 
  this.name = name;
}
SuperType.prototype.sayName = function(){
  alert(this.name)
}
function SubType(name,age){
  SuperType.call(this,name);
  this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayName = function(){
  alert(this.name)
}
var instance = new SubType('1',12)
instance.sayName() => 1
instance.colors => ['red']
var instance2 = new SubType('2',21)
instance2.sayName() => 2
instance2.colors => ['red']
原型式继承
  • 基于一个对象上,这个对象相当于作为原型,再根据需求对得到的对象加以修改
  • 在没有必要兴师动众创建构造函数,而只想让一个对象与另一个对象保持类似的情况下使用
  function object(o) {
    function F(){}
    F.prototype = o;
    return new F();
  }
  var person = {
    name:'n',
    friends:['a','b']
  }
  var anotherPerson = object(person);
  anotherPerson.name = 'y';  =>y
  anotherPerson.colors.push('c');  => ['a','b','c']
  var person = object(person);
 person.name = 'yy';  =>yy
person.color; =>['a','b','c']
  • Object.create() es5新增规范原型式继承

 var person = {
   name:'n',
   friends:['a','b']
 }
 var anotherPerson = Object.create(person);
 anotherPerson.name = 'y';  =>y
 anotherPerson.colors.push('c');  => ['a','b','c']
 var person =  Object.create(person);
person.name = 'yy';  =>yy
person.color; =>['a','b','c']
寄生式继承
  • 创建一个仅用于封装继承过程的函数,在函数内部以某种方式增强对象
  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:'n',
    friends:['a','b']
  }
  var anotherPerson = createAnother(person);
  anotherPerson.sayHi(); =>hi
寄生组合式继承
  • 组合继承最大的问题是无论什么情况下都会调用两次超类型构造函数
    • 一次是创建子类型原型时,一次是子函数的内部构造函数
  • 寄生组合继承通过借用构造函数来继承属性,通过原型链混成形式来继承方法。
  • 思路是不必为了指定的子类型原型而调用超类型的构造函数。
  • 使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型
    function inheritPrototype(subType,superType){
      var prototype = object(superType);
      prototype.constructor = subType;
      subType.prototype = prototype;
  }

function SuperType(name){
  this.colors = ['red']; 
  this.name = name;
}
SuperType.prototype.sayName = function(){
  alert(this.name)
}
function SubType(name,age){
  SuperType.call(this,name);
  this.age = age;
}
SubType.prototype =inheritPrototype(subType,superType) ;
SubType.prototype.sayName = function(){
  alert(this.name)
}

推荐阅读更多精彩内容