JS 学习笔记--13---原型

时间:2023-03-09 01:14:11
JS 学习笔记--13---原型

练习中使用的是IE11,如果有错误之处,还请各位朋友多多指教。本文关于原型难以描述,故多用代码展示

  原型是JS中一个很重要的概念,也是JS中一个难点,语言上难以描述,原型对象的属性和方法叫做原型属性和原型方法,构造函数中的属性和方法叫做实例属性和实例方法,它们的区别就是:对于一个对象的多个实例之间,它们的实例属性和实例方法是各不一样的,前面的面向对象中已经证明,而他们的原型属性和原型方法是一模一样的,完全相等的

构造函数方式声明原型对象:                                                                      

 //构造函数定义的成员变量时实例成员
function Box(user,age){
this.user=user; //实例属性
this.age=age;
this.run=function(){ //实例方法
return this.user+" "+this.age+" "+"运行中...";
}
} //原型声明,构造函数中什么也不写,然后通过prototype对象来添加属性和方法,调用都是一样的
function Box1(){};
Box1.prototype.user='abc'; //通过prototype定义的叫做 原型属性
Box1.prototype.age=22; //下面这个方法叫做原型方法
Box1.prototype.run=function(){ return this.user+" "+this.age+" "+"运行中..."; }; var box=new Box1();
alert(box.user); // abc
alert(box.age); //
alert(box.run()); // abc 22 运行中... //声明了两个对象 从下面的结果可以知道,这两个对象的引用不相等,但是他们中间的方法和属性是完全相等的 包括引用
var box1=new Box();
var box2=new Box();
alert(box1.user); //abc
alert(box2.user); //abc
alert(box1.age); //
alert(box2.age); // //如果是实例方法,不同的实例化,他们的方法的引用地址是不一样的,是唯一的
//但是原型对象,不同的实例化,他们的方法的引用地址也是一样的,共享的,大家都一样
alert(box1.run==box2.run); // true
alert(box1==box2); //false

JS 学习笔记--13---原型

JS 学习笔记--13---原型

1、  原型的作用主要作用

  用来共享一些属性和方法,我们每创建一个函数,都会自动的创建一个原型对象,原型对象是由函数下面的一个属性[__proto__]来指向的,这个属性是一个指针,它指向了原型对象的constructor属性,我们可以通过这两个属性就可以访问原型对象中的属性和方法了。

  constructor是一个构造属性,是可以获取构造函数的本身的,它的作用其实就是被原型指针[__proto__]定位,然后获取到构造函数的本身

    alert(Box.prototype);    //访问方法的属性prototype
alert(box1.prototype); //undefined 这个属性是一个对象,是访问不到的
alert(box1.__proto__); //object Object 低版本的IE可能打印不出来
   alert(box1.constructor); //function Box(){}; //构造属性
alert(Box.constructor); // function Function(){...}; Box 本身就是一个Function类型
//alert(box1.prototype.constructor) //error

  

  

2、isPrototypeOf() 方法

  判断一个实例对象是否是指向了该构造函数的原型对象,可以用 isPrototypeOf() 方法来判断,如果指向了返回为true,没有则返回为false,一切对象都是继承自Object对象

     alert(Box.prototype.isPrototypeOf(box1)); //true    只要是实例化的,都指向了原型对象
alert(Box.prototype.isPrototypeOf(box2)); //true
alert(Object.prototype.isPrototypeOf(box1));//true 因为一切对象都是 Object 类型的,故指向了
var box=new Object();
alert(Box.prototype.isPrototypeOf(box)); // false box对象是Object类型的对象,Box其实类似于是继承自Object类型

 

3、原型模型的执行流程:遵循JS中的就近原则

  先查找构造函数实例里面的方法和属性,即先查找实例属性,如果有,立即返回值或者执行对应的方法

  如果实例属性或者实例方法中没有,则去原型对象中查找相应的属性和方法,有就返回或执行方法,如果没有就返回undefined或者报错

     var box1=new Box();
var box2=new Box();
box1.name='kkk'; //给对象box1添加一个实例属性,
alert(box1.name); //访问的是box1的实例属性,box2是访问不到的,因为原型属性中也没有
alert(box2.name);
box1.user='jjj'; //其实是实例属性,并没有重写原型属性的值
alert(box1.__proto__.user);//abc 访问原型属性中的值
alert(box1.user);//jjj 访问的是实例属性中的值
alert(box2.user);//abc box2 中不存在实例属性 user 就返回的是原型属性,它访问不到box1中的实例属性,因为他们之间共享的只是原型属性和方法

4、属性的删除和修改
  可以通过 delete 关键字来删除实例属性和原型属性,删除和修改原型属性可以通过两种方式:(1) 通过实例对象的指针修改:box1.__proto__.age; (2) 定义原型属性一样用构造函数修改 prototype: Box.prototype.age;

  构造函数中是改了就生效,不管何时声明的对象,而在后面的字面量形式中只有在声明实例对象之前该才有效(详细的见后面字面量形式创建中)

     delete box1.user;    //删除对象box1中的实例属性
alert(box1.user); //abc 因为前面删除了实例属性中的user属性,返回的就是原型属性中的user属性
//delete box1.__proto__.age; //通过这种方式可以删除原型对象中的属性,但是别这样弄,牵一发而动全身
//delete Box.prototype.age; //也是删除了原型对象中的属性 age
alert(box2.age); //undefined 因为前面删除了原型属性中的age属性
box1.__proto__.age=33; //修改了原型属性中的值
alert(box2.age); //
Box.prototype.age=44; //修改了原型属性中的值
alert(box2.age); //

5、hasProperty() 方法和 in 操作符

  判断某个对象是否拥有某个实例属性,可以通过 hasOwnProperty() 方法来测试,有就返回true,否则返回false

  in 操作符可以判断对象中是否包含某个属性,不管这个属性是原型属性还是实例属性,包含则返回true

  可以通过 hasOwnProperty() 方法和 in 操作符 共同来判断某个对象是否包含某个原型属性

    var box1 = new Box();
var box2 = new Box(); 1    box1.name='jjj';
2 alert(box1.hasOwnProperty('name')); //true
3 alert(box2.hasOwnProperty('name')); //false box2 并没有实例属性name
4 alert(box1.hasOwnProperty('user')); //false user 属性是原型属性,不是实例属性
5
6 alert('name' in box1); //false
7 alert('user' in box1); //true 原型属性
8 box1.name='jjj';
9 alert('name' in box1); //true 实例属性
10
11 //通过 hasOwnProperty() 方法和 in 操作符 共同来判断某个对象是否包含某个原型属性
12 function checkPropo(object,element){ //判断的是在某个对象中的属性,故要将对象和属性传递过来
13 if(!object.hasOwnProperty(element)){ //先判断是否存在实例属性,如果存在就返回false
14 if(element in object){ //如果存在就返回true
15 return true;
16 }else{
17 return false;
18 }
19 }else{
20 return false;
21 }
22 //上面的代码可以用一个表达式来表示:return !object.hasOwnProperty(element)&& (element in object);
23 }
24
25 alert(checkPropo(box1,'name')); //false
26 box1.name='name';
27 alert(checkPropo(box1,'name')); //false 虽然有属性 name,但是这是一个实例属性
28 alert(checkPropo(box1,'user')); //true //原型属性 user 是存在的

字面量形式创建原型对象:                                                      

     function Box(){};
Box.prototype={ // 通过字面方法来创建原型属性和方法,这样有点封装的感觉
user:'abc',
age:123,
run:function(){return this.user+" "+this.age+" 运行中...";}
} //创建两个对象
var box1=new Box();
var box2=new Box();
//运行结果是一样的
alert(box1.run()); //abc 123 运行中...
alert(box2.run()); //abc 123 运行中...
alert(box1.run==box2.run); //true

6、字面量方式创建原型对象注意的问题一:构造属性的指向

  字面量创建的方式 用constructor 属性指向的是Object对象,而不是实例本身,但是构造函数方式创建的这相反;

  如果想让constructor指向实例[Box],可以采用强制指向的方式

     var box1 = new Box();

     alert(box1.constructor);    //function Object() { [native code] }
alert(box1.constructor == Box); //false
alert(box1.constructor == Object); //true
alert(box1 instanceof Box); //true
alert(box1 instanceof Object); //true
alert(Box.constructor); //function Function...
alert(Box.prototype); //object Object //使用构造函数名(对象名)访问prototype
alert(box1.__proto__); //object Object //使用对象实例访问prototype的指针 // 如果想让constructor指向实例[Box],可以采用强制指向的方式
function Box(){};
Box.prototype={
constructor:Box, //强制原型对象来指向Box
user:'abc',
age:123,
run:function(){return this.user+" "+this.age+" 运行中...";}
} //创建两个对象
var box1=new Box();
var box2=new Box(); //强制constructor指向Box的时候,返回结果如下
alert(box1.constructor == Box); //true
alert(box1.constructor == Object); //false

  之所以出现上面的原因是因为:通过Box.prototype={..};方式创建的时候,都创建一个新的对象,而每次创建一个函数,都会同时创建它自己的prototype,那么这个新的对象也就会自动获取它自己的constructor属性,这样新对象的constructor属性重写了Box实例的constructor属性,因此会执行新的对象,而这个新的对象又没有指定构造函数,故默认的就是Object

7、字面量形式创建原型对象的问题二:原型属性的重写

  原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型,故应该注意避免此种情况的发生

 var box2=new Box();
Box.prototype={ //字面量方式不存在修改原型属性,直接是覆盖掉原来的,因为每次都是创建一个新的对象
user:444 //这里不会保留之前原型的任何信息
//把原来的原型对象和构造函数对象之间的关系给切断了
}
//字面量方式创建,只要声明了,以后随便怎么重写原型对象,已经创建的实例对象的值不会改变
var box1=new Box(); alert(box1.user); // 444 重写中赋值为444
alert(box1.age); //undefined 因为第二次重写中没有这个属性 alert(box2.age);//123 因为在对象的定义是发生在用字面量形式重写原型属性之前,故以后的原型属性的修改和box2无关

9、通过原型模式扩展类型的方法

  原型对象不仅仅可以在自定义对象的情况下使用, 而 ECMAScript 内置的引用类型都可以使用这种方式,并且内置的引用类型本身也使用了原型。

  通过prototype来添加方法,访问的时候要用这种类型的变量点这个方法,和访问系统内置的方法是一样的,但是最好不要用这种方式来添加方法,因为可能会存在命名冲突的问题,特别是在代码量大的时候,容易照成命名冲突问题。

     String.prototype.addString=function(s){        //传递一个参数过来,
return '【' + s + '】';
} alert('abc'.addString('111'));// 【111】 String.prototype.addString=function(){ //可以不进行传参,通过this来代表当前调用这个方法 的字符串
return this+"被添加了!";
} alert("abcd".addString()); // abcd被添加了!

原型模式创建对象的缺点以及采用的方式:                          

  原型模式创建对象也有自己的缺点, 它省略了构造函数传参初始化这一过程, 带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。

  原型中所有的属性都能被很多实例共享,共享对于函数非常适合,对于包含基本值的属性页还可以,但是如果属性包含引用类型就一定存在问题,见代码中

 function Box(){};
Box.prototype={
constructor:Box,
user:'abc',
age:22,
family:['哥哥','姐姐','妹妹'],
run:function(){
return this.user+" "+this.age+" 运行中...";
}
} //var box=new Box(123); //缺点之一就是不能够传递参数 var box1=new Box(); //缺点二就是原型的共享性,这也是它最大的优点
alert(box1.family); //哥哥,姐姐,妹妹
box1.family.push('弟弟'); //在第一个实例后修改了引用类型,保持了共享,但本质上不希望它共享
alert(box1.family); //哥哥,姐姐,妹妹,弟弟 var box2=new Box();
alert(box2.family); //哥哥,姐姐,妹妹,弟弟 共享了box1引用类型添加后的原型

11、组合构造函数 + 原型模式

  这种方式能够很好的解决传参和引用共享的问题,是创建对象比较好的方法

 function Box(user,age){    //构造函数,这里面声明一些会变的属性和引用类型的属性
this.user=user;
this.age=age;
this.family=['哥哥','姐姐','妹妹'];
} Box.prototype={ //原型模式
constructor:Box,
run:function(){
return this.user+" "+this.age+" 运行中...";
}
} //下面可以看出通过构造函数总写一些自己会变的属性等,即使值改变了也不会被共享出去
var box1=new Box('abc',22);
alert(box1.run()); // abc 22 运行中...
alert(box1.family); //哥哥,姐姐,妹妹
box1.family.push("弟弟");
alert(box1.family); //哥哥,姐姐,妹妹,弟弟 var box2=new Box('jack',33);
alert(box2.family); //哥哥,姐姐,妹妹 并没有共享对象box1中的引用类型
alert(box2.run()); //jack 33 运行中... 和box1 中的输出结果是不一样的

11、动态原型模型

  将构造函数和原型模型封装在一起,也能够解决共享的问题,但是要注意两个问题,一是资源的浪费,还有就是要注意,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系

  在下面的代码中,当第一次调用构造函数时,run()方法发现不存在,然后初始化原型。当第二次调用, 就不会初始化, 并且第二次创建新对象, 原型也不会再初始化了。 这样及得到了封装, 又实现了原型方法共享,并且属性都保持独立

function Box(user,age){
this.user=user;
this.age=age;
this.family=['哥哥','姐姐','妹妹']; if(typeof this.run != 'function'){//判断this.run是否存在,因为执行一次后类型返回值就为为function
alert("原型初始化开始");//没有判断之前,每new一次Box都会执行一次,
Box.prototype.clss='person';
Box.prototype.run=function(){
return this.user+" "+this.age+" 运行中...";
};
alert("原型初始化结束");
}
} var box1=new Box('abc',22);
var box2=new Box('jack',33);
//也可以判断原型中任意一个属性的类型返回值,是否等于undefined,若等于原型就还没有创建
//因为原型中的属性一般都是一开始就有特定的值的,除非故意赋值为undefined[这样没意义了]
function Box(user,age){
this.user=user;
this.age=age;
this.family=["哥哥","姐姐","妹妹"]; if(typeof this.clss == "undefined"){
alert("原型初始化开始");//没有判断之前,每new一次Box都会执行一次,
Box.prototype.clss="person";
Box.prototype.run=function(){
return this.user+" "+this.age+" 运行中...";
};
alert("原型初始化结束");
}
} var box1=new Box('abc',22);
var box2=new Box('jack',33);

12、寄生构造函数

  寄生构造函数,其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对象关系,所以,在可以使用之前所说的模式时,不建议使用此模式;在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类型。由于之前说明不建议直接 String.prototype.addstring,可以通过寄生构造的方式添加

    function myString(string){
var str = new String(string);
str.addString = function(){
return this + ",被添加了!";//this 指的是对象str下的值string
}
return str;
} var box = new myString("abcd");
alert(box.addString());

13、稳妥构造函数

  在一些安全的环境中, 比如禁止使用 this 和 new, 这里的 this 是构造函数里不使用 this,这里的 new 是在外部实例化构造函数时不使用 new。这种创建方式叫做稳妥构造函数。

     function Box(name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.run = function () {
return name + age + '运行中...';
};
return obj;
} var box1 = Box('Lee', 100);
alert(box1.run()); var box2 = Box('Jack', 200);
alert(box2.run());

用的最多的应该是组合原型模式+构造函数以及动态原型模式