原型
对原型的一些理解
1. 原型的概念:原型是一种对象,其他对象可以通过它来实现属性和方法的继承。
2. 任何对象都可以成为原型,所有的对象在默认的情况下都有一个原型,因为原型本身也是对象,所以每个原型自身又有一个原型(只有一种例外,默认的对象原型在原型链的顶端,即 Object 对象的原型null).
3. 我们可以通过给原型添加属性和方法来给对象添加属性或方法!
比如:
Person.prototype.age;
Person.prototype.speak = function(){"添加对象的方法其实就是添加函数"}
4. Person是一个对象,它有一个prototype的原型属性(因为所有的对象都一prototype原型!)prototype属性有自己的prototype对象,而pototype对象肯定也有自己的constuct属性,construct属性有自己的constuctor对象,而这个constructor对象就是我们构造出来的function函数本身!
![原型的内存图](/attachments/image/20180127/1517039796141786.png "原型的内存图")
5. 我们也可以创建一个构造函数Person,然后通过它来new一个对象,这时可以发现在它这个对象里面找到一个类似于原型的东西[[__proto__]],其实它是对象原型对函数原型的属性的引用,但同时它其实却是不可见的,但我们的搜索引擎提供了一个方法可以获取它的存在,通过下面的代码我们可以更好地理解它:
function Person(){}
Person.prototype.age = 20;
var p1 = new Person();//创建p1对象
console.log(p1.__proto__ === Person.prototype); //true
console.log(p1.__proto__.constructor === Person); //true
console.log(Person.prototype.constructor === p1.__proto__.constructor); //true
var p2=new Person();
Person.prototype.gilrs=["张三","李四"];
// p1.gilrs.push("小明");//push()是在原基础上添加内容,而不是改变位置再创建,所以它的原型内容改变了
p1.gilrs=[]; //只是在内存里重新创建了一个位置,它的原型不会改变
console.log(p2.gilrs);
说明:这个对象的__proto__与原型是一样的.
总结:每个函数都有一个 prototype 的对象属性,对象内有一个 constructor 属性,默认指向函数本身;每个对象都有一个__proto__的属性,属性指向其父类型的prototype;构造函数和普通函数并没有区别,使用 new 关键字调用就是构造函数,使用构造函数可以 实例化 一个对象.
![](//atts.w3cschool.cn/attachments/image/20180127/1517040934436086.png)
6.如果我更改了构造函数的原型,已经存在的该构造函数的实例也不一定获得构造函数的最新版本,如果修改的是原型属性,那么这样的改变将会发生。如下代码:因为在a实际被创建之后,a.__proto__是一个对A.prototype 的一个引用。
var A= function(name){
this.name = name;
}
var a = new A('alpha');
a.name; //'alpha'
A.prototype.x = 23;
a.x; //23
实例对象a的原型(a.__proto__)是对函数A的原型属性(A.prototype)的引用,所以如果修改的是A的原型属性,
改变将影响由A创建的对象实例a .在下面的例子中,是对函数A的原型进行了修改,但是并没有反应到A所创建的实例a中
var A = function(name){
this.name = name;
}
var a = new A(‘alpha’);
a.name; //’alpha’
A.__proto__.y = 19880716;
a.y; //undefined
但是如果我现在替换A的原型属性为一个新的对象,实例对象的原型a.__proto__却仍然引用着原来它被创建时A的原型属性
var A = function(name) {
this.name = name;
}
var a = new A('alpha');
a.name; //'alpha'
A.prototype = {z:23};
a.z; //null
即如果在实例被创建之后,改变了函数的原型属性所指向的对象,也就改变了创建实例时实例原型所指向的对象,但是这并不会影响已经创建的实例的原型。
对原型的判断
1. hasOwnProperty():用来判断一个对象是否有你给出名称的属性或对象,如果这个属性存在于这个实例对象中才返回true。此方法无法检查该对象的原型链中是否具有该属性,所以这个要拿来判断的属性必须是对象本身的一个成员,如果这个属性不是在对象本身上那么他就会再到它的原型链上去寻找了。
2.in判断的是对象的所有属性,包括对象实例及其原型的属性。就是说它先找对象里的属性,如果找不到那么它就沿着这个对象的原型链往上找,只要找到了就会返回true;
总结起来就是:hasOwnProperty():只在实例对象身上判断是否有这个属性存在,而in不仅会先在这个实例对象里判断,还会在判断不出来的时候(在实例对象里找不到那个属性)沿着这个对象的原型链往上寻找来判断是否有这个属性。看下面我封装了一个函数来判断一个实例对象它里面是否有那个属性:
function Person(name, age){
this.name = name;
this.age = age;
this.speak = function (){
console.log("我的名字是:" + this.name);
}
}
Person.prototype.sex = "男";
var p1 = new Person("李四", 20);
console.log(isExist(p1, "age1"));
//封装一个函数,判断一个实例对象它里面是否有那个属性
function isExist(obj, proName){
if(p1.hasOwnProperty(proName)){
return "在对象上";
}else if(proName in p1){
return "在原型上";
}else{
return "不存在";
}
原型的应用
利用组合模式来创建对象:把属性放进构造函数里,把方法放在原型上,实现让数据属性每个一份(独享),方法共享.
function Person(name,age) {
this.name=name;
this.age=age;
}
//利用原型来共享方法
/* Person.prototype.speak=function () {
console.log(this.name);
}
Person.prototype.eat=function () {
console.log(this.age);
}*/
//也可以利用原型替换法封装原型的方法
Person.prototype={
constructor:Person,
speak:function () {
console.log(this.name);
},
eat:function () {
console.log(this.age);
}
}
var p1=new Person("李四",20);
var p2=new Person("张三",30);
p1.speak();
p2.speak();
p1.eat();
p2.eat();