《JavaScript高级程序设计》——对象学习笔记

建立对象

使用对象字面量的形式一个接口会建立不少对象, 会产生大量的重复代码。编程

工厂模式:用函数来封装以特定接口建立对象的细节

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("Simon", 29, "software Engineer");
var person2 = createPerson("Zaynex",22, "Doctor");
  • 这种模式解决了建立多个类似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)数组

构造函数模式

- 可用于建立特定模式的对象,像Object、Array等原生构造函数,在运行时会自动出如今执行环境中。

咱们利用构造函数重写下刚才的函数。浏览器

function createPerson(name, age, job)
    {
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function()
        {
            alert(this.name);
        };
    }

    var person1 = new Person("Simon",29, "software Engineer");
    var person2 = new Person("Simon",29, "software Engineer");

构造函数与工厂模式的差别

  1. 没有显示地建立对象;安全

  2. 直接将属性和方法赋给this对象;app

  3. 没有 return 语句;函数

咱们注意到Person开头是大写,按照惯例来说,构造函数开头字母是大写,非构造函数以小写字母开头。测试

调用构造函数四步骤

  1. 建立一个新对象;this

  2. 将构造函数的做用域赋给新对象(所以this就指向了这个新对象);spa

  3. 执行构造函数中的代码(为构造函数新对象添加属性)prototype

  4. 返回新对象

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

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

对象的constructor属性最初是用来标识对象类型的。可是提到检测对象类型,仍是instanceof操做符更可靠一些。

alert(person1 instanceof Object);
alert(person1 instanceof Person);
alert(person2 instanceof Object);
alert(person2 instanceof Person);
//都为true.

咱们所建立的全部对象都是Object的实例,同时也是Person的实例。

  • 建立自定义的构造函数意味着未来能够做为实例标识为一种特定的类型;构造函数模式赛过工厂模式的地方

把构造函数当函数

- 任何函数,只要经过  new 操做符来调用,那它就能够做为构造函数;

//当作构造函数使用

var person = new Person("Simon", 29, "software Engineer");
person.sayName(); //Simon

//普通函数调用
Person("Genf", 23, "DOCTOR");  //添加到window
window.sayName();  // Genf
以刚才的那种方式定义的构造函数定义在Global对象中(在浏览器中是window对象),在全局做用域中调用函数时,this指向的是window对象

// 在另一个对象的做用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, 'nusd');
o.sayName(); // Kristen

构造函数的缺陷

每一个方法都要在每一个实例上从新建立一遍。

person1 和 person2 都有一个名为 sayName() 的方法;但那两个方法都不是同一个 Function 的实例,所以会有不一样的做用域链和标识符解析;不一样实例上的同名函数是不一样的。

不要忘了,每一个函数都是一个对象!因此sayName方法也能够这样写,所以每一个Person实例都包含着不一样的Function实例。以这种方式建立函数,会致使不一样饿做用域和标识符解析。

this.sayName = new Function("alert(this.name)");  //与声明函数在逻辑上是等价的

咱们能够检验下

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

建立两个完成相同任务的Function 实例没有必要,何况有this对象在,根本不用在执行代码前就把函数绑定到特定对象上面。

  • 咱们能够经过函数定义转移构造函数外部来解决这个问题。

    function Person(name, age ,job)

    {
        this.name = name;
        this.age = age;
        this.sayName = sayName;
    }
    
    function sayName()
    {
        alert(this.name);
    }
    
    var person1 = new Person("Simon", 29, "software Engineer");
    var person2 = new Person("Zaynex", 29, "DOCTOR");

把sayName()函数的定义转移到了构造函数外部。

在构造函数内部,将sayName属性设置成等于全局的 sayName 函数。这样sayName 包含的是一个指向函数的指针。 person1和person2共享同一个sayName()函数。

但问题是:
在全局做用域中定义的函数实际上只能被某个对象调用,若是对象须要定义不少方法,那么就要定义多个全局函数。

所以咱们须要用原型模式来解决这个问题。

原型模式

咱们建立的每一个函数都有一个 prototype(原型) 属性,这个属性属于指针,指向一个对象,而这个对象的用途是包含能够由特定类型的全部实例的共享的属性和方法。即经过构造函数而建立的那个对象实例的原型对象。

咱们没必要将构造函数定义对象实例的信息中,而是能够将这些信息直接添加到对象原型中。

function Person(){
    }

    Person.prototype.name ="Simon";
    Person.prototype.age = 29;
    Person.prototype.job = "software Engineer";
    Person.prototype.sayName = function(){
        alert(this.name);
    };


    var person1 = new Person();
    person1.sayName();

    var person2 = new Person();
    person2.sayName();

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

实际上,person1和person2都不包含属性和方法, 但能够调用person1.sayName().这是经过查找对象属性的过程来实现的。

理解原型对象

不管什么时候,只要建立了新函数,就会根据一组特定的规则为该函数建立一个 prototype 属性,这个属性指向函数的原型对象。

在默认状况下,全部原型都会自动得到一个constructor(构造函数)属性,这个属性包含在一个指向 prototype属性所在的函数的指针。举例说明: Person.prototype.constructor 指向Person.

建立了自定义构造函数以后,其原型对象默认只会取得 constructor 属性。其余方法都是从Object继承来的。

调用构造函数的一个实例后,该实例内部将包含一个指针(ES5中称为[[Prototype]],指向构造函数的原型对象。在脚本中没有标准形式访问[[Prototype]],但在FF,SF,Chrome中的每一个对象都支持属性_proto_;在其余实现中,该属性对脚本不可见。

要明确的是, 这个连接存在于实例与构造函数的原型对象之间,而非实例与构造函数之间。

虽然在现实中没法访问到[[Prototype]],但能够经过 isPrototypeOf()来肯定是否存在这种关系。
在ES5中新增一个方法,使用 Object.getPrototypeOf()能够方便的获取一个对象的原型

每当代码读取某个对象的某个属性时,都会执行一次搜索,
1.先从实例自己开始搜索属性,存在,搜索结束。若不存在,执行2
2.从实例的原型开始搜索属性。

继续刚才的代码。若是咱们继续给实例添加相同的属性,会怎样?

function Person(){
    }

    Person.prototype.name ="Simon";
    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 = "xiwenzheng";

    alert(person1.name) //xiwenzheng  ——来自实例
    alert(person2.name) // Simon  ——来自原型

在person1这个实例中重写属性,那么解释器搜索到了实例自己的属性直接返回,对于person2而言,实例中没有属性,那么再往实例的原型开始搜素属性。

  • 给对象添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性,就是阻止咱们访问原型对象,但并不会修改原型对象中的同名属性。即便将person1.name 设置为 null 也不会影响原型对象中的同步属性。

  • 不过delete 实例属性,就能够访问原型对象中的属性了。

    function Person(){

    }
    
        Person.prototype.name ="Simon";
        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 = "xiwenzheng";
        alert(person1.name); //xiwenzheng  ——来自实例
        alert(person2.name); // Simon  ——来自原型
        delete person1.name;
        alert(person1.name); // Simon 来自原型

    使用hasOwnProperty()能够检测一个属性是否存在实例中仍是存在原型中,这个方法只在给定属性存在于对象实例中才会返回 true;

咱们继续采用刚才删除部分的整段代码。

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

原先person1.name是存在对象实例中的(被咱们设为了"Zaynex"),可是被咱们delete了。
若是咱们不delete的话,那就是true了。要想得到原型属性的描述符,必需要在原型对象上调用 Object.hasOwnPropertydDsecriptor();

原型与 in 操做符

in 操做符会在经过对象可以访问给定属性时返回 true ,不论该实行存在于实例中仍是原型中。

  • 利用in:判断是否有该属性

  • 利用hasOwnProperty()判断是否存在对象实例中;

  • 结合之后就能够判断该属性是在原型中仍是在实例中。

    function hasPrototypeProperty(object, name ){

    return !object.hasOwnProperty(name) && (name in object);

    }
    person1.name = "Zaynex";
    alert(hasPrototypeProperty(person1, "name")); //false;存在实例中

    for-in 循环时,返回的都是经过对象访问的、可枚举的属性(即将[[Enumberable]]标记为true的属性),在ES5中constructor 和 prototype属性的 [[Enumberable]]

设为false,但并非全部浏览器都照此实现。
想取得对象上全部可枚举的实例属性,可使用Object.Keys()方法。

function Person(){
    }

    Person.prototype.name ="Simon";
    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 = 29;

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

若是想获得全部实例属性,不管是否可枚举,均可以使用 Object.getOwnPropertyNames()

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

更简单的原型语法

以前的例子中每添加一个属性和方法都要 Person.prototype,咱们进行适当的封装。

function Person(){

}

Person.prototype = {
    name : "Simon",
    age : 29;
    job : "software Engineer",
    sayName : function  () {
        alert(this.name);
    }
};

咱们将 Person.prototype 设置为等于一个以对象字面量形式建立的新对象。
以前介绍到,每建立一个函数,同时会建立它的prototype对象,这个对象会指定得到constructor 属性。而咱们在这里的语法本质上是重写了默认的 prototype 对象。

因此 constructor属性也编程了新对象的属性。(指向Object构造函数),再也不指向Person了。

instanceof 测试 Object 和 Person 都返回 true,但constructor 属性则等于Object而不等于 Person ;

若是 constructor 的值很重要,则能够特地设置回适当的值

function Person(){
}

Person.prototype = {
    constructor:Person,
    name : "Simon",
    job : "software Engineer",
    sayName : function () {
        alert(this.name);
    }
}
  • 注意,以这种方式重设constructor属性会致使 [[Enumberable]]特性设置为true,但默认咱们是不可枚举constructor属性的。

为了兼容ES5的JS引擎,能够用 Object.defineProperty();

function Person(){
    }

    Person.prototype = {
        name : "Simon",
        job : "software Engineer",
        sayName : function () {
            alert(this.name);
        }
    }
Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});

原型的动态性

在原型中找值的过程是一次搜索,所以咱们对原型对象所作的任何修改都能当即从实例中反应出来——即便是先建立实例后修改原型。
不信你看:

var friend = new Person();
Person.prototype.sayHi = function(){
    alert("hi");
};
friend.sayHi();  // "hi"
  • 这个能够归结于实例与原型之间的松散连接关系。咱们首先会在实例中搜索sayHi的属性,在没找到的状况下会继续搜索原型,由于实例与原型之间的连接只不过是一个指针。

可是若是重写整个原型对象,状况就不同了。调用构造函数时会为实例添加一个指向最初原型的[[Prototype]]指针,而把原型修改成另一个对象就等于切断了构造函数与最初原型之间的联系。

请记住,实例中的指针仅指向原型,而不指向构造函数

function Person(){

}

var  friend = new Person();

Person.prototype = {
    constructor:Person,
    name : "Simon",
    job : "software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

friend.sayName();  //error

在这个例子中,咱们建立了Person的实例,而后又重写了其原型对象,而后在调用sayName()时发生错误,所以friend指向的原型不包含以该名字命名的属性。
重写整个原型对象

原型对象的问题

  1. 省略了为构造函数传递初始化参数这一环节,结果全部实例在默认状况下都取得相同的属性值。

  2. 共享致使的问题,不少属性能够共享,对于包含引用类型值的属性来讲,问题比较突出。

    function Person(){

    }
    
    Person.prototype = {
        constructor:Person,
        name : "Simon",
        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 === person2.friends) // true;

修改person1.friends 引用的数组,添加字符串,因为 friends数组存在 Person.prototype 而非 person1中,因此修改也会形成person2.friends反映出来,若是咱们的初衷就是要共享同一个数组,那无话可说。

但是通常都是要有属于本身的所有属性的。而这个问题正是咱们不多看到有人单独使用原型模式的缘由。

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

建立自定义类型的最多见方式就是这个。构造函数用于定义实例属性,原型模式用于定义方法和共享的属性。这样,每一个实例都会有本身的一份实例属性的副本,但又同事共享着对方法的引用,最大限度节省了内存。此外,这种混成模式还支持向构造函数传递参数。

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

实例属性都是在构造函数中定义的,全部实例共享的属性是在 constructor 和方法sayName()是在原型中定义的。

动态原型模式

当其余OO语言经验开发人员看到独立的构造函数和原型时,会感到困惑。所以出现了 动态原型模式———即把全部信息都封装在了构造函数中,而经过在构造函数中初始化原型(仅在必要的状况下),又保持了同事使用构造函数和原型的优势。

换句话说,能够经过检查某个应该存在的方法是否有效,来决定是否须要初始化原型。

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

       if( typeof this.sayName != "function"){
           Person.prototype.sayName = function() {
               alert(this.name);
           };
       }
 }
    // 只有在sayName不存在的状况下, 才将其添加到原型中,这段代码只会在初次调用函数时执行。此后原型已经完成初始化,不须要再作修改。
 var  friends1 = new Person("Nicholas", 29, "software Engineer");
 var friends2 = new Person("Zaynex",19,"Engineer");
 friends1.sayName();
 friends2.sayName();

因为第一次当friends1初始化以后,friends2就不须要再进行初始化原型。
详情参考点击此处

寄生构造函数模式

在上述几种模式都不适用的状况下,咱们可使用寄生构造函数模式
基本思想:建立一个函数,该函数的做用仅仅是封装对象的代码,而后再返回新建立的对象。

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

寄生构造函数应用

在特殊状况下为对象建立构造函数。 假设咱们想建立一个具备额外方法的特殊数组,因为不能直接修改Array构造函数,所以可使用这个模式。

function SpecialArray(){
          var values = new Array();

           values.push.apply(values, arguments);

           values.toPipedString = function(){
               return this.join("|");
           }

           return values;
    }

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

寄生构造函数模式:返回的对象与构造函数或者与构造函数的原型属性没有关系;不能依赖于 instanceof操做符肯定对象类型。所以不建议在已使用其余模式的状况下使用该种模式。

稳妥构造函数模式

应用场景

稳妥对象,是指没有公共属性,其方法也不引用this的对象。适合在安全环境下(这些环境会禁止使用this 和 new),或者放置数据被其余应用程序改动时使用。

稳妥函数与寄生构造函数差别

  1. 新建立的对象的实例方法不引用this。

  2. 不使用new 操做符调用构造函数。

    function Person(name, age, job) {

    var o = new Object();
       //能够在这里定义私有变量和函数。
       //
       //添加方法
       o.sayName = function(){
           alert(name);
       };
       //返回对象
       return o;

    }

    var friend = Person("Nicholas", 29, "software Engineer");
    friend.sayName();
    以这种模式建立的对象,除了使用sayName()方法之外,没有其余办法访问name的值。

与计生构造函数模式相似,使用稳妥构造函数模式建立的对象与构造函数之间没有什么关系,所以instanceof操做符对这种对象没有意义。

相关文章
相关标签/搜索