什么是面向对象
- 面向对象程序设计(Object Oriented Programming,OOP)是一种计算机编程架构。
- 面向对象是一种软件开发方法,它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的灵活性、重用性和扩展性。
- 面向对象是不是语法,是一个思想,是一种编程模式,一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物,是一种高级的编程思想。
- 面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。
面向对象的特征
- 封装:
封装是指将现实世界中存在的某个客体的属性与行为绑定在一起,并放置在一个逻辑单元内。该逻辑单元负责将所描述的属性隐藏起来,外界对客体内部属性的所有访问只能通过提供的用户接口实现。对象是封装的最基本单位。 - 继承:
继承性是子类自动共享父类数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。 - 抽象:
继承性是子类自动共享父类数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。 - 多态
多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。不同的对象,收到同一消息可以产生不同的结果,这种现象称为多态性。
类与对象
- 类描述了一组有相同特性和相同行为的对象,具有相同属性和相同方法的对象的抽象就是类。
- 类的实例是对象。
- 对象把数据及对数据的操作方法放在一起,作为一个相互依存的整体。
- 对象的抽象是类
类与对象的关系:模板 -> 产品
“JavaScript中所有事物都是对象”,如 字符串、数组、函数…等等。
因为所有对象均继承自Object,都是Object的实例。
对象是带有属性和方法的集合。
- 变量和属性:
变量是自由的,属性是属于对象的,是与对象相关的值。 - 函数和方法:
函数是自由的,方法是属于对象的,是与对象相关的函数
var a =‘qwe’; // 变量
alert( a );
var obj = {}; // 对象
obj.name = ‘xiaocuo’; // 属性
obj.age = 25; // 属性
obj.sayHi = function (){ // 方法
alert(‘hi,大家好!');
}
alert( obj.name );
obj.sayHi();
创建对象
调用系统内置的构造函数创建对象
- js 给我们内置了一个 Object 构造函数
- 这个构造函数就是用来创造对象的
- 当 构造函数 和 new 关键字连用的时候,就可以为我们创造出一个对象
var o1 = new Object()
o1.name = 'Jack'
o1.age = 18
o1.sex = '男'
字面量的方式创建一个对象
- 直接使用字面量的形式,也就是直接写 {}
- 可以在写的时候就添加好成员,也可以动态的添加
// 字面量方式创建对象
var o1 = {
name: 'Jack',
age: 18,
sex: '男'
}
// 再来一个
var o2 = {}
o2.name = 'Rose'
o2.age = 20
o2.sex = '女'
使用工厂函数的方式创建对象
- 工厂模式是软件工程领域一种常见的设计模式,这种模式抽象了创建对象的具体过程。
- 这个工厂函数里面可以创造出一个对象,并且给对象添加一些属性,还能把对象返回.
- 原料 => 加工 => 出厂
// 1. 先创建一个工厂函数
function createObj() {
// 手动创建一个对象(原料)
var obj = new Object()
// 手动的向对象中添加成员(加工)
obj.name = 'Jack'
obj.age = 18
obj.sex = '男'
// 手动返回一个对象(出厂)
return obj
}
// 2. 使用这个工厂函数创建对象
var o1 = createObj()
var o2 = createObj()
使用自定义构造函数创建对象
- 工厂函数需要经历三个步骤
- 手动创建对象
- 手动添加成员
- 手动返回对象
- 构造函数会比工厂函数简单一下
- 自动创建对象
- 手动添加成员
- 自动返回对象
- 先书写一个构造函数,在构造函数内向对象添加一些成员
- 使用这个构造函数创造一个对象(和 new 连用)
- 构造函数可以创建对象,并且创建一个带有属性和方法的对象
- 面向对象就是要想办法找到一个有属性和方法的对象
- 面向对象就是我们自己制造 构造函数 的过程
// 1. 先创造一个构造函数
function Person(name, gender) {
this.age = 18
this.name = name
this.sex = sex
}
// 2. 使用构造函数创建对象
var p1 = new Person('Jack', 'man')
var p2 = new Person('Rose', 'woman')
构造函数详解
构造函数的基本使用
- 和普通函数一样,只不过 调用的时候要和 new 连用,不然就是一个普通函数调用
function Person() {}
var o1 = new Person() // 能得到一个空对象
var o2 = Person() // 什么也得不到,这个就是普通函数调用
- 首字母大写
function person() {}
var o1 = new person() // 能得到一个对象
function Person() {}
var o2 = new Person() // 能得到一个对象
- 当调用的时候如果不需要传递参数可以不写 () ,建议都写上
function Person() {}
var o1 = new Person() // 能得到一个空对象
var o2 = new Person // 能得到一个空对象
- 构造函数内部的 this,由于和 new 连用的关系,是指向当前实例对象的
function Person() {
console.log(this)
}
var o1 = new Person() // 本次调用的时候,this => o1
var o2 = new Person() // 本次调用的时候,this => o2
注意:每次 new 的时候,函数内部的 this 都是指向当前这次的实例化对象
- 因为构造函数会自动返回一个对象,所以构造函数内部不要写 return
你如果 return 一个基本数据类型,那么写了没有意义。
如果你 return 一个引用数据类型,那么构造函数本身的意义就没有了。
使用构造函数创建一个对象
- 我们在使用构造函数的时候,可以通过一些代码和内容来向当前的对象中添加一些内容
function Person() {
this.name = 'Jack'
this.age = 18
}
var o1 = new Person()
var o2 = new Person()
// 我们得到的两个对象里面都有自己的成员 name 和 age
- 也可以添加一些方法进去
function Person() {
this.name = 'Jack'
this.age = 18
this.sayHi = function () {
alert('hello constructor')
}
}
var o1 = new Person()
var o2 = new Person()
- 但是这样写有缺点
function Person() {
this.name = 'Jack'
this.age = 18
this.sayHi = function () {
console.log('hello constructor')
}
}
var o1 = new Person()
// 第一次 new 的时候, Person 这个函数要执行一遍
// 执行一遍就会创造一个新的函数,并且把函数地址赋值给 this.sayHi
var o2 = new Person()
// 第二次 new 的时候, Person 这个函数要执行一遍
// 执行一遍就会创造一个新的函数,并且把函数地址赋值给 this.sayHi
console.log(o1.sayHi === o2.sayHi) //flase
//这样的话,那么我们两个对象内的 sayHi 函数就是一个代码一模一样,功能一摸一样
//但是是两个空间函数,占用两个内存空间
- 怎么解决呢,就需要用到一个东西,叫做 原型
原型
prototype
- 每一个函数天生自带一个成员,叫做 prototype,是一个对象空间
- 即然每一个函数都有,构造函数也是函数,构造函数也有这个对象空间
- 这个 prototype 对象空间可以由函数名来访问
function Person() {}
console.log(Person.prototype) // 是一个对象
- 即然是个对象,那么我们就可以向里面放入一些东西
function Person() {}
Person.prototype.name = 'prototype'
Person.prototype.sayHi = function () {}
在函数的 prototype 里面存储的内容,不是给函数使用的,是给函数的每一个实例化对象使用的
__ proto __
- 每一个对象都天生自带一个成员,叫做 __ proto __ ,是一个对象空间
- 即然每一个对象都有,实例化对象也是对象,那么每一个实例化对象也有这个成员
- 这个 __ proto __ 对象空间是给每一个对象使用的
- 当你访问一个对象中的成员的时候
- 如果这个对象自己本身有这个成员,那么就会直接给你结果
- 如果没有,就会去 __ proto __ 这个对象空间里面找
- 这个对象是由哪个构造函数 new 出来的,那么这个对象的 __ proto __ 就指向这个构造函数的 prototype
function Person() {}
var p1 = new Person()
console.log(p1.__proto__ === Person.prototype) // true
- 我们发现实例化对象的 __ proto __ 和所属的构造函数的 prototype 是一个对象空间
- 我们可以通过构造函数名称来向 prototype 中添加成员
- 对象在访问的时候自己没有,可以自动去自己的 __ proto __ 中查找
function Person() {}
//我们可以把函数放在构造函数的 prototype 中
Person.prototype.sayHi = function () {
console.log('hello Person')
}
var p1 = new Person()
//p1 自己没有 sayHi 方法,就会去自己的 __proto__ 中查找
//p1.__proto__ 就是 Person.prototype
p1.sayHi()
- 当我们实例化多个对象的时候,每个对象里面都没有方法
function Person() {}
Person.prototype.sayHi = function () {
console.log('hello')
}
var p1 = new Person()
//p1 是 Person 的一个实例
var p2 = new Person()
//p2 是 Person 的一个实例
console.log(p1.sayHi === p2.sayHi)
//都是去所属的构造函数的 protottype 中查找
//p1.__proto__ 和 p2.__proto__ 指向的都是 Person.prototype
//每一个对象使用的函数,其实都是同一个函数
//
这样就解决了构造函数的缺点
结论
- 当我们写构造函数的时候
- 属性我们直接写在构造函数体内
- 方法写在原型上
原型链
一个对象所属的构造函数
- 每一个对象都有一个自己所属的构造函数
- 比如 :数组
// 数组本身也是一个对象
var arr = []
var arr2 = new Array()
//数组所属的构造函数就是 Array
- 比如 :函数
// 函数本身也是一个对象
var fn = function () {}
var fun = new Function()
// 我们就说函数所属的构造函数就是 Function
constructor
- 对象的 __ proto __ 里面也有一个成员叫做 constructor
- 这个属性就是指向当前这个对象所属的构造函数,可以用来判断复杂数据类型
链状结构
- 当一个对象我们不知道准确的是谁构造的时候,我们就把它看成 Object 的实例化对象
- 也就是说,我们的 构造函数 的 prototype 的 __ proto __ 指向的是 Object.prototype
- 那么 Object.prototype 也是个对象
- 因为 Object 的 js 中的顶级构造函数,我们有一句话叫 万物皆对象
- 所以 Object.prototype 就到顶了, Object.prototype 的 __ proto __ 就是 null
原型链的访问原则
- 之前说过,访问一个对象的成员的时候,自己没有就会去 __ proto __ 中找
- 接下来就是,如果 __ proto __ 里面没有就再去 __ proto __ 里面找
- 一直找到 Object.prototype 里面都没有,那么就会返回 undefiend
对象的赋值
- 如果是赋值的话,那么也会按照原型链的规则来
- 赋值的时候,就是直接给对象自己本身赋值
- 如果原先有就是修改,原先没有就是添加,不会和 __ proto __ 有关系
混合模式
- 混合使用构造函数模式和原型模式,是目前JS中使用最广泛、认同度最高的一种创建对象的方法
- 构造函数模式用于定义实例属性,原型模式用于定义共享的属性和方法
- 即可以共享方法,又可以向构造函数传递参数,集两种模式之长!
function Person(name,sex,age){
this.name = name;
this.sex = sex;
this.age = age;
}
Person.prototype.attr = '人类';
Person.prototype.eat = function (){
alert('什么都吃');
}
var p1 = new Person('老王','男',36);
var p2 = new Person('小宋','女',26);
console.log(p1.constructor);//Person
console.log(p1.eat === p2.eat);//true
总结
- 面向对象的思维: 当我想完成一个功能的时候
- 先看看内置构造函数有没有能给我提供一个完成功能对象的能力
- 如果没有,我们就自己写一个构造函数,能创造出一个完成功能的对象
- 然后在用我们写的构造函数 new 一个对象出来,帮助我们完成功能就行了