一、认识面向对象
面向对象是什么?
并不是一个语法,也不是一个新的语言,它是用JS完成需求的一种思想
我们一开始学习的解决问题的方式,统称为面向过程
面向过程:注重的是过程,每一步都事无巨细,全都由我们的代码从上往下,依次完成
问题:每一个功能之间会互相影响
拿轮播图举例,如果有多个,那么这个代码需要重复的写多次
面向对象:注重的是一个对象,这个对象就是我们的需求,比如说这个对象可以是一个轮播图,这个对象也可以是一个分页
拿一个功能举例:我想要吃一碗面
面向过程:
- 1.准备面粉
- 2.准备水
- 3.和面
- 4.切面
- 5.烧水
- 6.煮面
- 7.吃面
面向对象:
- 1.找一个面馆
- 2.下单
- 3.吃面
假如我们没有所谓的“面馆”,那么我们直接开一个面馆
创建完成之后的“面馆”,除了你能使用,其他人也能使用,起到一个多次复用的效果
轮播图:
面向过程:
- 1.const banner = ···
- 2.const imgBox = ···
- 3.const index = ···
- 4.const timer = ···
- 5.function copyEle (){内部处理的是假图,这个函数依赖imgBox这个变量}
- 6.function setFocus (){内部处理的是焦点,依赖imgBox,focus}
- 7.function···
面向过程,创建第二个轮播图
- 1.const banner2 = ···
- 2.const imgBox2 = ···
- 3.const index2 = ···
面向对象:
- 1.找一个“机器”,能够帮我们创建一个轮播图
- 2.发现没有这个“机器”
- 3.我们创建一个“机器”
- 4.“机器”创建完成之后,就能给我们创建一个轮播图
- 5.这个“机器”的特点就是能够批量创建
注意点:面向对象我们关注的就是对象,因为需要批量创建,所以我们创建对象的方式就要和以前不同
二、创建对象的方式
1.字面量的方式
let obj = {
name: 'QF001',
age: 18
}
这个方式不合适,不利于批量创建
2.内置构造函数
这个方式不合适,不利于批量创建
let obj = new Object()
3.工厂函数的方式
其实就是创建一个函数,函数内部可以创建一个对象,我们把这种函数叫做工厂函数
// 3.工厂函数的方式
function createObj(num) {
// 3.1手动创建一个对象
const obj = {}
// 3.2手动给对象上添加一些属性
obj.name = 'QF001' //添加一个固定的字符串,每次创建对象他的属性都是 'QF001'
// obj.age = 18
obj.age = num //利用形参给对象的属性赋值一个变量,这样每次创建对象时都可以修改这个属性的值
// 3.3手动给对象返回
return obj
}
let obj1 = createObj(18)
let obj2 = createObj(66)
console.log(obj1)
console.log(obj2)
三、自定义构造函数
1.什么是构造函数
构造函数本质上就是一个普通函数,如果在调用的时候,前边加上一个关键字new,那么我们把这种函数叫做构造函数
2.构造函数的书写
-
1.构造函数的函数名首字母大写(建议,非强制),目的就是为了和普通函数做一个区分
-
2.构造函数内不要写return
-
如果return的是一个基本数据类型,写了也没用
-
如果return的是一个引用数据类型,写了就会导致构造函数没用
-
-
3.构造函数调用时,必须和new关键字连用
- 如果不连用,也能调用,但是构造函数就没用了
-
4.构造函数内部的this
-
当一个函数和new关键字连用的时候,那我们说这个函数是构造函数
-
然后这个函数内部的this指向本次调用被自动创建出来的那个对象1
-
-
5.构造函数不能使用箭头函数
- 因为箭头函数内部没有this
因为构造函数自动创建出来的对象可以通过this来访问,所以我们需要向这个对象上添加属性的时候,可以通过this来添加
function Person(name, age) {
// 因为构造函数自动创建出来的对象可以通过this来访问,所以我们需要向这个对象上添加属性的时候,可以通过this来添加
this.name = name
this.age = age
this.a = '我是随便添加的一个属性a,我是固定的内容'
}
const p1 = new Person('QF666', 18)
console.log(p1)
const p2 = new Person('QF001', 666)
console.log(p2)
3.构造函数的缺点
构造函数内部,如果有这个引用数据类型,比如函数
在多次调用构造函数时,每一次都会重新创建一个函数,必然会造成这个内存空间的浪费
// 原版构造函数(有小问题)
function Person(name, age) {
this.name = name
this.age = age
this.a = '我是随便添加的一个属性a,我是固定的内容'
this.fn = function () {
console.log('我是fn函数')
}
}
const p1 = new Person('QF666', 18)
/**
* 第一次调用Person构造函数
* 自动创建一个对象
* 1.添加一个属性name,值为形参name
* 2.添加一个属性age,值为形参age
* 3.添加一个属性a,值为一个固定的字符串
* 4.添加一个属性fn,值为一个函数,此时的函数是我们在这个函数内部定义的一个函数,假设地址为GD001
* 自动返回一个对象
*/
console.log(p1)
p1.fn()
const p2 = new Person('QF001', 99)
const p3 = new Person('QF003', 66)
/**
* 第二次调用Person构造函数
* 自动创建一个对象
* 1.添加一个属性name,值为形参name
* 2.添加一个属性age,值为形参age
* 3.添加一个属性a,值为一个固定的字符串
* 4.添加一个属性fn,值为一个函数,此时的函数是我们在这个函数内部定义的一个函数,假设地址为GD002
* 自动返回一个对象
*/
p2.fn()
console.log('验证两个对象内部的fn函数是否为同一个内存地址:', p1.fn === p2.fn, p1.fn === p3.fn)
//false false
更新版构造函数
// 更新版构造函数
function winFn() {
console.log('我是winFn函数')
}
function Person(name, age) {
this.name = name
this.age = age
this.a = '我是随便添加的一个属性a,我是固定的内容'
this.fn = winFn
}
const p1 = new Person('QF666', 18)
const p2 = new Person('QF001', 99)
p1.fn()
p2.fn()
console.log('验证两个对象内部的fn函数是否为同一个内存地址:', p1.fn === p2.fn)
//true true
四、原型(原型空间/原型对象)
1.什么是原型
-
每一个函数天生拥有一个属性
prototype,他的属性值是一个对象,我们通常把这个对象叫做这个函数的原型(空间|对象) -
原型这个对象中有一个属性叫做
constructor,这个属性表示的是:当前这个原型是哪个函数的原型 -
每一个对象天生拥有一个属性
__proto__(前后都是两个下划线),这个属性指向自己构造函数的原型
function Person() { }
// 向 Person函数的原型(对象)上添加一个属性age,值为18
Person.prototype.abc = '我是添加到Person函数的原型上的一个字符串,你可以通过这个Person创建出来的对象 身上的 __proto__ 查看到'
Person.prototype.age = 18
Person.prototype.name = '千锋'
Person.prototype.fn = function () {
console.log('我是添加在Person函数的原型对象上的一个函数')
}
console.log(Person.prototype)
let p1 = new Person()
/**
* 使用 new 结合 Person() 这个过程叫做构造函数的实例化,最终会得到一个对象
* 在我们当前案例中,是将这个对象放在变量p1里边
*
* 所以有些开发管这个p1叫做实例化对象,实例对象,本质上依然是一个对象
*/
// console.log(p1.__proto__)
console.log(p1.__proto__ === Person.prototype)
/**
* * __proto__ 属性指向自己构造函数的原型
*
* 因为p1这个对象的构造函数时Person
*
* 那么也就是说, p1.__proto__ 实际指向的就是Person 这个函数的原型
*/
-
使用
new结合Person()这个过程叫做构造函数的实例化,最终会得到一个对象 -
在我们当前案例中,是将这个对象放在变量p1里边
-
所以有些开发管这个
p1叫做实例化对象,实例对象,本质上依然是一个对象 -
因为
p1这个对象的构造函数是Person那么也就是说,p1.__proto__实际指向的就是Person 这个函数的原型
2.原型的作用
把构造函数中公共的方法提取出来,放在原型中
为什么?-- 构造函数的原型上的方法或者属性,在每一个实例化对象中都能正常访问
3.对象的访问规则(面试题)
- 访问对象的某一个属性时,会先在对象本身去查找
- 找到就直接使用
- 如果没有找到,那么回去对象的
__proto__中查找- 找到就使用
- 如果没有找到,那么回去这个对象(原型)的
__proto__查找 - 直到查找到 JS 的顶层对象
Object.prototype,如果还没找到,就不再向上查找
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.fn = function () {
console.log('我是添加在Person函数的原型对象上的一个函数')
}
let p1 = new Person('QF001', 18)
console.log(p1.__proto__)
let p2 = new Person('QF002', 28)
console.log(p2.__proto__)
console.log(p1)
console.log(p1.fn)
p1.fn()
console.log(p1.abc) //undefined
五、扩展内置构造函数
new Object()
new Array()
new RegExp()
new String()
JS 内的数据结构类
-
在 JS 中,任何一个数组,他的构造函数都是 Array
-
在 JS 中,任何一个对象,他的构造函数都是 Object
拿数组举例:Array.prototype
需求:在数组上扩展一个方法 getMax ,作用是求出数组中的最大值,并且要求所有数组都能使用
let arr = [1, 2, 3, 4, 5]
let arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Array.prototype.getMax = function () {
// console.log(this)
//函数的this指向是他的调用者,所以我们可以通过this,拿到数组中的值
let max = this[0]
for (let i = 1; i < this.length; i++) {
if (this[i] > max) {
max = this[i]
}
}
console.log(max)
}
arr.getMax()
arr2.getMax()