js原型

创建对象

通常,我们可以通过Object构造函数或对象字面量创建单个对象,但是当我们使用同一个接口创建安很多对象时,就会产生大量代码。所以,我们可以使用下面几种方式创建对象。

1. 工厂模式

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('Shuaige', 28, '前端');
var person2 = createPerson('Meinv', 19, 'UI');

工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型。什么是对象的类型呢?你可以在下面的构造函数中找到答案)

2.构造函数模式

构造函数习惯以大写字母开头。任何函数,通过new运算符调用,就可以作为构造函数。

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

var person1 = new Person('Shuaige', 28, '前端');
var person2 = new Person('Meinv', 19, 'UI');

上面的例子,Person被称为构造函数,person1和person2为这个构造函数的实例。

Person()函数取代了createPerson()函数,它们存在以下不同之处:

  • 没有显式地创建对象;
  • 直接将属性复制给this对象;
  • 没有return语句;

通过new运算符调用构造函数,实际上经历了下面四个步骤:

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象
  3. 执行构造函数的代码(为这个新对象添加属性和方法)
  4. 返回新对象

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

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

创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,这个类型可以理解为所调用的构造函数。

构造函数模式虽然好用,但也存在缺点。每个方法都要在每个实例上创建一遍。ECMAScript 中的函数是对象,因此每定义一个函数,也就实例化了一个对象。此时的构造函数可以像下面这样定义:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = new Function("alert(this.name)"); // 与声明函数在逻辑上是等价的
}

每个Person 实例都包含一个不同的Function实例。因此,不同实例上的同名函数是不相等的。

3.原型模式

我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象包含所有实例共享的属性和方法。简单的说就是,prototype是一个指针,指向实例的原型对象。不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。

function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true

下面,我们来深入探讨一下到底什么是原型对象:

只要创建一个新函数,就会为这个函数分配一个prototype属性,这个属性是一个指针,指向函数的原型对象。所有原型对象都会自动获得一个constructor属性,这个属性也是一个指针,指向构造函数。以前面使用Person构造函数和Person.prototype创建实例的代码为例,下图展示了各个对象之间的关系。

注意:对象实例可以访问原型中的值,但不能修改原型中的值。若对象实例中有一个与原型同名的属性,则对象实例的属性会覆盖原型的同名属性。

在上面的例子中,每天加一个属性和方法都要敲一遍Person.prototype。下面的这种写法可以更好的优化代码,体现封装性。

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

前面介绍过,每创建一个函数就会同时创建它的prototype对象,这个对象会自动获得constructor属性。我们这里采用的写法,本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。所以,我们需要在Person.prototype中再加一行代码:

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

采用原型模式创建对象,还可能出现一个问题,例如下面的一段代码:

function Person(){}
var friend = new Person();
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
friend.sayName(); //error

把原型对象修改为另一个对象等于切断了构造函数与最初原型之间的联系。实例中的指针仅指向原型,而不指向构造函数。所以,重写原型对象切断了现有原型与之前任何已经存在的实例之间的联系,这些实例引用的仍然是最初的原型。下图展示了这一过程的内幕:

我们可以将对象实例化放在重写原型之后,即可解决这个问题。

function Person(){}
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
var friend = new Person();
friend.sayName();

采用原型模式还有一个缺点。原型中所有的属性被很多实例共享,这种共享对于函数非常合适,但对于包含引用类型值(引用类型指那些可能由多个值构成的对象,是保存在内存中的对象,js规定不允许直接操作对象的内存空间)的属性来说,问题就比较突出了。

function Person(){}
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    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); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true

修改person1.friends 引用的数组,向数组中添加了一个字符串。由于friends数组存在于Person.prototype 而非person1中,所以刚刚提到的修改也会通过person2.friends(与person1.friends 指向同一个数组)反映出来。

4.组合使用构造函数模式和原型模式(推荐使用)

创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数用于定义实例的私有属性,而原型模式用于定义实例方法和共享属性。当某个属性是数组时,一定要将这个属性放在构造函数内,绝不可挂在prototype上。

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,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

我们在前面说过,对象实例可以访问原型中的值,但不能修改原型中的值。每个Person实例都包含一个Array实例,不同实例上的同名函数是不同的

原文地址:https://www.cnblogs.com/renzhiwei2017/p/8301992.html