javascript进阶课程--第二章--对象
学习要点
- 理解面向对象的概念
- 掌握对象的创建方法
- 掌握继承的概念和实现方法
基本概念
- 对象究竟是什么?什么叫面向对象编程?
- 对象是从我们现实生活中抽象出来的一个概念,俗话说物以类聚,人以群分.这里面就有两个重要概念
- 类:无论是物以类聚,还是有一类人,这里说的类并不是实际存在的事物,是一些特征、是一些规则等
- 人/物,是现实存在,和类的关系就是符合类的描述
- 类和实例的概念
- 类:每个对象都由类定义,可以把类看做对象的配方。
类不仅要定义对象的的属性和方法,还要定义对象的内部工作工作原理。JavaScript并没有正式的类,创建一个对象只要定义一个该对象的构造函数并通过它创建对象即可。虽然类并不真正存在,我们也把对象定义叫做类。而且从功能上说,两者是等价的。
- 实例:程序使用类创建对象时,生成的对象叫作类的实例。由类创建对象实例的过程叫做实例化
- 类:每个对象都由类定义,可以把类看做对象的配方。
- JavaScript 对象定义:可以把对象理解为属性集合,每个属性存放一个原始值、对象或函数。
对象一般认为由方法和属性构成。方法的实质就是函数,而属性的实质就是变量,只不过这里由于它从属于某个对象所以叫法不同。
- 面向对象:可以简单的理解为,不必去了解对象的内部结构,就可以去使用它。就比如我们可以使用手机打电话,但是不必去了解它内部的工作原理。就像我们使用Date对象的方法可以获取和设置时间,单是我们并不用去弄清楚它为什么会实现这个功能。(类似于我们常说的模块化,便于不同之间的协作,个人理解)
创建对象的方法
本质上都是把"属性"和"方法",封装成一个对象
创建对象的基本模式
-
普通创建对象的方法,缺陷:
- 如果创建多个对象会比较繁琐,效率低
- 实例与原型之间,没有任何办法,可以看出有什么联系。
工厂模式
- 工厂模式:使用创建并返回特定类型的对象的工厂函数(其实就是普通函数,没啥区别,只是叫法不同)
- 创建过程类似于工厂生产产品的过程,即:原材料--加工--产品...
- 解决了多次重复创建多个对象的麻烦。
- 问题:
- 创建出的实例之间没有内在的联系,不能反映出它们是同一个原型对象的实例。
- 创建对象的时候没有使用 new 关键字
- 会造成资源浪费,因为每生成一个实例,都增加一个重复的内容,多占用一些内存。
构造函数模式
- new 调用的函数为构造函数,构造函数和普通函数区别仅仅在于是否使用了new来调用。
- 所谓“构造函数”,就是专门用来生成“对象”的函数。它提供模板,作为对象的基本结构。
- 构造函数内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。
- instanceof 验证原型对象与实例对象之间的关系。
- 使用call和apply方法实现对象的冒充
- 问题:浪费内存--使用构造函数每生成一个实例,都增加一个重复的内容,多占用一些内存。这样既不环保,也缺乏效率。
(Prototype)原型模式
Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。可以把那些不变的属性和方法,直接定义在prototype对象上。
- prototype方式定义的方式,函数不会拷贝到每一个实例中,所有的实例共享prototype中的定义,节省了内存。
- Prototype模式的验证方法
- isPrototypeOf()这个方法用来判断,某个proptotype对象和某个实例之间的关系。
- hasOwnProperty()每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。
- in运算符in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。in运算符还可以用来遍历某个对象的所有属性。
- 对象的constructor属性用于返回创建该对象的构造函数.(注:了解即可)
在JavaScript中,每个具有原型的对象都会自动获得constructor属性。
- 原型方式的问题:
- 构造函数没有参数。使用原型方式,不能通过给构造函数传递参数来初始化属性的值
- 属性指向的是对象,而不是函数时。函数共享不会造成问题,但对象却很少被多个实例共享,如果共享的是对象就会造成问题。
构造函数和原型组合模式
这是目前最为常用的创建对象的方式。
这种概念非常简单,即用构造函数定义对象的所有非函数属性,用原型方式定义对象的函数属性(方法)。结果是,所有函数都只创建一次,而每个对象都具有自己的对象属性实例。
此外,组合模式还支持向构造函数传递参数,可谓是集两家之所长。
在所接触的JS库中,jQuery类型的封装就是使用组合模式来实例的!!!
动态原型方法
动态原型方法的基本想法与混合的构造函数原型方式相同,即在构造函数内定义非函数属性,而函数属性则利用原型属性定义。
组合模式中实例属性与共享方法(由原型定义)是分离的,这与纯面向对象语言不太一致;动态原型模式将所有构造信息都封装在构造函数中,又保持了组合的优点。
其原理就是通过判断构造函数的原型中是否已经定义了共享的方法或属性,如果没有则定义,否则不再执行定义过程。该方式只原型上方法或属性只定义一次,且将所有构造过程都封装在构造函数中,对原型所做的修改能立即体现所有实例
继承继承的相关概念
- 这里的继承和我们现实生活的中儿子继承父亲财产的这种关系虽然有相似的地方,但本质其实不一样;
- 举一个简单的例子理解继承的含义:
- 猴子--会法术的猴子--孙悟空;
- 会法术的猴子和猴子是我们这里所说的继承关系;
- 会法术的猴子是猴子的一种,即猴子是会法术的猴子的类型;
- 孙悟空是会法术的猴子的一个实例,即会法术的猴子是孙悟空的类;
- 孙悟空即使会法术的猴子的一种,也是猴子的一种,这种关系就和我们接下来要讲的继承是一个道理;
- 在程序里我们将猴子叫做父类(超类,基类),会法术的猴子是他的子类(子类型,派生类),会法术的猴子继承了猴子的所有特征;
- 会法术的猴子是孙悟空的类;孙悟空是会法术的猴子的实例;
- 一旦确定了两个类的继承关系,就包含以以三个意思:
- 子类的实例可以共享父类的方法
- 子类可以覆盖或扩展父类的方法
- 子类和父类都是子类实例的类型
继承的实现
实现继承的方式不止一种。这是因为 JavaScript 中的继承机制并不是明确规定的,而是通过模仿实现的。所以大家看参考书的时候会发现很多种实现继承的方式,这里我们介绍最常用的几种。
- 对象冒充(构造函数绑定):
原理如下:使用对象冒充(call或apply方法)(实质上是改变了this指针的指向)继承基类。
- 原型链
这种方法更常见,使用prototype属性。
- prototype 对象是个模板,要实例化的对象都以这个模板为基础。总而言之,prototype 对象的任何属性和方法都被传递给那个类的所有实例。原型链利用这种功能来实现继承机制。
- 原型链的弊端是不支持多重继承。记住,原型链会用另一类型的对象重写类的 prototype 属性。
- 子类的所有属性和方法都必须出现在 prototype 属性被赋值后,因为在它之前赋值的所有方法都会被删除。因为 prototype 属性被替换成了新对象,添加了新方法的原始对象将被销毁。
- 混合方式
我们曾经讲解过创建类的最好方式是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承 prototype 对象的方法。