JavaScript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类)。
面向对象的语言有一个标志,即拥有类的概念,抽象实例对象的公共属性与方法,基于类可以创建任意多个实例对象,一般具有封装、继承、多态的特性!但JS中对象与纯面向对象语言中的对象是不同的,ECMA标准定义JS中对象:无序属性的集合,其属性可以包含基本值、对象或者函数。可以简单理解为JS的对象是一组无序的值,其中的属性或方法都有一个名字,根据这个名字可以访问相映射的值(值可以是基本值/对象/方法)。
JS对象的封装
最原始的对象封装:
这样的写法有两个缺点,一是如果多生成几个实例,写起来就非常麻烦;二是实例与原型之间,没有任何办法,可以看出有什么联系。
改进版:
这种方法生成出来的实例之间也没有一点联系,不能看出实例是同一个原型的。
构造函数:
所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。
这时我们想知道两个实例之间的关系,可以通过constructor属性来访问它们的构造函数。
Javascript还提供了一个instanceof运算符,验证原型对象与实例对象之间的关系。
这里我们还发现一个问题,就是构造函数会造成内存资源的浪费。即如果生成的实例中有很多一致的属性或方法。生成的每个实例都会去创建。这时我们可以通过构造函数的prototype属性去访问构造函数的原型对象去设置公共的属性或方法。在原型对象中的属性或方法在会默认被生成的实例继承。
为了配合prototype属性,Javascript定义了一些辅助方法,帮助我们使用它。
isPrototypeOf()
这个方法用来判断,某个proptotype对象和某个实例之间的关系。
hasOwnProperty()
每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。
in运算符
in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。
JS构造函数的继承
js对象的继承的实现方法大致有5种方式。
一、 构造函数绑定
第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:
二、 prototype模式
第二种方法更常见,使用prototype属性。
如果Dog的prototype对象,指向一个Animal的实例,那么所有Dog的实例,就能继承Animal了。
它相当于完全删除了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。
与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。但是这种方法的缺点也是显而易见的。我们让Dog.prototype指向了Animal.prototype。这种引用类型的数据之间地址共享,使得只要修改了其中一个的值,另一个就会收到影响。
四、 利用空对象作为中介
这种方法是对上一种方法的修复,即利用一层空对象来解决。
F是空对象,所以几乎不占内存。这时,Dog与Animal之间相互修改彼此的prototype对象就不会彼此影响了。
五、 拷贝继承
上面是采用prototype对象,实现继承。我们也可以换一种思路,纯粹采用"拷贝"方法实现继承。简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?这样我们就有了第五种方法。
JS非构造函数的继承
什么是"非构造函数"的继承?
object方法
利用构造函数的prototype指向Chinese对象,并返回实例。
浅拷贝
除了使用"prototype链"以外,还有另一种思路:把父对象的属性,全部拷贝给子对象,也能实现继承。
但是,这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。
深拷贝
所谓"深拷贝",就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难,只要递归调用"浅拷贝"就行了。