js之面对对象编程

JavaScript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类)。

面向对象的语言有一个标志,即拥有类的概念,抽象实例对象的公共属性与方法,基于类可以创建任意多个实例对象,一般具有封装、继承、多态的特性!但JS中对象与纯面向对象语言中的对象是不同的,ECMA标准定义JS中对象:无序属性的集合,其属性可以包含基本值、对象或者函数。可以简单理解为JS的对象是一组无序的值,其中的属性或方法都有一个名字,根据这个名字可以访问相映射的值(值可以是基本值/对象/方法)。

JS对象的封装

最原始的对象封装:

js之面对对象编程_第1张图片

这样的写法有两个缺点,一是如果多生成几个实例,写起来就非常麻烦;二是实例与原型之间,没有任何办法,可以看出有什么联系。

改进版:

js之面对对象编程_第2张图片

这种方法生成出来的实例之间也没有一点联系,不能看出实例是同一个原型的。

构造函数:

所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。

js之面对对象编程_第3张图片

这时我们想知道两个实例之间的关系,可以通过constructor属性来访问它们的构造函数。

Javascript还提供了一个instanceof运算符,验证原型对象与实例对象之间的关系。

这里我们还发现一个问题,就是构造函数会造成内存资源的浪费。即如果生成的实例中有很多一致的属性或方法。生成的每个实例都会去创建。这时我们可以通过构造函数的prototype属性去访问构造函数的原型对象去设置公共的属性或方法。在原型对象中的属性或方法在会默认被生成的实例继承。

js之面对对象编程_第4张图片

为了配合prototype属性,Javascript定义了一些辅助方法,帮助我们使用它。

isPrototypeOf() 

这个方法用来判断,某个proptotype对象和某个实例之间的关系。

hasOwnProperty()

每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。

in运算符

in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。

JS构造函数的继承

js对象的继承的实现方法大致有5种方式。

一、 构造函数绑定

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

js之面对对象编程_第5张图片

二、 prototype模式

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

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

js之面对对象编程_第6张图片

它相当于完全删除了prototype 对象原先的值,然后赋予一个新值。

由于任何一个prototype对象都有一个constructor属性,指向它的构造函数。如果没有“Dog.prototype = new Animal();"这一行,Dog.prototype.constructor是指向Animal的;加了这一行以后,Dog.prototype.constructor指向Dog。

更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。如果不手动修改Dog.prototype的constructor属性。则该构造函数生成的实例的constructor属性都会指向Animal。造成继承链的混乱。

三、 直接继承prototype

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

js之面对对象编程_第7张图片

与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。但是这种方法的缺点也是显而易见的。我们让Dog.prototype指向了Animal.prototype。这种引用类型的数据之间地址共享,使得只要修改了其中一个的值,另一个就会收到影响。

四、 利用空对象作为中介

这种方法是对上一种方法的修复,即利用一层空对象来解决。

js之面对对象编程_第8张图片

F是空对象,所以几乎不占内存。这时,Dog与Animal之间相互修改彼此的prototype对象就不会彼此影响了。

五、 拷贝继承

上面是采用prototype对象,实现继承。我们也可以换一种思路,纯粹采用"拷贝"方法实现继承。简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?这样我们就有了第五种方法。

js之面对对象编程_第9张图片

JS非构造函数的继承

什么是"非构造函数"的继承?

js之面对对象编程_第10张图片

object方法 

利用构造函数的prototype指向Chinese对象,并返回实例。

js之面对对象编程_第11张图片

浅拷贝

除了使用"prototype链"以外,还有另一种思路:把父对象的属性,全部拷贝给子对象,也能实现继承。


js之面对对象编程_第12张图片
js之面对对象编程_第13张图片

但是,这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。

深拷贝

所谓"深拷贝",就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难,只要递归调用"浅拷贝"就行了。

js之面对对象编程_第14张图片

你可能感兴趣的:(js之面对对象编程)