【北京分院一百七十一期】JS中的面向对象编程

1.背景介绍

什么是面向对象编程?

“面向对象编程”(Object OrientedProgramming,缩写为OOP)是目前主流的编程范式。它的核心思想是将真实世界中各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。

主要概念为:把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)/泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派(dynamicdispatch)。

Javascript是一种基于对象(object-based)的语言,遇到的东西几乎都是对象,但是它不是一种面对对象的语言。像其他语言里面的class(类),它就没办法直接用了。(听说ES 6可以用了,笔者一直学的ES5,6暂未研究,有兴趣的同学可以去看看教程)

2.知识剖析

2.1对象的概念

因为JS是一个基于对象的语言,所以我们遇到的大多数东西几乎都是对象。例如函数就是一个对象,如果你要在js里面新建一个对象,这样写其实就是创建了一个object的实例。对象就是一个容器,封装了属性和方法。属性就是对象的状态,比如下面的name属性。方法就是写在对象里面的函数,也就是对象的行为,比如下面的sayName方法。

var person = new object();

person.name = "Tom";

person.sayNmae = function() {

alert(this.name);

}

2.2 工厂模式
“面向对象编程”的第一步,就是要生成“对象”。但是很多时候我们不得不面临重复生成很多对象的情况,如果我有一千个人要记录他们的信息,像上面这种方法写的话,大大增加了代码的重复量,为了解决这个问题,人们开始使用工厂模式的一种变体,写法如下页。虽然工厂模式解决了代码复用的问题,但是却没办法显示实例(person1)和对象o之间的关系,比如aler(person1 instanceof o);

代码演示:
function Person(name,age, job) {

this.name = name;

this.age = age;

this.job = job;

this.sayName = function() {

alert(this.name)

};

}

person1 = new Person("Tom",20,"Engineer");

person2 = new Person("Damon",22,"Waiter");

2.2 构造函数

后来就出现了构造函数,用来创建特定类型的对象,可以将实例和对象联系起来,用到了JS中的“this”,写法如下:

这样对象和实例之间就有关系了,以new这种方式调用构造函数会经历4个步骤:

(1)创建一个新对象。

(2)将构造函数的作用域赋给新对象(这个this就指向了这个新对象)。

(3)执行函数内代码(给对象添加属性)

(4)返回新对象。

代码演示:

function Person(name,age, job) {

this.name = name;

this.age = age;

this.job = job;

this.sayName = function() {

alert(this.name)

};

}

person1 = new Person("Tom",20,"Engineer");

person2 = new Person("Damon",22,"Waiter");

构造函数特点:

上面代码中,Persoon就是构造函数,它提供模板,用来生成对象实例。为了与普通函数区别,构造函数名字的第一个字母通常大写。

构造函数的两个特点:

1.函数体内部使用了this关键字,代表了所要生成的对象实例。

2.生成对象的时候,必需用new命令,调用函数。

如果忘了使用new命令,直接调用构造函数会导致构造函数变成普通函数,就不会生成实例对象,并且此时的this这时代表全局对象,将造成一些意想不到的结果。

var Vehicle = function (){

this.price = 1000;

};

var v = Vehicle();

v.price

// Uncaught TypeError: Cannot read property 'price' of undefined

上面代码中,调用Vehicle构造函数时,忘了加上new命令。结果,price属性变成了全局变量,而变量v变成了undefined。

因此必须小心,记得使用new命令。

2.3原型和原型链

原型prototype

JavaScript的每个对象都继承另一个对象,后者称为“原型” (prototype)对象。只有null除外,它没有自己的原型对象。

原型对象上的所有属性和方法,都能被派生对象共享。这就是JavaScript继承机制的基本设计。

通过构造函数生成实例对象时,会自动为实例对象分配原型对象。每一个构造函数都有一个prototype属性,这个属性就是实例对象的原型对象。

原型链

对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype

chain)。比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。

“原型链”的作用是,读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。

需要注意的是,一级级向上,在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。

利用原型(prototype)的继承特性,我们可以将我们的函数写成

function Person() {

};

Person.prototype.name = "Tom";

Person.prototype.age = "20";

Person.prototype.job = "engineer";

Person.prototype.sayName = function() {

alert(this.name);

};

var person1 = new Person();

var person2 = new Person();

alert(person1.sayName == person2.sayName); //true

因为原型的继承,person1和person2的prototype都指向Person的prototype,所以这两个函数其实是相等的。但是用工厂函数或者构造模式, alert(person1.sayName == person2.sayName);就绝对不会为真了。

奇淫巧技1:每次写属性都要加一个prototype是不是很麻烦,其实还有另外一种写法

function Person() {

}

Person.prototype = {

name : "Tom";

age  : "20";

job : "engineer";

sayName : function() {

alert(this.name);

}

}

var person1 = new Person();

var person2 = new Person();

alert(person1.sayName == person2.sayName); //true

2.4 构造函数的继承

让一个构造函数继承另一个构造函数,是非常常见的需求。

也有多种方法实现,各有优缺点。比如现在有一个动物对象的构造函数,和一个猫对象的构造函数。

function Animal() {

this.species = “动物”;

};

function Cat(name,color) {

this.name = name;

this.color = color;

}

如何才能使Cat继承Animal呢?

2.4.1 构造函数绑定

第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:

function Cat(name,color){

Animal.apply(this, arguments); //加的

this.name = name;

this.color = color;

}

var cat1 = new Cat("大毛","黄色");

alert(cat1.species); // 动物

2.4.2 prototype(原型)模式

第二种方法更常见,使用prototype属性。

如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。

Cat.prototype = new Animal();

Cat.prototype.constructor = Cat;

var cat1 = new Cat("大毛","黄色");

alert(cat1.species); // 动物

代码的第一行,我们将Cat的prototype对象指向一个Animal的实例。相当于将Cat原先的原型对象删除,重新赋一个Animal实例的值。但是任何一个prototype对象都有一个constructor属性,指向它的构造函数。这个时候Cat的构造函数也改变了,变成了Animal。

2.4.2 prototype(原型)模式

所以我们需要“Cat.prototype.constructor = Cat”将Cat的构造函数重新指向为Cat,不然的话会很容易出问题。

这是很重要的一点,编程时务必要遵守。如果替换了prototype对象,

b.prototype = new a();

那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。b.prototype.constructor = b;

2.4.3 直接继承prototype(原型)

第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。现在我们将Animal对象改写

function Animal() {

Animal.prototype.species = "动物";

}

然后,将Cat的prototype对象,指向Animal的prototype对象,这样就完成了继承。

Cat.prototype = Animal.prototype;

Cat.prototype.constructor = Cat;

var cat1 = new Cat("大毛","黄色");

alert(cat1.species); // 动物

2.4.3 直接继承prototype(原型)

与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。所以Animal.prototype的构造函数也变成了Cat。

这个时候我们就需要引入一个空对象作为中转的中介,无论Cat的constructor如何变,只会影响到中转对象F而无法影响到父对象Animal了。

var F = function(){};

F.prototype = Animal.prototype;

Cat.prototype = new F();

Cat.prototype.constructor = Cat;

2.4.3 直接继承prototype(原型)

然后我们将上述方法封装成为一个函数,使用起来就很方便了

function extend(Child, Parent) {

var F = function(){};

F.prototype = Parent.prototype;

Child.prototype = new F();

Child.prototype.constructor = Child;

Child.uber = Parent.prototype;

}

2.4.3 直接继承prototype(原型)

使用的时候方法如下:

extend(Cat,Animal);

var cat1 = new Cat("大毛","黄色");

alert(cat1.species); // 动物

奇淫巧技2:封装函数的时候怎么方便怎么写不必太过考虑语义化的东西,比如写个状态机,直接将状态用数字表示,这样比字符串的形式好判断多了。但是一点也不语义化。

3.常见问题

必须要声明new来创建实例对象吗?

4.解决方案

1.必须要声明new来创建实例对象吗?

为了保证构造函数必须与new命令一起使用,一个解决办法是,在构造函数内部使用严格模式,即第一行加上use strict。

function Fubar(foo, bar){

'use strict';

this._foo = foo;

this._bar = bar;

}

Fubar();

// TypeError: Cannot set property '_foo' of undefined

上面代码的Fubar为构造函数,use

strict命令保证了该函数在严格模式下运行。由于在严格模式中,函数内部的this不能指向全局对象,默认等于undefined,导致不加new调用会报错(JavaScript不允许对undefined添加属性)。

另一个解决办法,是在构造函数内部判断是否使用new命令,如果发现没有使用,则直接返回一个实例对象。

function Fubar(foo, bar){

if (!(this instanceof Fubar)) {

return new Fubar(foo, bar);

}

this._foo = foo;

this._bar = bar;

}

Fubar(1, 2)._foo // 1

(new Fubar(1, 2))._foo // 1

上面代码中的构造函数,不管加不加new命令,都会得到同样的结果。

>5.编码实战

用面对对象编程的思想写状态机

6.扩展思考

面向对象与面向过程的区别?

传统的过程式编程(procedural programming)由一系列函数或一系列指令组成;而面向对象编程的程序由一系列对象组成。

每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。因此,面向对象编程具有灵活性、代码的可重用性、模块性等特点,容易维护和开发,非常适合多人合作的大型应用型软件项目。

7.参考文献

参考一:http://javascript.ruanyifeng.com/oop/basic.html">阮一峰


参考二:

href="http://www.ruanyifeng.com/blog/search.html?cx=016304377626642577906%3Ab_e9skaywzq&cof=FORID%3A11&ie=UTF-8&q=Javascript+%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B&sa.x=9&sa.y=8">阮一峰

参考三:《Javascript高级程序设计》chapter 6

8.更多讨论

new命令的原理?

构造函数中的return语句的作用?

面向对象编程的继承原理?

鸣谢

感谢大家观看

PTT链接


JS中的面向对象编程_腾讯视频


------------------------------------------------------------------------------------------------------------------------

技能树.IT修真院

“我们相信人人都可以成为一个工程师,现在开始,找个师兄,带你入门,掌控自己学习的节奏,学习的路上不再迷茫”。

这里是技能树.IT修真院,成千上万的师兄在这里找到了自己的学习路线,学习透明化,成长可见化,师兄1对1免费指导。快来与我一起学习吧 !http://www.jnshu.com/login/1/96194340

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容