什么是原型
Javascript 只有一种结构,那就是:对象。在 javaScript 中,每个对象都有一个指向它的原型(prototype)对象的内部链接。这个原型对象又有自己的原型,直到某个对象的原型为 null 为止(也就是不再有原型指向),组成这条链的最后一环。这种一级一级的链结构就称为原型链(prototype chain)原型链Wiki
我们创建每个函数都有一个原型属性,这个属性指是一个指针,指向一个对象,而这个对象的用途包含由特定类型的所有实例共享的属性和方法
function Person(){}
Person.prototype.name = 'freax';
Person.prototype.age = 18;
Person.prototype.job = 'javascript';
Person.prototype.sayName = function () {
console.info(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.sayName(); //freax
person2.sayName(); //freax
console.info(person1.sayName == person2.sayName); //true
理解原型
使用Person.isPrototypeOf() 检查对象间的是否是原型关系
console.info(Person.isPrototypeOf(person1));//true
console.info(Person.isPrototypeOf(person2));//true
在es5 中可以通过Object.getPrototypeOf() 获取到指定对象的原型对象
console.info(Object.getPrototypeOf(person1)); //返回原型对象
console.info(Object.getPrototypeOf(person1).name); //freax 原型对象的name属性
虽然我们可以通过实例可以访问到原型属性的值,但我们不能通过实例重写原型对象属性的值,如下
person1.name = 'freax1';
console.info(person1.name);//freax1
console.info(person2.name);//freax
把person1的name属性设置成freax1 而person2的name属性值依然没有任何变化,而person1是个实例,因此是不能通过实例修改原型属性的
为什么person1没有找到原型上的name属性freax?
当执行person1.name的时候javascript就会搜索实例上是否有同名属性,有则返回,没有则往原型链搜索, person1实例name属性屏蔽了原型上name属性,因此返回freax1
更简单的原型写法
Person.prototype = {
name:"freax",
age:18,
job:'javascript'
};
这样就可以很方便重写原型对象,但是这么做也要付出代价的,分别有以下几点
- 原型的构造属性不再指向Person,因为每创建一个函数,就会同时创建他的原型对象,因此字面量创建的对象也自动获得constructor属性,这个构造属性指向Object的构造函数,
- 对象识别问题,尽管instancenof操作符还能正确返回结果,但是通过constructor无法识别对象类型
var friend = new Person();
console.info(friend instanceof Person);//true
console.info(friend instanceof Object); //true
console.info(friend.constructor == Person.constructor); //false
console.info(friend.constructor == Object);//true
此时instanceof 能正确返回,但是constructor不等与Person如果constructor真的很重要,可以像下面这样设回适当的值
function Person1(){}
Person1.prototype = {
constructor:Person1,
name:'freax'
//........
};
注意:这样导致constructor属性变成可枚举的enumerable的,原生默认是不可枚举的,因此你可以使用兼容ECMAscript5 的javascript引擎
设置成不可枚举, Object.defineProperty()设置属性的特性
function Person2(){}
Person2.prototype = {
constructor:Person2,
name:'freax'
//........
};
重新设置构造属性
Object.defineProperty(Person2.prototype,'constructor',{
enumerable:false,
value:Person2
});
原型的动态性
由于在原型查找值的过程是一次搜索,因此我们在原型对象所做的任何修改都能够在实例上立即反应出来—即先创建对象后修改原型也是如此
var friend = new Person();
Person.prototype.sayHi = function () {
console.info('hi');
};
friend.sayHi();
尽管随时可以为原型添加属性和方法,但是重写整个原型对象的情况就不一样了
function Person3(){}
var friend3 = new Person3();
Person3.prototype = {
constructor:Person3,
name:"freax",
age:12,
sayName: function () {
console.info(this.name);
}
};
friend3.sayName(); ///Uncaught TypeError: friend3.sayName is not a function
原生对象的原型
原型模式的重要性不仅体现在创建自定义类型方面,就连所有引用类型都是采用这种模式创建,原生引用类型(Array,Object,String),通过原生对象的原型不仅取得所有默认引用方法,而且可以定义新方法,可以修改原型对象
var str = 'str';
String.prototype.getLength = function () { //增强原生原型对象
return this.length;
};
console.info(str.getLength());
当然你也可以重写整个原型对象,但是不推荐在产品化的程序上修改原生对象的原型,如果因为某个实现缺少某个方法,
就在原生对象的原型中添加这个方法,那么当一个支持该方法的实现中运行代码时,就会导致命名冲突,这样做也会意外的重写整个原型对象
原型对象的问题
原型模式也并不是没有问题,它省略构造函数的环节,结果是在所有实例中取得相同的值,这还不是原型模式最大的问题,最大问题在于原型共享本性所导致
function Person4(){}
Person4.prototype = {
constructor:Person4,
name:"freax",
age:"29",
job:'javascript',
friend:[1,2,3],
sayNaeme: function () {
console.info('freax');
}
};
var person4 = new Person4();
var person5 = new Person4();
person4.friend.push(4);
console.info(person4.friend);//[1,2,3,4]
console.info(person5.friend);//[1,2,3,4]
假如我们的初衷就是共享这个数组,这没有什么问题,但是没有的人的朋友不可能都是一样的吧!这不是我们期望的,每一个人的朋友总是有些差别的,所以我们不想共享friend属性,这就是原型模式的问题,
说白我们就是想在原型上定义私有属性像java的private一样