0x09

Summer Training Day 10.
构造函数与原型.
继承.

今天补充前两天的内容.


构造函数与原型

构造函数模式.

1
2
3
4
5
6
7
8
9
10
11
//构造函数默认以大写字母开头.
function Person(name, age){
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicolas",29);
var person2 = new Person("John",18);
//可以说,使用new操作符来创建新对象的函数就可以作为构造函数.

要创建新实例,则必须使用new操作符.
new做了5件事情:
1.创建新对象.
2.this指向这个新对象(通过把构造函数的作用域赋于新对象).
3.连接原型链.
4.执行函数(也是在给新对象添加属性).
5.返回新对象.

(Object与Array为原生构造函数)

纯构造函数的缺点.

每个方法要在每个实例上创建一遍,因此,不同实例上的同名函数其实是不相等的.
与其在构造函数内部定义函数,还不如在外部定义,这样每个实例共享一个函数,但是这样做又有问题.
在全局作用域定义函数但是只是让某个对象使用就有点浪费,而且可能需要为此定义许多全局函数.
这个自定义引用类型就毫无封装性可言了.
以上问题可以通过原型模式解决.

原型模式.

每个函数都有一个prototype(原型)属性,该属性为一个指针指向一个对象(原型对象),
这个对象包含特定类型所有实例共享的属性和方法.
使用原型对象可以让所有实例共享它所包含的属性和方法.就不必要在构造函数中定义.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(){
}
Person.prototype.name = "Nicolas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicolas"
var person2 = new Person();
person2.sayName(); //"Nicolas"
console.log(person1.sayName === person2.sayName); //True

默认情况下,所有原型对象会自动获得一个constructor(构造函数)属性.
此属性为一个指向prototype属性所在函数(即构造函数)的指针.
即:Person.prototype.constructor = Person;
创建了构造函数后,其原型对象默认只会取得constructor属性,其他属性继承Object;
创建新实例后,该实例内部包含一个指针(内部属性)__proto__,指向构造函数的原型对象,该属性对脚本完全不可视.
[需要注意的是,此关系(Person与Person.prototype)存在于构造函数与其原型对象之间,而与实例无关(即person1无prototype属性只有看不见的__proto__)].
但是实例对象共享constructor属性(可能是因为__proto__属性吧).person1.constructor == Person.


虽然__proto__属性无法访问,但的确是可以通过 isPrototypeOf() 方法来确定这个关系.
若__proto__指向Person.prototype,则会返回True.

1
2
console.log(Person.prototype.isPrototypeOf(person1)); //True
console.log(Person.prototype.isPrototypeOf(person2)); //True

也可以通过Object.getPrototypeOf()方法返回__proto__的值.

1
2
console.log(Object.getPrototypeOf(person1) == Person.prototype); //True
console.log(Object.getPrototypeOf(person1).name); //"Nicolas"

每当代码读取某个对象的某个属性时,都会执行一次搜索.首先从实例开始,如果没找到,
则继续搜索其指针指向的原型对象.有的话就返回其属性值.
也就是说,当我们调用person1.sayName()时,会先后进行两次搜索.
如果在实例找到就不会往上找.


这样就引出了在实例不能重写原型属性,但在属性同名的情况下会屏蔽原型属性的问题.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(){
}
Person.prototype.name = "Nicolas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg"; //此操作相当于在实例中新建name属性并且屏蔽Person.prototype中的name.
console.log(person1.name); //"Greg"
console.log(person2.name); //"Nicolas"

即便 person1.name = “”; 也不能恢复对原型Person.name的链接.
要想恢复连接,就要用到delete操作符.

1
delete person1.name;



hasOwnProperty()方法可以检测一个属性是在原型中还是实例中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Person(){
}
Person.prototype.name = "Nicolas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
console.log(person1.hasOwnProperty("name")); //false
person1.name = "Greg";
console.log(person1.name); //Greg————来自实例
console.log(person1.hasOwnProperty("name")); //true

Object.getOwnPropertyDescriptor( object , propertyname )方法只能用于实例属性信息获取.

1
2
3
4
console.log(Object.getOwnPropertyDescriptor(person1,"name"));
//Object {value: "Greg", writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(person2,"name"));
//undefined.因为person2中未创建name属性.原型的属性它不会找.

in操作符
单独使用时,会在能够访问给定属性时返回true,无论该属性存在于原型中还是实例中.

1
console.log("name" in person1); //true

Object.keys()方法获取对象上所有可枚举的实例属性.

1
2
3
4
5
6
7
var keys = Object.keys(Person.prototype);
console.log(keys); //"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
console.log(p1keys); //"name,age"

定义原型对象属性的简便写法:

1
2
3
4
5
6
7
8
9
function Person (){}
Person.prototype = {
name : "Nicolas",
age : 29,
job : "Software Engineer",
sayName : function(){
console.log(this.name);
}
};//注意冒号和逗号

此方法有个缺陷,就是constructor不再指向Person而是Object(本质上改写了整个prototype),
要指回来需要在花括号里面加上”constructor : Person”:

1
2
3
4
5
6
7
8
9
10
function Person (){}
Person.prototype = {
constructor : Person
name : "Nicolas",
age : 29,
job : "Software Engineer",
sayName : function(){
console.log(this.name);
}
};

但是这样做又有一个问题,就是其[[Enumerable]]属性被设置为true,即 可枚举.
若不想让constructor可枚举,则可以换一种添加方法.
Object.defineProperty(object,property,descriptor)

1
2
3
4
Object.defineProperty(Person.prototype,"constructor",{
enumerable : false,
value : Person
});

继承

原型链

本质上是把父类型的实例赋予子类型的原型对象.
重写原型对象,代之以一个新类型的实例.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function SuperType () {
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
//继承 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subproperty;
};
var instance = new SubType();
console.log(instance.getSuperValue()); //true

相关继承方式(原型式,寄生式,寄生组合式)以后再议…



以上抄书.