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 相关文章推荐
原型方法的不同写法居然会影响调试的解决方法
Mar 08 Javascript
前端开发的开始---基于面向对象的Ajax类
Sep 17 Javascript
javascript窗口宽高,鼠标位置,滚动高度(详细解析)
Nov 18 Javascript
jquery.map()方法的使用详解
Jul 09 Javascript
jQuery插件实现带圆点的焦点图片轮播切换
Jan 18 Javascript
实现隔行换色效果的两种方式【实用】
Nov 27 Javascript
js实现音频控制进度条功能
Apr 01 Javascript
VsCode新建VueJs项目的详细步骤
Sep 23 Javascript
JavaScript实现精美个性导航栏筋斗云效果
Oct 29 Javascript
JavaScript ES2019中的8个新特性详解
Feb 20 Javascript
一起写一个即插即用的Vue Loading插件实现
Oct 31 Javascript
微信小程序实现canvas分享朋友圈海报
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读取xml方法介绍
2013/01/12 PHP
解析php DOMElement 操作xml 文档的实现代码
2013/05/10 PHP
php过滤表单提交的html等危险代码
2014/11/03 PHP
JQueryEasyUI Layout布局框架的使用
2013/04/08 Javascript
Linux下为Node.js程序配置MySQL或Oracle数据库的方法
2016/03/19 Javascript
大型JavaScript应用程序架构设计模式
2016/06/29 Javascript
nodejs连接mongodb数据库实现增删改查
2016/12/01 NodeJs
vue.js实现请求数据的方法示例
2017/02/07 Javascript
JavaScript的继承实现小结
2017/05/07 Javascript
react-router实现跳转传值的方法示例
2017/05/27 Javascript
JS实现上传图片的三种方法并实现预览图片功能
2017/07/14 Javascript
JS设计模式之命令模式概念与用法分析
2018/02/06 Javascript
React 路由懒加载的几种实现方案
2018/10/23 Javascript
简述ES6新增关键字let与var的区别
2019/08/23 Javascript
JS如何生成随机验证码
2020/03/02 Javascript
解决VueCil代理本地proxytable无效报错404的问题
2020/11/07 Javascript
浅析Python中的getattr(),setattr(),delattr(),hasattr()
2016/06/14 Python
python实现12306抢票及自动邮件发送提醒付款功能
2018/03/08 Python
解决Pandas to_json()中文乱码,转化为json数组的问题
2018/05/10 Python
Python import与from import使用及区别介绍
2018/09/06 Python
python修改txt文件中的某一项方法
2018/12/29 Python
详解canvas多边形(蜘蛛图)的画法示例
2018/01/29 HTML / CSS
canvas小画板之平滑曲线的实现
2020/08/12 HTML / CSS
迪拜航空官方网站:flydubai
2017/04/20 全球购物
外包公司软件测试工程师
2014/11/01 面试题
网络维护管理员的自我评价分享
2013/11/11 职场文书
金融事务专业毕业生求职信
2014/02/23 职场文书
婚前协议书怎么写
2014/04/15 职场文书
正科级干部考察材料
2014/05/29 职场文书
感恩祖国演讲稿
2014/09/09 职场文书
个人四风问题对照检查材料
2014/09/26 职场文书
大学生档案自我鉴定(2篇)
2014/10/14 职场文书
大学生自荐书范文
2015/03/05 职场文书
公司员工管理制度
2015/08/04 职场文书
SpringBoot SpringEL表达式的使用
2021/07/25 Java/Android
SpringBoot使用AOP实现统计全局接口访问次数详解
2022/06/16 Java/Android