Javascript的面向对象

/ 0评 / 0

本文的主要目的是对面向对象进行总结,笔记

=================================================

书中,面向对象主要讲了两点:
1.创建对象
2.继承

创建对象

1.工厂模式
==================================================
ECMAscript中是无法创建类的,因此开发人员通过用函数封装以特定接口创建对象的细节来实现工厂模式。
[code lang="js"]
function createPerson(name, job, age){
var o = {};
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(name);
};
return o;
}
var person1 = createPerson("Zheng", "coder", 23);
var person2 = createPerson("Yi", "mariner", 23);
alert(person1.job);
person2.sayName();
[/code]

2.构造函数模式
==================================================
通过创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
[code lang="js"]
function Person(name, job, age){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = Person("Zheng", "coder", 23);
var person2 = Person("Yi", "mariner", 23);
alert(person1.job);
person2.sayName();
[/code]

工厂模式跟构造函数模式的主要区别在于:
1.没有显式创建对象
2.直接将属性和方法赋予了this对象
3.没有return

需要注意的是:
按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应以一个小写字母开头
要创建Person的新实例的时候,必须使用new操作符
person1,person2分别保存着Person的一个不同的实例,且都保存着constructor(构造函数)属性,指向Person

其经历的四个过程为:
1.创建一个新对象
2.将构造函数的作用域赋给新对象(this指向了这个新对象)
3.执行构造函数中的代码(为这个函数添加属性)
4.返回新对象

构造函数的深入探讨
==================================================
1.将构造函数当作函数
构造函数跟函数的唯一区别就是在于它们的调用方式不同,即使用new操作符来创建一个新对象。如果不用new操作符来调用,它也就跟普通函数没差别了。
[code lang="js"]
//构造函数
var person = new Person("Steve", "CEO", 56);
person.sayName();

//普通函数
Person("Steve", "CEO", 56);
window.sayName();

//在另一个对象的作用域中调用
var o = {};
Person.call(o, "Steve", "CEO", 56);
o.sayName();
[/code]
在全局作用域中调用一个函数后,this对象总是指向Global对象(浏览器中的window对象)。

2.构造函数的问题
主要问题就是每个方法都要在每个实例上重新创建一遍。
因此通过把函数定义转移到构造函数外部来解决这个问题:
[code lang="js"]
function Person(name, job, age){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
};
[/code]
person1,person2对象共享了在全局作用域中定义的同一个sayName()函数,这样子解决了两个函数做同一件事的问题,却又带来了新问题:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域名不副实。而且,如果对象需要定义很多方法,那就要定义很多个全局函数,那么我们的自定义引用类型就没有了封装性可言了。所以我们将用到下面的原型模式来解决。

3.原型模式
==================================================
每一个函数都有一个prototype(原型)的属性,这个属性是个对象,它的用途是包含由特定类型的所有实例共享的属性和方法。
使用原型的好处就是可以让所有对象实例共享它所有包含的属性和方法,也就是不必再构造函数中定义对象信息,就可以讲这些信息直接添加到原型对象中。
[code lang="js"]
function Person(){}
Person.prototype.name = "Zheng";
Person.prototype.age = 23;
Person.prototype.job - "coder";
Person.prototype.sayName = function(){
alert(this.name);
};

var person1 = new Person();
person1.sayName(); //"Zheng"
[/code]
构造函数虽然是一个空函数,但是我们通过将sayName()方法和所有属性添加到了Person的prototype属性中。所以新对象还是具有相同的属性和方法。

深入理解原型模式
==================================================
1.理解原型
只要创建了一个新函数,该函数就会被创建一个prototype的属性。默认情况下,prototype属性还会自动获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针。通过这个构造函数,我们可以继续为原型添加其他属性和方法。
自定义的构造函数的原型属性只会取得constructor属性,其他的方法都是从Object继承来的。
在不能访问到内部的__proto__属性的时候,可以通过isPrototyleOf()方法来确定对象之间是否存在这种关系。
其执行过程是:实例本身→实例原型
我们可以通过实例访问保存在原型中的值,但是不能通过实例重写原型。而只能屏蔽掉原型中的那个属性。一旦屏蔽发生了,只有在使用delete操作符的时候才可以完全删除实例属性。
用hasOwnProperty()方法可以检测一个属性是否存在于实例中,还是存在于原型中。如果在实例中就返回true。

2.原型与in操作符
in操作符有两种使用方式:单独使用和在for-in循环中使用。
单独使用时,不管该属性是在实例还是原型中,都返回true。
for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性,其中包括存在于实例中的属性,也包括存在于原型中的属性。

3.更简单的原型语法
[code lang="js"]
function Person(){};

Person.prototype = {
name : "Steve",
age : 56,
job : "CEO",
sayName : function(){
alert(this.name);
}
};
[/code]
通过一个以字面量形式创建的新对象,我们可以得到跟之前的Person.prototype一样的结果。
但是这样子就导致了constructor属性不再指向Person。用字面量形式创建的新对象,本质上是完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的Content属性(指向Object构造函数),不再指向Person了。如果我们需要constructor属性的话,可以用以下办法解决:
[code]
function Person(){};

Person.prototype = {
constructor : Person,
name : "Steve",
age : 56,
job : "CEO",
sayName : function(){
alert(this.name);
}
};
[/code]
这样子就确保了constructor可以访问到适当的值了。

4.原型的动态性
原型中查找值是一个搜索的过程,因此我们对原型对象所做的任何修改都能够立即从实例上面反映出来——哪怕是先创建了实例,然后对原型进行了修改。其原因可以归结于实例与原型之间的松散连接关系。下面看一个例子:
[code lang="js"]
var person = new Person();

Person.prototype.sayHi = function(){
alert("hi");
};

person.sayHi();
[/code]
当我们调用person.sayHi()的时候,首先会在实例中搜索sayHi的属性,在没有找到的情况下,就会继续搜索原型。因为实例和原型之间的连接只不过是一个指针,而非一个副本,因此就可以在原型中找到新的sayHi属性并返回保存在那里的函数了。
但是在重写整个原型的时候,情况就不一样了。当我们调用构造函数时会为实例添加一个指向最初原型的__proto__指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系了。
强调:实例中的指针仅指向原型,而不指向构造函数。

5.原生对象的原型(略)

6.原型对象的问题
它的问题是省略了构造函数传递初始化参数这一环节,结果所有实例在默认情况下都是取得相同的属性。最大的问题则在于其共享的本质所导致的。对包含引用类型值的属性来说,问题比较突出:
[code lang="js"]
function Person(){}

Person.prototype = {
constructor : Person,
name : "Steve",
age : 56,
friends : ["Bill"],
sayName : function(){
alert(this.name);
}
};

var person1 = new Person();
var person2 = new Person();

person1.friends.push("Van");

alert(person1.friends); //"Bill,Van"
alert(person2.friends); //"Bill,Van"
alert(person1.friends === person2.friends); //true
[/code]
这就是为什么很少人单独使用原型模式的原因所在。

4.组合使用构造函数模式和原型模式
======================================================
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。

5.动态原型模式
======================================================
这是一个把所有信息都封装在构造函数中的,通过在构造函数中初始化原型的模式。

6.寄生构造函数模式(不推荐)
======================================================
这种模式的本质思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
这个模式可以再特殊的情况下用来为对象创建构造函数。
返回的对象与构造函数或者构造函数的原型属性之间没有关系;构造函数返回的对象与在构造函数外部创建的对象没有什么不同。

7.稳妥构造函数模式
======================================================
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。

参考文献

《Javascript高级程序设计(第二版)》人民邮电出版社----Nicholas C.Zakas

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注