JS学习笔记(第6章)(面向对象之建立对象)

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 0;
}
var person1 = createPerson("Nicholas",29,"Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

抽象了建立具体对象的过程。工厂模式虽然解决了建立多个类似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)数组

2、构造函数模式

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        alert(this.name);
    };
}
var person1 = new Person("Nicholas",29,"Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

构造函数模式与工厂模式存在如下几点不一样:
(1)没有显示地建立对象;
(2)直接将属性和方法赋给了this对象;
(3)没有return语句
要建立Person的新实例,必须使用new操做符,以这种方式调用构造函数实际上会经历一下4个步骤:
(1)建立一个新得对象;
(2)将构造函数的做用域赋值给新对象(所以this就指向了这个新对象);
(3)执行构造函数中的代码(为这个新对象添加属性);
(4)返回新对象。
建立自定义的构造函数意味着未来能够将它的实例标识为一种特定的类型;而这正是构造函数模式赛过工厂模式的地方。
使用构造函数的主要问题就是每一个方法都要在每一个实例上从新建立一遍。
解决办法:经过把函数定义转移到构造函数外部来解决这个问题。函数

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName() {
    alert(this.name);
};
var person1 = Person("Nicholas",29,"Software Engineer");
var person2 = Person("Greg", 27, "Doctor");

clipboard.png

clipboard.png

3、原型模式

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

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

一、理解原型对象

(1)只要建立了一个新的函数,就会根据一组特定的规则诶该函数建立一个prototype属性,这个属性指向函数的原型对象;
(2)在默认状况下,全部原型对象都会自动得到一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针;
(3)当调用构造函数建立一个新实例后,该实例内部将包含一个指针(内部属性),指向构造函数的原型对象。注意:这个链接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。spa

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();
var person2 = new Person();

下图展现了上述代码建立的各个对象之间的关系。
clipboard.png
(1)能够经过isPrototypeOf()方法来肯定对象之间是否存在[[Prototype]]关系,若是[[Prototype]]指向调用isPrototypeOf()方法的对象(Person.prototype),那么这个方法就返回true。prototype

alter(Person.prototype.isprototypeOf(person1));  //true

Object.getPrototypeOf(),在全部支持的实现中。这个方法返回[[Prototype]]的值。指针

alert(Object.getPrototypeOf(person1)  == Person.prototype);  //true
alert(Object.getPrototypeOf(person1).name);  //"Nicholas"

(2)每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具备给定名字的属性。搜索首先从对象实例自己开始。若是在实例中找到啦具备给定名字的属性,则返回属性的值;若是没有找到。则继续搜索指针指向的原型对象,在原型对象中查找具备给定名字的属性。若是在原型中找到了这个属性,则返回该属性的值。
(3)虽然能够意经过对象实例访问保存在原型中的值,但却不可以经过对象实例重写原型的值。当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性;也就是说,添加这个属性只会阻止咱们访问原型中的那个属性,但不会修改那个属性。
(4)使用delete操做符则能够彻底删除实例属性,从而使咱们可以从新访问原型中的属性。code

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();
var person2 = new Person();

person1.name = "Greg";
alert(person1.name); //"Greg"来自实例
alert(person2.name); //"Nicholas"来自原型

delete(person1.name); //彻底删除实例对象
alert(person1.name); //"Nicholas"来自原型

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

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();
var person2 = new Person();

alert(person1.hasOwnPrototype("name")); //false

person1.name = "Greg";
alert(person1.name); //"Greg"来自实例
alert(person1.hasOwnPrototype("name")); //true

alert(person2.name); //"Nicholas"来自原型
alert(person1.hasOwnPrototype("name")); //false

delete(person1.name); //彻底删除实例对象
alert(person1.name); //"Nicholas"来自原型
alert(person1.hasOwnPrototype("name")); //false

二、原型与in操做符

(1)有两方式使用in操做符:单独使用和在for-in循环使用。在单独使用时,in操做符会在经过对象可以访问给定属性时返回true,不管属性存在于实例仍是原型中;在使用for-in循环时,返回的是全部可以经过对象访问的、可枚举的属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。blog

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();
var person2 = new Person();

alert(person1.hasOwnPrototype("name")); //false
alert("name" in person1); //true

person1.name = "Greg";
alert(person1.name); //"Greg"来自实例
alert(person1.hasOwnPrototype("name")); //true
alert("name" in person1); //true

alert(person2.name); //"Nicholas"来自原型
alert(person1.hasOwnPrototype("name")); //false
alert("name" in person2); //true

delete(person1.name); //彻底删除实例对象
alert(person1.name); //"Nicholas"来自原型
alert(person1.hasOwnPrototype("name")); //false
alert("name" in person1); //true

(2)同时使用hasOwnProperty()方法和in操做符,就能够肯定该属性究竟是存在于对象中,仍是存在于原型中。ip

function hasOwnProperty(object, name) {
    return !object.hasOwnProperty(name) && (name in object);
}

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

var person = new Person();
alert(person1.hasOwnPrototype(person,"name")); //true表示属性存在于原型中 

person.name = "Greg";
alert(person1.hasOwnPrototype(person,"name")); //false表示属性存在于实例中

Object.keys()方法:取得对象上全部可枚举的实例属性。这个方法接收一个对象做为参数,返回一个包含全部可枚举属性的字符串数组。

function Person() {
}
Person.prototype.name = "Nicholas";
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"

使用Object.getOwnPropertyName()方法能够获得全部的实例属性,不管它是否可枚举.

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

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

三、更简单的原型语法

clipboard.png

//原型模式
function Person() {
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};

//更简单的原型语法:用一个包含全部属性和方法的对象字面量来重写整个原型对象;
致使的问题:至关于重写了默认的prototype属性,constructor属性再也不指向Person 
function Person() {
}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

解决办法:特地将constructor属性设置回适当的值

function Person() {
}
Person.prototype = {
    constructor : Person, //将constructor设置为原来的值
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

clipboard.png
可是,以这种方式重设constructor属性致使它的[[Enumberable]]特性被设置为true。默认状况下,原生的constructor属性是不可枚举的。所以能够尝试用Object.defineProperty()重设构造函数。

function Person() {
}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
//重设构造函数
Object.defineProperty(Person.prototype,"constructor",{
    enumerable : false,
    value : Person
});

四、原型的动态性

function Person() {
}
var friend = new Person();
//重写整个原型对象
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
friend.sayName();  //error

重写对象以前

clipboard.png

重写原型对象以后

clipboard.png
重写原型对象切断了现有原型与任何以前已经存在的对象实例之间的联系;它们引用的仍然是最初的原型。

五、原型对象的问题

原型模式的最大问题是由其共享的本质所致使的,原型中的全部属性是被不少实例共享的。可是,实例通常都是要有属于本身的所有属性的。

function Person() {
}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    friends : ["Shebly", "Court"],
    sayName : function () {
        alert(this.name);
    }
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push["Van"];
alert (person1.friends); //"Shebly", "Court","Van"
alert (person2.friends); //"Shebly", "Court","Van"
alert(person1.friends === person2.friends); //true

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

建立自定义类型的最多见方式,就是组合使用构造函数模式和原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。

//构造函数
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); //"Shebly", "Court","Van"
alert (person2.friends); //"Shebly", "Court"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

5、动态原型模式

能够经过检查某个应该存在的方法是否有效来决定是否须要初始化

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    //方法
    if (typeof this.sayName != "function") {
        Person.prototype.sayName = function() {
            alert(this.name);
        };
    }
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

注意:若是在已经建立了实例的状况下重写原型,就会切断现有实例与新原型之间的联系。

6、寄生构造函数模式

P160封装建立对象的代码,而后再返回新建立的对象

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"

寄生构造函数模式除了使用new操做符并把使用的包装函数叫作构造函数以外,这个函数模式和工厂模式实际上是如出一辙的,下面附上两种模式的代码

clipboard.png

7、稳妥构造函数模式

P161稳妥对象,指的是没有公共属性,并且其方法也不引用this的对象。稳妥构造函数遵循与寄生构造函数相似的模式,但有两点不一样:(1)新建立对象的实例方法不引用this;(2)不使用new操做符调用构造函数。

function Person(name, age, job) {
    var o = new Object();  //建立要返回的对象
    //能够在这里定义私有变量和函数

    //添加函数
    o.sayName = function() {
        alert(name);      `//注意寄生构造函数中是alert(this.name)`
    };
    //返回对象
    return o;
}
var friend = Person("Nicholas", 29, "Software Engineer");   
`//注意寄生构造函数中是var friend = newPerson("Nicholas", 29, "Software Engineer"); `
friend.sayName();  //"Nicholas"

注意,在以这种模式建立的对象中,除了使用sayName()方法以外,没有其余办法访问name的值。

相关文章
相关标签/搜索