“我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。”
引用类型才具有prototype属性,包含:
1 var fn=new String("text"); 2 String.prototype.value="val"; 3 console.log(fn.value); //val
1 function Person(name){ 2 this.name=name; 3 } 4 Person.prototype.getName = function() { 5 return this.name; 6 }; 7 var p1 = new Person("Evan"); 8 9 console.log(p1.name); //Evan 10 console.log(p1.getName()); //Evan
这个prototype属性也就相当于动态添加属性或方法
再看一个更详细的例子:
1 function Person(){ 2 } 3 Person.prototype.name = "Nicholas"; 4 Person.prototype.age = 29; 5 Person.prototype.job = "Software Engineer"; 6 Person.prototype.sayName = function(){ 7 console.log(this.name) 8 }; 9 var person1 = new Person(); 10 var person2 = new Person(); 11 person2.sayName(); //Nicholas 12 console.log(person1.sayName == person2.sayName); //true
一张图看各对象的关系:
Person里面只有一个prototype属性,指向原型对象。原型对象中constructor指向它的构造函数(它的来源),和其他原型属性方法。
Person.prototype就是原型 isPrototypeOf确定二者是否有关系,Object.getPrototypeOf获取原型值
1 console.log(Person.prototype); //Object {name: "Nicholas", age: 29, job: "Software Engineer"} (原型对象) 2 console.log(Person.prototype.isPrototypeOf(person1)) //true; 3 console.log(Object.getPrototypeOf(person1).name) //Nicholas; 4 console.log(Person.prototype.constructor == Person); //true
若将上例稍改一下,给实例化的person1添加name属性并赋值: name:me
1 ↑ 2 person1.name = "me"; 3 4 //先在实例中找,没有再到原型中找 5 console.log(person1.name);//me
console.log(person1.constructor == Person);//true 6 7 //用hasOwnPrototy()检测是否在实例中,为false说明属性来自原型 8 console.log(person1.hasOwnProperty("name")) ;//true 9 console.log(person2.hasOwnProperty("name")) ;//false 10 11 //用一个in,检测是否有次属性,无论在实例还是原型中 12 console.log("name" in person1) ;//true 13 console.log("name" in person2) ;//true
若更改整个 Person.prototype:
1 Person.prototype = {xx:"xx"}; 2 person1.xx; //undefined 3 var ppp=new Person(); 4 ppp.xx; //输出:xx
只会改变后面实例的对象,之前的不会改变。之前引用的不会被垃圾清理。
person1和person2是实例化的Person,也能访问Person的原型对象,用指针[[Prototype]]来实现,我们不可操作[[Prototype]],但可以有另一个__proto__来访问。
接上例。
1 ↑ 2 console.log(person1.prototype);//undefined 3 console.log(Person.prototype);//Object {name: "Nicholas", age: 29, job: "Software Engineer"} 4 console.log(person1.__proto__);//Object {name: "Nicholas", age: 29, job: "Software Engineer"} 5 console.log(Person.__proto__);//function(){}
实例化对象调用原型对象,是__proto__指针,不是prototype属性。它本身没有原型对象,是去调用构造函数的原型对象。
当构造函数(Person)调用__proto__指针时,返回它本身。
__proto__与prototype的区别:(IE不支持__proto__)
__proto__:对象的内部原型的引用。
prototype :返回是对象的原型。
当我们访问一个对象的属性 时,如果这个对象内部不存在这个属性,那么他就会去__proto__里找这个属性,这个__proto__又会有自己的__proto__,于是就这样 一直找下去,也就是我们平时所说的原型链的概 念。所以__proto__是联系各对象的桥梁。
1 var Person = function () { }; 2 var p = new Person(); 3 alert(p.__proto__ === Person.prototype); //true
这个例子很容易理解,实例p的来源就是Person的原型
看一个复杂的例子:
1 var Person = function () { }; 2 Person.prototype.Say = function () { 3 alert("Person say"); 4 } 5 Person.prototype.Salary = 50000; 6 7 var Programmer = function () { }; 8 Programmer.prototype = new Person(); 9 Programmer.prototype.WriteCode = function () { 10 alert("programmer writes code"); 11 }; 12 Programmer.prototype.Salary = 500; 13 14 var p = new Programmer(); 15 p.Say(); 16 p.WriteCode(); 17 alert(p.Salary);
我们来做这样的推导:
var p=new Programmer()可以得出p.__proto__=Programmer.prototype;
而在上面我们指定了Programmer.prototype=new Person();我们来这样拆分,var p1=new Person();Programmer.prototype=p1;那么:
p1.__proto__=Person.prototype;
Programmer.prototype.__proto__=Person.prototype;
由根据上面得到p.__proto__=Programmer.prototype。可以得到p.__proto__.__proto__=Person.prototype。
好,算清楚了之后我们来看上面的结果,p.Say()。由于p没有Say这个属性,于是去p.__proto__,也就是 Programmer.prototype,也就是p1中去找,由于p1中也没有Say,那就去p.__proto__.__proto__,也就是 Person.prototype中去找,于是就找到了alert(“Person say”)的方法。
(参考:http://rockyuse.iteye.com/blog/1426510)
1 var student = {name:'aaron'}; 2 console.log(student.__proto__);//Object {}
JS中的所有对象都是继承自Object对象,所以这里来源是Object {}
关于继承
1 function Animal(){} 2 Animal.prototype.say=function(){return "www"}; 3 function Person(){} 4 Person.prototype.say=function(){return "hello"}; 5 Person.prototype = new Animal(); 6 var p2 = new Person(); 7 p2.say(); //www
当一个对象的prototype指向另一对象时,就实现了继承。如例子中Person继承Animal
此时,要注意,父类和子类都有自己的prototype。继承后,子类的实例对象构造函数就指向父类了。如下
1 ↑ 2 console.log(p2.constructor);//function Animal(){} 3 console.log(Object.getPrototypeOf(Person.prototype).constructor) ;//function Animal(){}
按道理这很正常,理应如此。但有时不是我们想要的结果。如上例子,虽然继承了父类,但 p2.say()我想让它输出子类的结果,那怎么弄呢
只需在继承后重写即可
1 function Animal(){} 2 Animal.prototype.say=function(){return "www"}; 3 function Person(){} 4 Person.prototype.say=function(){return "hello"}; 5 Person.prototype = new Animal(); 6 Person.prototype.say=function(){return "i am people"} //这里重写父类函数 7 var p2 = new Person(); 8 9 p2.say(); //i am people
引用类型的问题:
1 function SuperType(){this.color = ["red","blue","green"]} 2 function SubType(){ 3 } 4 SubType.prototype=new SuperType(); 5 var s1=new SubType(); 6 s1.color.push("black"); 7 console.log(s1.color);//["red", "blue", "green", "black"] 8 var s2=new SubType(); 9 console.log(s2.color);//["red", "blue", "green", "black"]
s1和s2是子类的实例化,s1把继承父类的color属性进行添加,按理说只能s1自己添加。结果把子类实例化出的其他对象的属性都改了。
SubType.prototype=new SuperType();相当于:子类是父类的一个实例,子类自己拥有了color属性。但实例化子类时,s1和s2共享color属性,导致更改时一起改了。
这样肯定不合常理。更改
1 function SuperType(){this.color = ["red","blue","green"]} 2 function SubType(){ 3 SuperType.call(this); 4 } 5 SubType.prototype=new SuperType(); 6 var s1=new SubType(); 7 s1.color.push("black"); 8 console.log(ss.color); //["red", "blue", "green", "black"] 9 var s2=new SubType(); 10 console.log(s2.color); //["red", "blue", "green"]
call()函数在闭包时有讲,把某个函数绑定到某个对象中。在这里,就相当于把父类函数拿过来,在自己作用域调用,借用构造函数,也相当于重写了父类。
所以每次实例化子类,都要调用子类重写的函数,进行一次分配,每个实例拥有自己的color属性。互不干扰。
或者这样继承:
1 function Person(){} 2 Person.prototype.language="chinese"; 3 Person.prototype.eat= function () { 4 console.log("eating"); 5 } 6 function Programmer(){} 7 Programmer.prototype=Object.create(Person.prototype);//js原生复制函数 8 Programmer.prototype.constructor=Programmer; //指回子类自己 9 Programmer.prototype.language="javascript";//覆盖父类
此文章是学习小结,如有不正,望指正。