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 相关文章推荐
WebGame《逆转裁判》完整版 代码下载(1月24日更新)
Jan 29 Javascript
用js判断页面刷新或关闭的方法(onbeforeunload与onunload事件)
Jun 22 Javascript
Javascript 中 null、NaN和undefined的区别总结
Apr 10 Javascript
JavaScript中window.open用法实例详解
Apr 15 Javascript
jQuery中$.ajax()和$.getJson()同步处理详解
Aug 12 Javascript
整理AngularJS框架使用过程当中的一些性能优化要点
Mar 05 Javascript
AngularJS在IE8的不支持的解决方法
May 13 Javascript
妙用缓存调用链实现JS方法的重载
Apr 30 Javascript
vue: WebStorm设置快速编译运行的方法
Oct 18 Javascript
在vue中使用express-mock搭建mock服务的方法
Nov 07 Javascript
浅析Vue.js 中的条件渲染指令
Nov 19 Javascript
详解Webpack如何引入CDN链接来优化编译后的体积
Jun 21 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登陆后跳转到登陆前页面实现思路及代码
2014/01/17 PHP
php实现建立多层级目录的方法
2014/07/19 PHP
php判断linux下程序问题实例
2015/07/09 PHP
PHP在同一域名下两个不同的项目做独立登录机制详解
2017/09/22 PHP
基于JQuery实现异步刷新的代码(转载)
2011/03/29 Javascript
解决javascript:window.close()在chrome,Firefox下失效的问题
2013/05/07 Javascript
js输入框邮箱自动提示功能代码实现
2013/12/10 Javascript
Node.js 的异步 IO 性能探讨
2014/10/08 Javascript
JavaScript检测字符串中是否含有html标签实现方法
2015/07/01 Javascript
喜大普奔!jQuery发布 3.0 最终版
2016/06/12 Javascript
Ionic2系列之使用DeepLinker实现指定页面URL
2016/11/21 Javascript
js实现楼层导航功能
2017/02/23 Javascript
jquery编写日期选择器
2017/03/16 Javascript
JavaScript数据结构之链表的实现
2017/03/19 Javascript
Express下采用bcryptjs进行密码加密的方法
2018/02/07 Javascript
jQuery轮播图实例详解
2018/08/15 jQuery
vuejs选中当前样式active的实例
2018/08/22 Javascript
深入学习Vue nextTick的用法及原理
2019/10/08 Javascript
Vue文本模糊匹配功能如何实现
2020/07/30 Javascript
Javascript如何实现扩充基本类型
2020/08/26 Javascript
[02:30]DOTA2放量测试专访海涛:呼吁保护新手玩家
2013/08/26 DOTA
Python基于select实现的socket服务器
2016/04/13 Python
浅谈Python接口对json串的处理方法
2018/12/19 Python
Python编程图形库之Pillow使用方法讲解
2018/12/28 Python
Python3进制之间的转换代码实例
2019/08/24 Python
西班牙香水和化妆品连锁店:Druni
2019/05/05 全球购物
澳大利亚办公室装修:JasonL Office Furniture
2019/06/25 全球购物
外语系大学生自荐信范文
2014/03/01 职场文书
幼儿园课题实施方案
2014/05/14 职场文书
财务管理专业求职信
2014/06/11 职场文书
我的中国梦演讲稿1000字
2014/08/19 职场文书
售后客服个人自我评价
2014/09/14 职场文书
2014年十八届四中全会思想汇报范文
2014/10/17 职场文书
怎样写辞职信
2015/02/27 职场文书
爱的教育观后感
2015/06/17 职场文书
学生会招新宣传语
2015/07/13 职场文书