十七、面向对象
17.1 简介
“面向过程”(Procedure Oriented)是一种以过程为中心的编程思想。
我们之前写的所有代码都是面向过程的
“面向对象”(Object Oriented)是软件开发方法,一种编程范式。把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。
我们不再重复书写过程,而是将过程封装成函数,每一次调用这个函数时,就会得到一个对象,该对象所具备功能与之前面向过程的功能是一模一样的。
17.2 面向过程
- 如果是面向过程 去描述一个人 每一条属性都要自己定义 书写的是定义的过程 叫做面向过程
// 如果是面向过程 去描述一个人 每一条属性都要自己定义 书写的是定义的过程 叫做面向过程
var obj = {
name: "zhangsan",
age: 13,
sex: '男'
}
// 如果要再定义一个人 需要再写一遍
var obj1 = {
name: "lisi",
age: 14,
sex: "男"
}
// 如果要再定义一个人 需要再写一遍
// ……
// 这么写代码 可以 但是比较繁琐
17.3 面向对象
17.3.1 工厂函数
- 如果能够有一种方式 能够快速的生成一个对象 我们就不用每次都从头到尾敲一遍
function createPerson() {
return {
name: "lisi",
age: 14,
sex: "男"
}
}
// 自从有了createPerson函数之后,就每调用一次 就可以得到一个对象
var obj = createPerson();
var obj1 = createPerson();
console.log(obj)
console.log(obj1)
提示: 这种可以返回内容的函数,我们称之为“工厂”。这种书写代码的模式,叫做工厂模式。属于设计模式的一种。 问题: 虽然可以创建 但是内容完全一致 我们希望内容可变
思考:函数中的什么内容可以发生变化?
答案:参数是可以变化的
于是 我们将可变的部分提取成参数 由外部传入
function createPerson(name, age, sex) {
return {
name: name,
age: age,
sex: sex
}
}
var obj = createPerson("张三", 13, "男");
var obj1 = createPerson("李四", 14, "男");
console.log(obj);
console.log(obj1);
定义”人”的工厂创建完毕 现在再定义一个”车”的工厂
function createCar(type, color, price) {
return {
type: type,
color: color,
price: price
}
}
function createPerson(name, age, sex) {
return {
name: name,
age: age,
sex: sex
}
}
// 调用函数创建一个人
var p = createPerson("张三", 13, "男");
// 调用函数创建一辆车
var c = createCar("五菱宏光", "blue", 108000);
console.log(p);
console.log(c);
// 想要分辨两个对象各自的类型
console.log(Object.prototype.toString.call(p)); // [object Object]
console.log(Object.prototype.toString.call(c)); // [object Object]
结论:想要分辨两个对象各自的类型 无法分辨 注: Object.prototype.toString.call()这种方式虽然可以检测到JS内置的构造函数名称,但是无法分辨自定义的构造函数名称
17.4 构造函数
用于构造对象的函数,叫做构造函数。
- 用new调用
- 首字母要大写
- 内部要通过this给实例添加属性
- 内部最好不要出现return
- 如果出现了return 返回的如果是值类型 则忽略return
- 如果出现了return 返回的如果是引用类型 则以return为准
- 对象的类型是构造函数名称
引导
function createPerson() {
}
function createCar() {
}
// 构造函数: 此时 createPerson和createCar的作用就是在构造对象 这样的函数叫做构造函数
// 构造函数其实就是普通函数
// 如果使用new调用 就是构造函数 会返回对象
var p = new createPerson();
var p1 = new createPerson();
var c = new createCar();
// 如果不使用new调用 就是普通函数 是否返回内容要取决于是否有return
var d = createPerson();
var e = createCar();
new 是一个关键字 它的作用类似于运算符 是一种调用函数的方式 new 的英文意思: 新的 也就是说 一旦new了 就希望得到一个新对象 注:构造函数其实就是普通函数 如果使用new调用 就是构造函数 会返回对象 如果不使用new 就是当做普通函数在调用 是否有返回内容要取决于是否有return
构造函数
构造函数在执行的时候:
- 开辟一个内存空间(空对象)
- 将空对象与this绑定
- 然后让这个空对象的 proto 指向函数的原型prototype,继承了该函数的原型。
- 执行函数体中的代码
- 返回这个this地址
17.5 安全类
属于设计模式的一种 能够保证不论程序员使用new还是不使用new都可以得到实例对象。
function Person() {
if (this === window) {
// 说明外面没有new 就是当普通函数来调用的 外部想要得到返回内容 内部就得有return
return new Person();
}
}
var p = new Person();
var p1 = Person();
// p 和 p1都是Person的实例
17.6 称呼问题
用于构造对象的函数叫做构造函数。
构造函数构造出来的对象叫做实例。
17.7 原型的推导过程
要搞明白原型是什么,先来看问题:
// 定义构造函数
var Animal = function(eye, weight) {
this.eye = eye;
this.weight = weight;
this.eat = function() {
console.log("吃东西");
}
this.sleep = function() {
console.log("zzzzzzzzzzzzzzz...")
}
}
var a1 = new Animal("眼睛", 100);
a1.eat();
a1.sleep();
var a2 = new Animal("眼睛1", 200);
a2.eat();
a2.sleep();
console.log(a1.eat === a2.eat); // false
问题: 既然是同样的方法 最好在内存中只保留一个 能够被每一个实例复用 思考: 之所以每一个实例的方法的地址不同 是因为每一次执行构造函数的时候 都定义了两个新函数
// 定义构造函数
var Animal = function(eye, weight) {
this.eye = eye;
this.weight = weight;
this.eat = eat;
this.sleep = sleep;
}
// 之所以每一次都定义两个新函数 是因为代码放在了里面
// 现在将代码抽取到函数的外部 这样就只会定义一次 不论初始化多少Animal的实例 都共用同一个方法
var eat = function() {
console.log("吃东西");
}
var sleep = function() {
console.log("zzzzzzzzzzzzzzz...")
}
var a1 = new Animal("眼睛", 100);
a1.eat();
a1.sleep();
var a2 = new Animal("眼睛1", 200);
a2.eat();
a2.sleep();
console.log(a1.eat === a2.eat); // true
结论:虽然将代码抽取到外部 可以解决复用的问题 但是污染了外部的作用域 也就意味着:定义一个构造函数 还要定义一堆变量 定义一堆函数 这也不合适
为了解决外部环境的污染问题 我们就定义一个变量 保存一个对象 将所有的函数都挂载在该对象身上
var Animal = function (eye, weight) {
this.eye = eye;
this.weight = weight;
this.eat = prototype.eat;
this.sleep = prototype.sleep;
}
// 为了解决外部环境的污染问题 我们就定义一个变量 保存一个对象 将所有的函数都挂载在该对象身上
var prototype = {
eat: function () {
console.log("吃东西");
},
sleep: function () {
console.log("zzzzzzzzzzzzzzz...")
}
}
var a1 = new Animal("眼睛", 100);
a1.eat();
a1.sleep();
var a2 = new Animal("眼睛1", 200);
a2.eat();
a2.sleep();
console.log(a1.eat === a2.eat); // true
现在只剩下一个变量 可是这一个对象我们也不想要 所以可以挂载到函数本身 => Constructor.prototype 我们能够考虑到这个问题 JS之父布兰登·艾奇也想到了 于是,他规定每一个函数都有一个属性prototype 而该对象 它身上的内容都可以被构造函数的实例访问到 用于共享方法和属性 默认有一个属性 constructor指向构造函数本身
17.8 原型查找机制
原型查找机制: 当一个对象调用属性时,会先查找自身是否具备,如果有,就用 如果没有 会查找自身的构造函数的原型(原型对象)是否具备 如果有就用 如果没有 就继续向上查找(因为原型对象也是对象 查它的属性又触发了它的原型查找机制) 直到Object.prototype为止 因为再往上就是null
17.9 对象类型的分辨
- 方式 Object.prototype.toString.call(对象)
- 学习了原型之后,我们就可以使用 实例.constructor 来获取对象的构造函数 进而判定对象的类型
17.10 instanceof
用来检测一个对象是否是函数的实例
返回值是布尔值
17.11 hasOwnProperty
该方法是ES5中新增的方法 用于检测一个对象的属性是否是该对象自身的属性
如果是自身属性 返回true 否则返回false