创建对象

创建对象的方法

理解原型对象:
无论什么时候,只要创建了新函数,就会根据一组特定的规则为该函数创建一个 prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype属性的所在函数的指针。(重写原型属性会默认取消constructor属性)详细可见文章下图。
创建object实例的方式有三种:对象字面量表示法、new操作符跟object构造函数、(ECMAScript5中的)object.create()函数。下面主要讲的是最为复杂的new操作符跟object构造函数的创建对象实例的方法。

工厂模式

    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("NIcho",29,"software engineer");

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

构造函数模式

创建对象的自有属性的主要方法,每定义一个函数就实例化了一个对象。

function Person(name,age,job){
        this.name=name;
        this.age=age;
        this.job=job;
        this.sayName=function(){
            alert(this.name);
        }
   }
    //将构造的函数Person实例化,传入的参数作为自有属性,并且继承自Person.prototype
    var person1=new Person(NIcho",29,"software engineer);
    var person2=new Person(Jhon",26,"software engineer);

Person函数代替了createPerson函数,还有以下不同之处:

  1. 没有显式创建对象
  2. 直接将属性和方法赋给this对象(这里的this指的就是这个构造的函数)
  3. 没有return 语句

我们在这个例子中创建的对象既是object的实例又是person的实例(所有对象均继承自object。object可以理解为对象的根原型,我们所用到的所有对象方法的操作,如toString 、substring 、splice等都是继承自object)。


    alert(person1 instanceOf Object);//true
    alert(person1 instanceOf Person);//true

构造函数模式的问题:

使用构造函数的主要问题是,每个方法都要在每个实例上创建一次。person1和person2都有一个sayName的方法,但两个方法不是同一个Function实例(ECMAScript中的函数是对象,每定义一个函数就是实例化了一个对象)。

如果没有自定义向person原型中添加属性,实例化之后则得到Person自有属性的一个副本,并且继承object(注:person1与person2中sayName()引用位置不同)。
图片描述

通过把函数定义转移到构造函数外来解决:

    //全局函数
    function sayName(){
        alert(this.name);
    }

对应的:

this.sayName:sayName;

原型模式

用于定义实例共享的属性和方法。

    function Person(){
    }
    Person.prototype.name="Nicho";
    Person.prototype.age=29;
    Person.prototype.jod="software engineer";
    Person.prototype.sayName=function(){alert(this.name)};
    var person1=new Person();
    person1.sayName();//"Nicho" 继承Person.prototype
    var person2=new Person();
    person2.sayName();//"Nicho"继承Person.prototype
    alert(person1.sayName==person2.sayName);//true 引用位置相同

图片描述

(注:继承连接存在与实例与构造函数的原型之间而不是实例与构造函数之间)

关于原型上的一些函数:

  1. 检测obj是否为obj1的原型:obj.isPrototypeOf(obj1)//返回true/false
  2. 获得obj1的原型:object.getPrototypeOf(obj1)
  3. 检测一个属性是否存在于实例中(也就是自有属性)还是原型中(来自于继承):
Obj1.hasOwnProperty();//如果obj1有自有属性则返回true,否则返回false

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

组合使用构造函数模式和原型模式既可以继承原型对象的属性,又可以设置自由属性。

    function Person(name,age,job){
            this.name=name;
            this.age=age;
            this.job=job;
    }
    Person.prototype={
        constructor:Person,
        sayName:function(){
            alert("Hi");
        }
     }
    var person1=new Person(“Nicho”,29,”software engineer”);
    //person1自有属性:name aga job;原型属性(来自继承):constructor sayName

代码读取某个对象的某个属性时的搜索方法

搜索首先从对象实例开始,如果实例中找到了给定名字的属性,则返回该属性的值。如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找给定名字的属性。如果在原型对象中找到了该属性,则返回属性的值,否则一直向上查找给定名字的属性,直到object。如果没有找到则返回undefined。

这就需要我们注意:虽然我们可以通过对象实例访问原型中的值,但却不能通过对象实例(注意!!这里说的是通过对象实例而不是在原型中修改属性)来重写原型中的值。如果在实例中添加了一个与实例原型中同名的属性,该属性将会屏蔽原型中的那个属性。
来看下面的的例子:

    function Person(){
    
    }
    Person.prototype.name="Nicho";
    Person.prototype.age=29;
    Person.prototype.jod="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);//Nicho-来自原型;
    //person1对属性 name的修改并不能修改原型上的相应属性,因此person2继承自原型

图片描述

  1. person1=new Person()person3=person1是不同的:前者是实例化一个对象,后者遵循复制函数(对象引用)的原理
  2. 对象整体进行操作(引用)和对对象中的某个属性(值)进行操作实现原理是不同的
  • 整个对象的复制即引用相同
    var person3=person1;//整个对象的复制即引用相同(指针指针指针!只是一个索引,实际存储都不在指针位置下)
    person3.name=”newName”;//修改属性名称的值
    alert(person1.name);//newName;//另一个来自此处的引用属性的值会修改;即复制对象原理
    alert(person1.isPrototypeOf(person3));//false
    alert(Person.prototype.isPrototypeOf(person3));//true
  • 简单的属性的复制,并非引用同一个属性值.
    var person1=new Person("NIcho",29,"software engineer");
      var person2=new Person("HIcho",9,"Hardware engineer");
      person1.name=person2.name;//简单的属性的复制,并非引用同一个属性值
      person2.name="NEW";//尝试赋新值
      alert(person1.name);//HIcho,说明是值的传递而并非引用

继承

  1. ECMAScript只支持实现继承,而且实现继承主要是依靠原型链来实现的。
  2. 其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法(即:原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型来实现的)。

原型链的继承

    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  getSuperValue来自superType.prototype
        alert(instance.property);//true   property来自superType实例

实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于superType的实例的所有属性和方法(包括自有属性和继承属性),现在也存在于subType.prototype中了。
图片描述
(此时instance.constructor现在指向的是superType,因为subType的原型指向了另一个对象superType的原型,而这个原型对象的constructor指向的是superType)

注意:

  1. 别忘记默认的对象Object.prototype: subType继承了superType,superType继承了Object.prototype
  2. 确定原型和实例的关系:
  • instanceOf;测试实例与原型链中出现过的构造函数
   alert(instance instanceOf  superType);//true
   alert(instance instanceOf  subType);//true
  • isPrototypeOf:只要原型链中出现过的原型都可以说是该原型链所派生出来的实例的原型
    alert(superType.protoType.isPrototypeOf (instance));//true
    alert(subType.protoType.isPrototypeOf (instance));//true

原型链的问题:
在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。

解决原型链继承带来的问题的方法

解决这个问题的技术是借用构造函数,使用最多的继承模式是组合继承。此外还有原型式继承,寄生式继承,寄生组合继承。这里主要讲借用构造函数和组合继承。

借用构造函数

在子类型构造函数的内部调用超类型的构造函数。

    function superType(){
        this.colors=["red","blue","green"];
      }
      function subType(){
        //继承了superType
        superType.call(this);//在新创建的subType实例的环境下调用superType函数
        //这样一来,就会在新subType对象上执行superType函数中定义的所有初始化代码
        //结果subType的每一个实例都会具有自己的color属性的副本了
      }
      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;
  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("Nicho",29);
instance1.sayName();//”Nicho”
instance1.sayAge();//29

总结:

  1. subType的自有属性: this.age = age
  2. subType的继承属性:
superType.prptotype.sayName
subtype.prototype.sayAge
function superType(name){    
    this.name=name;
       this.colors=["red","blue","green"];
     }

组合继承改进

组合式继承的问题
组合式继承是js最常用的继承模式,但组合继承的超类型在使用过程中会被调用两次;一次是创建子类型的时候,另一次是在子类型构造函数的内部。我们使用Object.create做一下改进:

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;
}

function inheritPrototype(sup,sub){
  const obj = Object.create(sup);
  obj.constructor = sub;
  sub.prototype = obj;
}
// 继承
inheritPrototype(superType,subType);

重写原型对象

下面让我们看下一种情况:重写原型对象(完全重写而不是简单的添加原型对象的属性)
我们知道,在调用构造函数是会为实例添加一个prototype指针,而把这个原型修改为另一个对象就等于切断了构造函数与最初原型之间的联系。请记住:实例中的指针仅指向原型而不指向构造函数。

    function Person(){};
    var friend=new Person();
    Person.prototype={
    Constructor:Person,
    Name:“Nicholas”,
    Age:29,
    sayName=function(){
    alert(“this.name”);
    }
    }
    friend.sayName();//error

重写原型对象之前:

图片描述

重写原型对象之后:

图片描述

由图可见:重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系,它们引用的仍是最初的原型。


specialcoder
2.2k 声望170 粉丝

前端 设计 摄影 文学


下一篇 »
闭包详解