JavaScript面向对象知识串结(读JavaScript高级程序设计(第三版))


Posted in Javascript onJuly 17, 2012

第一遍囫囵吞枣,不求甚解,感觉恍然大悟,结果晚上睡觉一想发现很多问题,什么都不明白,再看第二遍,发现原来是这样。过了几天一用,发现手写起来原来还是在凭记忆,于是下一遍,下一遍...

单凭记忆去弄清楚东西很不靠谱,时间一长脑袋空白。特别是技术上的很多思想和原理,只看不练,即便当时想得特别清楚,过久了也会忘。再者就是网上一些东西,只能说是提供了一种便捷的查看途径,事后还是自己总结为好,毕竟大多都是个人总结,一些概念很难讲的很清楚,而且两个人谈同一件事情,一般说的步骤和章节都是不同的,这样很容易形成交叉记忆,越多交叉记忆越混乱。还是持怀疑的态度看东西好一点,动手试一下就知道到底是怎么个样子,知识串一下。高质量有保证的书或者官方的有些东西,是不错的来源。

趁自己这会看得还算明白,脑袋还算清楚,记录一下,做个备忘。概念性的东西是书上的,减少日后误导。例子手写加验证,再画个图,以便以后一看就明白。

一、封装

对象定义:ECMA-262把对象定义为:“无序属性的集合,其中属性可以包括基本值、对象或者函数”。

创建对象:每个对象都是基于一个引用类型创建的,这个引用类型可以是原生类型(Object, Array, Date, RegExp, Function, Boolean, Number, String),也可以是自定义类型。

1、构造函数模式

function Person(name, age) { 
this.name = name; 
this.age = age; 
this.sayName = function() { 
alert(this.name); 
} 
} 
通过以上构造函数使用new操作符可以创建对象实例。 
var zhangsan = new Person('zhangsan', 20); 
var lisi = new Person('lisi', 20); 
zhangsan.sayName();//zhangsan 
lisi.sayName(); //lisi

通过new创建对象经历4个步骤

1、创建一个新对象;[var o = new Object();]

2、将构造函数的作用域赋给新对象(因此this指向了这个新对象);[Person.apply(o)] [Person原来的this指向的是window]

3、执行构造函数中的代码(为这个新对象添加属性);

4、返回新对象。

通过代码还原new的步骤:

function createPerson(P) { 
var o = new Object(); 
var args = Array.prototype.slice.call(arguments, 1); 
o.__proto__ = P.prototype; 
P.prototype.constructor = P; 
P.apply(o, args); 
} 
测试新的创建实例方法 
var wangwu = createPerson(Person, 'wangwu', 20); 
wangwu.sayName();//wangwu

2、原型模式

原型对象概念:无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。而通过这个构造函数,可以继续为原型对象添加其他属性和方法。创建了自定义的构造函数后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都从 Object 继承而来。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262第5版管这个指针叫 [[Prototype]] 。脚本中没有标准的方式访问 [[Prototype]],但Firefox、Safari和Chrome在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于示例和构造函数的原型对象之间,而不是存在于实例和构造函数之间。

这段话基本概述了构造函数、原型、示例之间的关系,下图表示更清晰
JavaScript面向对象知识串结(读JavaScript高级程序设计(第三版))

function Person(name, age) { 
this.name = name; 
this.age = age; 
} 
Person.prototype.country = 'chinese'; 
Person.prototype.sayCountry = function() { 
alert(this.country); 
} var zhangsan = new Person('zhangsan', 20); 
var lisi = new Person('lisi', 20); 
zhangsan.sayCountry(); //chinese 
lisi.sayCountry(); //chinese 
alert(zhangsan.sayCountry == lisi.sayCountry); //true

注意地方:构造函数的原型对象,主要用途是让多个对象实例共享它所包含的属性和方法。但这也是容易发生问题的地方,如果原型对象中包含引用类型,那么应引用类型存的是指针,所以会造成值共享。如下:
Person.prototype.friends = ['wangwu']; //Person添加一个数组类型 
zhangsan.friends.push('zhaoliu'); //张三修改会对李四造成影响 
alert(zhangsan.friends); //wangwu,zhaoliu 
alert(lisi.friends); //wangwu,zhaoliu李四也多了个

3、组合使用构造函数模式和原型模式

这种模式是使用最广泛、认同度最高的一种创建自定义类型的方式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。这样,每个实例都有自己的一份实例属性的副本,同时有共享着对方法的引用,最大限度的节省了内存。

原型模式改造后的如下:

function Person(name, age) { 
this.name = name; 
this.age = age; 
this.friends = ['wangwu']; 
} Person.prototype.country = 'chinese'; 
Person.prototype.sayCountry = function() { 
alert(this.country); 
} 
var zhangsan = new Person('zhangsan', 20); 
var lisi = new Person('lisi', 20); 
zhangsan.friends.push('zhaoliu'); 
alert(zhangsan.friends); //wangwu,zhaoliu 
alert(lisi.friends); //wangwu

二、继承

继承基本概念

ECMAScript主要依靠原型链来实现继承(也可以通过拷贝属性继承)。

原型链基本思想是,利用原型让一个引用类型继承另外一个引用类型的属性和方法。构造函数、原型、示例的关系是:每个构造函数都有一个原型对象,原型对象都包含了一个指向构造函数的指针,而实例都包含了一个指向原型的内部指针。所以,通过过让原型对象等于另外一个类型的实例,此时原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含这一个指向另一个构造函数的指针。假如另外一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例和原型的链条。这就是原型链的基本概念。

读起来比较绕,不容易理解。直接通过实例说明验证。

1、原型链继承

function Parent() { 
this.pname = 'parent'; 
} 
Parent.prototype.getParentName = function() { 
return this.pname; 
} function Child() { 
this.cname = 'child'; 
} 
//子构造函数原型设置为父构造函数的实例,形成原型链,让Child拥有getParentName方法 
Child.prototype = new Parent(); 
Child.prototype.getChildName = function() { 
return this.cname; 
} 
var c = new Child(); 
alert(c.getParentName()); //parent

图解:
JavaScript面向对象知识串结(读JavaScript高级程序设计(第三版))

原型链的问题,如果父类中包括了引用类型,通过Child.prototype = new Parent()会把父类中的引用类型带到子类的原型中,而引用类型值的原型属性会被所有实例共享。问题就回到了[一、2]节了。

2、组合继承-最常用继承方式

组合继承(combination inheritance),是将原型链和借用构造函数(apply, call)的技术组合到一块。思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样既可以在原型上定义方法实现了函数的复用,又能保证每个实例都有它自己的属性。

function Parent(name) { 
this.name = name; 
this.colors = ['red', 'yellow']; 
} 
Parent.prototype.sayName = function() { 
alert(this.name); 
} function Child(name, age) { 
Parent.call(this, name); //第二次调用Parent() 
this.age = age; 
} 
Child.prototype = new Parent(); //第一次调用Parent(),父类的属性会 
Child.prototype.sayAge = function() { 
alert(this.age); 
} 
var c1 = new Child('zhangsan', 20); 
var c2 = new Child('lisi', 21); 
c1.colors.push('blue'); 
alert(c1.colors); //red,yellow,blue 
c1.sayName(); //zhangsan 
c1.sayAge(); //20 
alert(c2.colors); //red,yellow 
c2.sayName(); //lisi 
c2.sayAge(); //21

组合继承的问题是,每次都会调用两次超类型构造函数:第一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。这样就会造成属性的重写 ,子类型构造函数中包含了父类的属性,而且子类的原型对象中也包含了父类的属性。

3、寄生组合继承-最完美继承方式

所谓寄生组合继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。 其背后的基本思路是:不必为了指定子类的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本

function extend(child, parent) { 
var F = function(){}; //定义一个空的构造函数 
F.prototype = parent.prototype; //设置为父类的原型 
child.prototype = new F(); //子类的原型设置为F的实例,形成原型链 
child.prototype.constructor = child; //重新指定子类构造函数指针 
} function Parent(name) { 
this.name = name; 
this.colors = ['red', 'yellow']; 
} 
Parent.prototype.sayName = function() { 
alert(this.name); 
} 
function Child(name, age) { 
Parent.call(this, name); 
this.age = age; 
} 
extend(Child, Parent); //实现继承 
Child.prototype.sayAge = function() { 
alert(this.age); 
} 
var c1 = new Child('zhangsan', 20); 
var c2 = new Child('lisi', 21); 
c1.colors.push('blue'); 
alert(c1.colors); //red,yellow,blue 
c1.sayName(); //zhangsan 
c1.sayAge(); //20 
alert(c2.colors); //red,yellow 
c2.sayName(); //lisi 
c2.sayAge(); //21
Javascript 相关文章推荐
Javascript Cookie读写删除操作的函数
Mar 02 Javascript
上传图片预览JS脚本 Input file图片预览的实现示例
Oct 23 Javascript
JQUERY实现网页右下角固定位置展开关闭特效的方法
Jul 27 Javascript
javascript中Date format(js日期格式化)方法小结
Dec 17 Javascript
JavaScript的String字符串对象常用操作总结
May 26 Javascript
JS实现六边形3D拖拽翻转效果的方法
Sep 11 Javascript
JavaScript实现图像模糊化的方法实例
Jan 15 Javascript
Bootstrap4如何定制自己的颜色和风格
Feb 26 Javascript
JS获取指定月份的天数两种实现方法
Jun 22 Javascript
深入理解Vue 的钩子函数
Sep 05 Javascript
在Vue项目中使用snapshot测试的具体使用
Apr 16 Javascript
JavaScript编写开发动态时钟
Jul 29 Javascript
JavaScript面向对象(极简主义法minimalist approach)
Jul 17 #Javascript
基于jQuery的获取标签名的代码
Jul 16 #Javascript
写自已的js类库需要的核心代码
Jul 16 #Javascript
js不完美解决click和dblclick事件冲突问题
Jul 16 #Javascript
js jquery数组介绍
Jul 15 #Javascript
js限制文本框只能输入数字(正则表达式)
Jul 15 #Javascript
基于jquery的图片幻灯展示源码
Jul 15 #Javascript
You might like
学习使用PHP数组
2006/10/09 PHP
微信公众号开发之微信公共平台消息回复类实例
2014/11/14 PHP
Zend Framework教程之Loader以及PluginLoader用法详解
2016/03/09 PHP
PHP实现微信JS-SDK接口选择相册及拍照并上传的方法
2016/12/05 PHP
setInterval 和 setTimeout会产生内存溢出
2008/02/15 Javascript
由document.body和document.documentElement想到的
2009/04/13 Javascript
Prototype Template对象 学习
2009/07/19 Javascript
公共js在页面底部加载的注意事项介绍
2013/07/18 Javascript
JQGrid的用法解析(列编辑,添加行,删除行)
2013/11/08 Javascript
JavaScript和JQuery的鼠标mouse事件冒泡处理
2015/06/19 Javascript
快速解决Canvas.toDataURL 图片跨域的问题
2016/05/10 Javascript
浅析JavaScript Array和string的转换(推荐)
2016/05/20 Javascript
为JQuery EasyUI 表单组件增加焦点切换功能的方法
2017/04/13 jQuery
深入浅析Vue不同场景下组件间的数据交流
2017/08/15 Javascript
Vue-cli项目部署到Nginx服务器的方法
2019/11/01 Javascript
vue项目前端微信JSAPI与外部H5支付相关实现过程及常见问题
2020/04/14 Javascript
JavaScript常用工具函数汇总(浏览器环境)
2020/09/17 Javascript
centos系统升级python 2.7.3
2014/07/03 Python
python中urllib模块用法实例详解
2014/11/19 Python
python机器学习实战之K均值聚类
2017/12/20 Python
python通过tcp发送xml报文的方法
2018/12/28 Python
树莓派采用socket方式文件传输(python)
2019/06/22 Python
win7下 python3.6 安装opencv 和 opencv-contrib-python解决 cv2.xfeatures2d.SIFT_create() 的问题
2019/10/24 Python
pytorch梯度剪裁方式
2020/02/04 Python
Python利用matplotlib绘制折线图的新手教程
2020/11/05 Python
整理HTML5移动端开发的常用触摸事件
2016/04/15 HTML / CSS
PHP如何设置和取得Cookie值
2015/06/30 面试题
领导的自我鉴定
2013/12/28 职场文书
公务员保密承诺书
2014/03/27 职场文书
情人节寄语大全
2014/04/11 职场文书
植树节口号
2014/06/21 职场文书
代理人委托书
2014/08/01 职场文书
医者仁心观后感
2015/06/17 职场文书
在CSS中映射鼠标位置并实现通过鼠标移动控制页面元素效果(实例代码)
2021/04/22 HTML / CSS
详解使用 CSS prefers-* 规范提升网站的可访问性与健壮性
2021/05/25 HTML / CSS
windows10声卡驱动怎么安装?win10声卡驱动安装操作步骤教程
2022/08/05 数码科技