- 对象是单个事物的抽象
- 对象是一个容器,封装了属性(property)和方法(method)
属性表征了对象的状态,方法表征了对象的行为
什么是面向对象
Object Oriented Programming,简称 OOP
面向对象是面向过程的封装
面向对象的特征:
- 封装性;继承性;多态性
面向对象的设计思想
- 抽象出 Class(构造函数),包含了属性和方法
- 根据 Class(构造函数) 创建 Instance(实例)
- 指挥 Instance 得结果
创建对象的几种方法
- 调用 new Object() 构造函数,然后属性各自赋值
- 对象字面量 {},对象直接赋值
相对化简了代码 - 封装工厂函数
将 new Object() 或字面量的方法封装进工厂函数,并将结果返回,后期可多次调用
但工厂函数生成的对象不是具体的实例 - 自定义构造函数#5
与工厂函数的区别:- 不需要 return
- 构造函数的函数名首字母要求大写
- 内部不生成对象,使用 this 代指要生成的对象
- 构造函数定义完毕,再使用 new 生成实例
new 关键字的作用:- 创建一个新对象
- 将函数内部的 this 指向了这个新对象
- 执行构造函数内部的代码
- (默认)将新对象作为返回值
通过构造函数生成的实例都有 constructor 属性,它指向实例的构造函数,但是它可能会被修改。所以需要判断实例与构造函数之间关系时,还是要用 instance of 操作符。
静态成员和实例成员#
使用构造函数方法创建对象时,可以给构造函数和创建的实例对象添加属性和方法,这些属性和方法都叫做成员。
- 静态成员:添加给构造函数自身的成员。
只能使用构造函数调用,不能使用生成的实例对象调用。 - 实例成员:在构造函数内部添加给 this 的成员。
在创建实例对象后必须由对象调用,不能由构造函数调用。
Math 的所有成员都是静态成员,因为它不是由构造函数生成的。
构造函数的问题
浪费内存:每个实例的函数都是不同的
优化:将公共的函数提取到构造函数之外,构造函数内的函数属性做赋值即可
进一步:多个公共函数封装为一个对象,避免全局作用域大量函数声明
原型对象#8
任何函数都有 prototype 属性,其值是一个对象,称之为原型对象。原型对象上可以再添加属性和方法。
构造函数的原型对象象默认都有一个 constructor 属性,指向构造函数本身。
Person === Person.prototype.constructor;
实例包含一个 __proto__
属性,指向其构造函数的原型对象。
Person.prototype === p1.__proto__
此指针非标准,实际使用中可以省略,即实例可以直接访问原型对象的成员,例如 constructor。也就是上节所说的,实例调用 constructor 指向其构造函数。
最优解#9
公共的函数无需另外封装进对象,而是直接添加到构造函数的原型对象上。原型对象的属性和方法都会被构造函数及构造函数生成的实例所继承。
原型链#10
实例对象读写原型对象成员#11
读取:原型链查找
- 先在自己身上找,找到即返回
- 自己身上找不到,则沿着原型链向上查找,找到即返回
- 如果一直到原型链的末端(null)还没有找到,则返回 undefine
写入:分两大类情形
-
值类型成员写入(实例对象.自有 值类型成员 = xx)e.g.
ps1.age=18;
:
引用类型成员写入(实例对象.原型 引用类型成员 = xx)e.g.ps1.type='person';
:写入或修改只会作用于实例自身,屏蔽了对原型对象成员的访问,不会沿原型链查找。
-
复杂类型成员修改(实例对象.成员对象.xx = xx):
会沿原型链查找
- 同样会先在自己身上找该成员,如果自己身上找到则直接修改
- 如果自己身上找不到,则沿着原型链继续查找,如果找到则修改
- 如果一直到原型链的末端还没有找到该成员,则报错(实例对象.undefined.xx = xx)
更简单的原型语法#12
可以直接以对象字面量的形式,将原型对象重置到一个新的对象。
【注意】原原型对象的 constructor 属性会被覆盖丢失,需要在对象赋值时,手动添加上 constructor,并将其指向正确的构造函数。
定义构造函数时的成员分类:
- 私有成员:一般是非函数成员,直接放入构造函数定义内
- 共享成员:一般是函数,则放入原型对象中
- 特殊成员:如果重置了 prototype,需要修正
原生构造函数的原型对象
内置的原生构造函数有:
Object()、Function()、Array()、String()、Number()
他们的原型对象不能使用字面量覆写修改,但是可以新写入属性
【注意】工作中禁止修改内置构造函数的原型