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 相关文章推荐
使用js实现关闭js弹出层的窗口
Feb 10 Javascript
纯js实现遮罩层效果原理分析
May 27 Javascript
浅析javascript的间隔调用和延时调用
Nov 12 Javascript
javascript继承机制实例详解
Nov 20 Javascript
浅谈javascript 归并方法
Jan 21 Javascript
jQuery实现表格隔行及滑动,点击时变色的方法【测试可用】
Aug 20 Javascript
JavaScript中Number对象的toFixed() 方法详解
Sep 02 Javascript
JavaScript 是什么意思
Sep 22 Javascript
Angular工具方法学习
Dec 26 Javascript
详解angular2采用自定义指令(Directive)方式加载jquery插件
Feb 09 Javascript
JS笛卡尔积算法与多重数组笛卡尔积实现方法示例
Dec 01 Javascript
详解bootstrap-fileinput文件上传控件的亲身实践
Mar 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
discuz Passport 通行证 整合笔记
2008/06/30 PHP
yii2使用ajax返回json的实现方法
2016/05/14 PHP
什么是PHP7中的孤儿进程与僵尸进程
2019/04/14 PHP
Laravel框架验证码类用法实例分析
2019/09/11 PHP
JS trim去空格的最佳实践
2011/10/30 Javascript
不同的jQuery API来处理不同的浏览器事件
2012/12/09 Javascript
jQuery调用AJAX时Get和post公用的乱码解决方法实例说明
2013/06/04 Javascript
分享33个jQuery与CSS3实现的绚丽鼠标悬停效果
2014/12/15 Javascript
jQuery中append()方法用法实例
2015/01/08 Javascript
javascript获取网页各种高宽及位置的方法总结
2016/07/27 Javascript
轻松掌握JavaScript代理模式
2016/08/26 Javascript
jQuery实现页面顶部下拉广告
2016/12/30 Javascript
bootstrap datepicker插件默认英文修改为中文
2017/07/28 Javascript
vue中component组件的props使用详解
2017/09/04 Javascript
Bootstrap 树控件使用经验分享(图文解说)
2017/11/06 Javascript
ng-repeat指令在迭代对象时的去重方法
2018/10/02 Javascript
node.js微信小程序配置消息推送的实现
2019/02/13 Javascript
vue中组件通信详解(父子组件, 爷孙组件, 兄弟组件)
2020/07/27 Javascript
零基础写python爬虫之urllib2中的两个重要概念:Openers和Handlers
2014/11/05 Python
Python使用BeautifulSoup库解析HTML基本使用教程
2016/03/31 Python
Python cookbook(数据结构与算法)从字典中提取子集的方法示例
2018/03/22 Python
Pyorch之numpy与torch之间相互转换方式
2019/12/31 Python
整理HTML5移动端开发的常用触摸事件
2016/04/15 HTML / CSS
联想新西兰官方网站:Lenovo New Zealand
2018/10/30 全球购物
澳大利亚儿童鞋在线:The Trybe
2019/07/16 全球购物
Notino希腊:购买香水和美容产品
2019/07/25 全球购物
小学体育教学反思
2014/01/31 职场文书
副科级后备干部考察材料
2014/05/15 职场文书
人事任命书格式
2014/06/05 职场文书
计划生育宣传标语
2014/06/21 职场文书
村级四风对照检查材料
2014/08/24 职场文书
安全教育第一课观后感
2015/06/17 职场文书
2016优秀青年志愿者事迹材料
2016/02/25 职场文书
python中pandas.read_csv()函数的深入讲解
2021/03/29 Python
一文搞懂Golang 时间和日期相关函数
2021/12/06 Golang
Mysql数据库group by原理详解
2022/07/07 MySQL