今天中秋假期的第二天,继续开始学习这本书咯~
第6章:面向对象的程序设计
constructor,prototype属性是不可枚举的
创建对象
工厂模式
function createPerson(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.sayHello = function(){
alert(this.name);
}
return o;
}
var person1 = createPerson("Daming",12);
var person2 = createPerson("Xiaohong",11);
特点:
- 工厂函数内部会每次生成一个新的Object对象。
- 函数结束会返回新建立的对象。
- 缺点:无法识别这个新生成对象的类型。
构造函数模式
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name);
}
}
var p1 = new Person("Daming",12);
var p2 = new Person("Xiaohong",11);
特点:
- 构造函数内没有显式创建新对象。
- 通过new来生成对象。不过可以不用new。只不过这个时候,由于在函数内部this指向的是window,故实际上是添加了全局变量。
- 没有return值。
- 缺点:每个实例都会重新创建一遍每个方法。意思就是每生成一次实例,都会再编译一遍他的方法。而且每个实例的同名函数是不相等的,比如:
alert(p1.sayName == p2.sayName);//false
为了解决这个问题,可以通过将函数定义在外部。
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
var p1 = new Person("Daming",12);
var p2 = new Person("Xiaohong",11);
但是这样的话是不是就违反了封装函数的要求呢
原型模式
每个函数都有一个prototype属性。就是一个指针,指向一个大容器,包含这个类型的所有实例共享的方法和属性。这样就可以使得实例共享一些数据。
function Person(){
}
Person.prototype.age = 15;
Person.prototype.sayName = function(){alert(this.name);}
var person1 = new Person("Daming");
var person2 = new Person("Xiaohong");
person1.name = "Daming";
person2.name = "Xiaohong";
从上面的例子来看。Person内有prototype这个指针,指向一个大容器(我们暂时叫做PersonProto,随便叫的),PersonProto内包含constructor(这个是自动就有的,指向Person),还有age。
注意Person.prototype->PersonProto,PersonProto.constructor->Person。实际上构成了一个循环了,还蛮有意思的。
每一个实例中也有一个[[Prototype]]指向PersonProto。但是正常情况下是没法访问到这个属性的。在ES5中可以通过Object.getPrototypeOf()
来获取这个属性。
如果不是在原型链中的属性,就不会被所有对象进行共享。比如上述代码中的name属性。
简单介绍了一下原型链,我们可以来说一下具体读到某个属性的发生的事。
每当代码读取某个对象的某个属性时,都会进行一次搜索。首先查这个对象实例有没有这个属性。没有的话再查原型链。
故如果在实例中修改属性值,是会在实例中新创建一个属性,并不会修改原型链中的数据,也就不会影响其他实例的值。
function Person(){
}
Person.prototype.age = 15;
Person.prototype.sayAge = function(){alert(this.age);}
var person1 = new Person();
var person2 = new Person();
person1.name = "Daming";
person2.name = "Xiaohong";
person1.age = 13;
person1.sayAge();//13;
person2.sayAge();//15
//person1中的age=15并不会被13替代,只是存在于原型链中,可以通过删除实例的属性来恢复原型链属性
delete person1.age;
person1.sayAge();//15
关于判断属性是属于原型链还是实例中
思路很简单,hasOwnProperty()可以判断属性是否存在实例中。in可以判断是否有这个属性(不管是原型链还是实例),两个方法结合起来就OK啦
function Person(){
}
Person.prototype.age = 15;
Person.prototype.sayName = function(){alert(this.name);}
var person1 = new Person();
var person2 = new Person();
person1.name = "David";
person2.name = "Xiaohong";
function hasPrototypeProperty(obj,name){
return !obj.hasOwnProperty()&&(name in obj);//存在于对象但是不存在于实例中
}
获取属性方法
- 获取所有可枚举的实例属性(prototype默认不可枚举):ES5中有Object.keys()方法可以接受一个对象作为参数,返回所有属性的字符串数组。
- 获取所有实例属性(包括prototype):Object.getOwnPropertyNames()。
更简单的原型语法
function Person(){
}
Person.prototype = {
name:"David",
age:15,
}//实际上是用一个字面量重新写了Person的prototype
var person = new Person();
但是这样写了会出现以下结果
alert(Person.constructor == Person);//false
原因在于原本prototype是会默认加上constructor属性,并指向Person。但是这样写的话就相当于把原型链重写了一遍。为了解决这个问题,可以手动加上
Person.prototype = {
name:"David",
age:15,
constructor:Person
}
另外要注意的是这样写会破坏原型的动态性。看以下代码
function Person(){}
var person1 = new Person();
Person.prototype = {
name:"David";
sayName: function(){
alert(this.name);
}
}
person1.sayName();//error
原因是在实例化的时候的Person已经和之后的Person不一样了。
特点:
- 每个实例都可以共享一些属性和方法
- 也可以灵活的为每一个实例添加一些属性,不会影响到原型链
- 可以动态的添加方法,并且每个实例都可以用到。
- 缺点:想要每个实例都能在生成的时候都有一个引用属性的话,就会出现问题。
function Person(){
}
Person.prototype = {
constructor:Person,
name:"Daming",
age:12,
friends:["Xiaohong","Liwei"]//引用属性
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("haha");
alert(person2.friends);//Xiaohong,liwei,haha
可以看到我们没办法像普通属性那样去屏蔽原型链的内容,而是直接修改原型链。因此我们一般采用构造函数和原型模式组合使用
function Person(name,age){
this.name = name;
this.age = age;
this.friends = ["Xiaohong","Liwei"];
}
Person.prototype={
constructor:Person,
sayName:function(){alert(this.name);}
}