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
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());
用的最多的应该是组合原型模式+构造函数以及动态原型模式
JS 学习笔记--13---原型的更多相关文章
- JS学习笔记 - 面向对象 - 原型
<script> var arr1 = new Array(12, 55, 34, 78, 676); var arr2 = new Array(12, 33, 1) Array.prot ...
- Python3+Selenium3+webdriver学习笔记13(js操作应用:弹出框无效如何处理)
#!/usr/bin/env python# -*- coding:utf-8 -*-'''Selenium3+webdriver学习笔记13(js操作应用:弹出框无效如何处理)'''from sel ...
- vue.js 学习笔记3——TypeScript
目录 vue.js 学习笔记3--TypeScript 工具 基础类型 数组 元组 枚举 字面量 接口 类类型 类类型要素 函数 函数参数 this对象和类型 重载 迭代器 Symbol.iterat ...
- golang学习笔记13 Golang 类型转换整理 go语言string、int、int64、float64、complex 互相转换
golang学习笔记13 Golang 类型转换整理 go语言string.int.int64.float64.complex 互相转换 #string到intint,err:=strconv.Ato ...
- 【转】Backbone.js学习笔记(二)细说MVC
文章转自: http://segmentfault.com/a/1190000002666658 对于初学backbone.js的同学可以先参考我这篇文章:Backbone.js学习笔记(一) Bac ...
- WebGL three.js学习笔记 6种类型的纹理介绍及应用
WebGL three.js学习笔记 6种类型的纹理介绍及应用 本文所使用到的demo演示: 高光贴图Demo演示 反光效果Demo演示(因为是加载的模型,所以速度会慢) (一)普通纹理 计算机图形学 ...
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
- Vue.js学习笔记(2)vue-router
vue中vue-router的使用:
- JS 学习笔记--9---变量-作用域-内存相关
JS 中变量和其它语言中变量最大的区别就是,JS 是松散型语言,决定了它只是在某一个特定时间保存某一特定的值的一个名字而已.由于在定义变量的时候不需要显示规定必须保存某种类型的值,故变量的值以及保存的 ...
- Ext.Net学习笔记13:Ext.Net GridPanel Sorter用法
Ext.Net学习笔记13:Ext.Net GridPanel Sorter用法 这篇笔记将介绍如何使用Ext.Net GridPanel 中使用Sorter. 默认情况下,Ext.Net GridP ...
随机推荐
- text-rendering 详解
原文链接:http://www.feeldesignstudio.com/2013/05/text-rendering Text-rendering 属性是一个非标准属性,主要用来告诉渲染引擎(ren ...
- jQuery身份证验证插件
jQuery身份证验证插件 /*! * jQuery isIDCard Plugin v1.0.0 * http://www.cnblogs.com/cssfirefly/p/5629561.html ...
- ASP.NET的错误处理机制之二(实例log4net)
一.log4net下载:http://logging.apache.org/log4net/download_log4net.cgi 二.web.config配置如下: <?xml versio ...
- 重拾C,一天一点点_8
这两天发现一个问题,我最近发的几篇博文稀里糊涂地被转到别的网站去了,目前发现有两个网站转载了,一个注明了作者出处(博客园 lltong),但没给任何链接.另一个网站呢,就是直接抓的,而且还抓的乱七八糟 ...
- js实现选项卡功能
1.css .liclick{ border: 1px black solid; background: #fff; float: left; width: 80px; height: 35px; l ...
- 生产WCF客户端类文件的命令格式
生产WCF客户端类文件的命令格式: svcutil.exe net.tcp://127.0.0.1:8732/ChromaMI.Remote.ConfigService/RemoteConfigSer ...
- Oracle存储过程学习备忘
之前的项目使用存储过程很少,但在实际的项目中,存储过程的使用是必不可少的. 存储过程是一组为了完成特定功能的SQL 语句 集,经编译后存储在数据库中:存储过程创建后,一次编译在程序中可以多次调用,对安 ...
- Mapreduce中的字符串编码
Mapreduce中的字符串编码 $$$ Shuffle的执行过程,需要经过多次比较排序.如果对每一个数据的比较都需要先反序列化,对性能影响极大. RawComparator的作用就不言而喻,能够直接 ...
- STM32F0xx_PWR低功耗配置详细过程
Ⅰ.概述 今天总结PWR部分知识,请看“STM32F0x128参考手册V8”第六章.提供的软件工程是关于电源管理中的停机模式,工程比较常见,但也是比较简单的一个实例,根据项目的不同还需要适当修改或者添 ...
- 学一点Git--20分钟git快速上手 [Neil]
From: http://www.cnblogs.com/shuidao/p/3535299.html (图片已修复)在Git如日中天的今天,不懂git都不好意思跟人说自己是程序猿.你是不是早就跃跃欲 ...