原型 prototype
传统构造函数的问题
由于js是解释执行的语言,在代码中出现函数与对象如果重复执行,会创建多个副本
1、在代码中重复执行的代码容易出现重复的对象
2、创建一个Person构造函数,以创建对象,要求创建的对象必须具有name、age、gender属性和公共的sayHello方法
传统代码中出现的问题
// 1
function Person ( name , age , gender ){
this.name = name;
this.age = age;
this.gender = gender;
this.sayHello = function(){
console.log( "...." );
};
}
var p1 = new Person( "jim", 19 , "男" );
var p2 = new Person( "potter" , 20 , "女" );
console.log( p1.sayHello == p2.sayHello ); //false
比较p1和p2的sayHello 方法结果为false,很明显新定义的两个对象的sayHello()方法,创建了两个方法副本。该代码消耗性能,浪费内存资源。
4、传统的构造方法的定义方式会影响性能,容易造成多个对象由多个方法副本。应该将方法单独提取出来,让所有方法的对象共享该方法。
//改良
function sayHello () {
console.log( "......" );
}
function Person ( name , age , gender ){
this.name = name;
this.age = age;
this.gender = gender;
this.sayHello = sayHello;
}
var p1 = new Person( "jim", 19 , "男" );
var p2 = new Person( "potter" , 20 , "女" );
console.log( p1.sayHello == p2.sayHello ); //true
p1.sayHello();
p2.sayHello();*/
改良版为将sayHello()方法定义在function的外部,然后在function内部引用,此时两个对象的sayHello()比较为true,说明为同一个方法副本。
5、可以考虑将方法全部放到外面但是有安全隐患
- 在开发中会引入各种功能框架或库。自定义的成员越多,出现命名冲突的几率越大。
- 可能在开发中会有多个构造函数,每一个构造函数应该有多个方法,那么就会变得的不容易维护。
6、设想:能否由构造函数创建出来的众多的对象共享一个神秘对象中,只需要将共享的东西,重复会多占用内存的东西放到公共神秘对象中,那么由构造函数创建出来的的对象就都可以共享了。通过prototype完全可以实现该设想!
7、原型的相关概念:
首先了解JS中的实例、对象;属性、方法;父子类的定义。
- 实例 ( instance ) 与 对象 ( Object )
- 实例一般是指某一个构造函数创建出来的对象。我们称为 XXX 构造函数的实例
- 实例就是对象,对象是一个泛称。
- 键值对与属性和方法
- 在js中键值对的集合称为对象
- 如果值为数据( 非函数 ),就称该键值对为属性 property
- 如果值为函数( 方法 ),就称该键值对为方法 method
- 父类与子类
- 传统的面向对象语言中使用类来实现继承,那么就有父类,子类的概念
- 父类又称为基类,子类又称为派生类
- 在js中常常称为父对象,子对象。基对象,派生对象
- 创建一个函数,会附带创建一个特殊的对象,该对象使用函数.prototype引用。称其为函数的
原型相关的概念
- 以上6中设想的神秘对象对构造函数称为 “原型属性”;
- 神秘对象就是构造函数的原型属性
- 简称 原型
- 神秘对象与构造函数所创建处理的对象也有一定的关系 创建的对象继承构造函数的原型属性
- 神秘对象针对构造函数所创建出来的对象称为原型对象 简称 原型
- 对象继承自其原型
- 构造函数创建的对象 继承自 构造函数的原型属性
- 构造函数创建的对象 继承自 该对象的原型对象
- 构造函数所创建的出来的对象与构造函数的原型属性所表示的对象是两个不同的对象
- 原型中的成员,可以直接被实例对象所使用
- 也就是说实例对象对象 直接 “含有” 原型中的成员
- 因此 实例对象 继承自 原型
- 这样的继承 就是 ”原型继承“;
8、再回到2中我们讨论的问题:
//利用原型的改良版
function Person ( name ,age , gender ) {
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.sayHello = function () {
console.log( "你好" );
};
var p1 = new Person( "jim", 19 , "男" );
var p2 = new Person( "potter" , 20 , "女" );
console.log( p1.sayHello == p2.sayHello ); //true
p1.sayHello();
p2.sayHello();
很明显这段代码完全符合2中需求,p1和p2的sayHello比较为true,没有引入多余的方法,也没有浪费资源。使用原型的优势,显而易见,但是也存在问题,在实际开发中,由于定义的funciton较多,往往不知道某个对象的构造函数具体是谁?那对象又该如何访问构造函数的原型对象呢?
原型 ( _ proto _)
以前要访问原型,必须使用构造函数来实现,无法直接使用实例对象来访问原型
火狐最早引入属性 _ proto _ 非标准属性,表示使用实例对象引用原型,但是早期是非标准的。
通过该属性可以允许使用实例对象直接访问原型
function Person(){}
//神秘对象 Person.prototype
// 那么只有使用构造函数 才可以访问它
var o = new Person();
// 以前不能直接使用o来访问神秘对象,现在有了(__proto__)(两个下划线)后,可以通过o.__proto__来直接访问神秘对象
o.__proto__ === Person.prototype //true
- 1、神秘对象中默认都有一个属性 “constructor”,翻译为构造器,表示该原型是与什么构造函数联系起来的
- 2、 ’ _ _ proto _ _ '有什么用?
- 可以访问原型
- 由于在开发中,除非特殊要求不要使用实例去修改实例的原型成员,因此此属性开发时应用较少
- 但是在调试过程中非常方便,可以轻易的访问原型、查看原型