面向对象基础
了解面向对象
面向对象
- 面: 脸
- 向: 朝着
- 对象: 我们 JS 的数据类型
- 脸朝着我们 JS 的数据类型
- 学名: 关注对象的形式进行开发
- 面向对象核心: 对面向过程的高度封装, 高内聚低耦合
- 面向对象不是一个语法或者方法, 而是一个编程思想,是一个你怎么写代码
- 也就是你怎么写代码, 怎么完成功能的一个思想
面向过程
- 关注过程的开发, 我们以前写的选项卡, 九宫格, 轮播图都是面向过程的开发
- 我要关注里面的每一个步骤
- 获取哪些元素
- 设置哪些元素
- 先执行什么后执行什么
- …
swiper 面向对象的开发
- 关注对象的开发, 我们使用的 swiper 就是面向对象的开发
- 直接找到一个能帮我完成功能的东西
- 直接调用这个东西, 进行一些配置, 完成功能
- 面向对象是不是不需要面向过程了?
- 不是, 是因为有人把过程给我们封装好了
- 所以我们可以直接使用
例子:我要吃一碗打卤面
- 面向过程: 所有的步骤顺序细节都要考虑
- 和面 — 考虑多少水多少面粉
- 切面 — 多宽多细
- 煮面 — 煮熟了再吃
- 拌面 — 要和卤搅拌在一起, 拌多少卤
- 吃面 — 拿筷子吃, 还是拿手吃
- 面向对象: 我们考虑的只有找不找得到一个面馆
- 找到一个面馆
- 点一碗面(把你的要求都说出来)
- 等着吃
面向对象的思想
-
当我要吃 面 的时候, 找到一个 “面馆”
-
如果能找到 “面馆”, 那么我直接点一碗面
-
如果找不到 “面馆”, 那么我自己盖一个 “面馆”
-
然后再点一碗面
-
当你需要实现一个功能的时候, 找到有没有一个对象能帮我实现这个功能
-
如果有, 那么直接拿过来使用
-
如果没有, 那么我们创造一个 “机器”, 这个 “机器” 能帮我们制造一个完成功能的对象
-
然后我们使用这个 “机器” 创造一个对象完成功能
-
“机器”: 就是把面向过程拿一套东西高度封装起来, 能创造一个完成功能的对象
面向对象的核心: 对面向过程的高度封装, 高内聚低耦合
-
字面量创建对象
- 使用字面量的形式创建一个对象
- 创建的时候向里面添加一些内容
- 也可以再后期动态添加
- 缺点:这个东西不能量产
let obj = {
name: 'Jack',
age: 18,
fn: function () {}
}
obj.gender = '男'
obj.fun = function () {}
console.log(obj) // {name: "Jack", age: 18, gender: "男", fn: ƒ, fun: ƒ}
// 当出现第二个的时候, 需要再写一遍
let obj2 = {
name: 'Rose',
age: 20
}
内置构造函数创建对象
- 我们可以使用 内置构造函数 创建一个空对象
- 动态象里面添加内容
- 缺点:不能量产, 需要第二个, 那么一模一样的代码需要再写一遍
let obj = new Object()
obj.name = 'Jack'
obj.age = 18
obj.gender = '男'
console.log(obj) // {name: "Jack", age: 18, gender: "男"}
// 需要第二个对象
let obj2 = new Object()
obj2.name = 'Rose'
obj2.age = 20
console.log(obj2) // {name: "Rose", age: 20}
工厂函数创建对象
- 自己书写一个工厂函数
- 使用自己书写的工厂函数创建对象
- 什么是工厂函数?
- 就是一个普通的函数, 只不过这个函数能给我返回出来一个对象
- 我们给他起个名字叫做 工厂函数
// 1. 书写一个工厂函数
function createObj(name, age, gender) {
// 手动创建一个对象
let obj = {}
// 手动添加成员
obj.name = name
obj.age = age
obj.gender = gender
// 手动返回这个对象
return obj
}
// 2. 使用工厂函数创建对象
// 本次调用的时候, 传递进去的三个参数分别是 'Jack' 18 和 '男'
// 那么向对象里面添加的就是这三个数据
// 我传递的三个实参, 就是对象里面三个成员的值
let obj = createObj('Jack', 18, '男')
console.log(obj) // // {name: "Jack", age: 18, gender: "男"}
// 创建第二个对象
// 本次调用的时候, 传递的是 Rose 20 和 女
// 那么对象里面三个成员的值就分别是我传递进去的东西
let obj2 = createObj('Rose', 20, '女')
console.log(obj2) // {name: "Rose", age: 20, gender: "女"}
自定义构造函数创建对象
- 自定义一个构造函数
- 使用自定义的构造函数创建对象
自定义构造函数
- 一个自己写的函数, 就是一个函数
- 当你使用的时候和 new 关键字连用, 就是构造函数
构造函数里面
- 不需要我们手动创建对象, 他会自动创建
- 不需要我们手动返回对象, 他会自动返回
- 我们只需要手动向对象里面添加内容就可以了
- 构造函数必须要和 new 连用
- 在构造函数里面, this 指向当前创建的那个对象
- 只要这个函数和 new 关键字连用了,我们叫做 构造函数 调用
- 此时这个函数里面的 this 就指向本次调用的时候自动创建的对象
// 1. 书写一个自定义构造函数
// 构造函数也是函数, 只不过和 new 连用有了一个创造对象的能力
// 我们就给他起了一个 高端 一些的名字, 叫做构造函数而已
// 和普通函数的能力是一样的, 一样可以接收参数, 使用参数
function createObj(name, age) { // 自动创建对象
// 手动向对象里面添加成员
this.name = name
this.age = age
// 自动返回这个对象
}
// 2. 使用自定义构造函数创建对象
// 当一个函数和 new 关键字连用的时候, 就有自动创建对象的能力
// 本次调用的时候, 因为和 new 关键字连用了
// 所以构造函数会自动创建一个对象, 并且自动返回
// 我把返回的内容赋值给了 obj 这个变量
// 函数内部的 this 就是指向本次返回的对象
// 所以我写 this.name = 'Jack' 就是在向 obj 这个对象中添加成员
let obj = new createObj('Jack', 18) // new createObj 的时候, 这个函数需要执行一遍
console.log(obj) // createObj {name: "Jack", age: 18}
// 使用自定义构造函数创建第二个对象
let obj3 = new createObj('Rose', 20) // new createObj 的时候, 这个函数又需要执行一遍
console.log(obj3) // createObj {name: "Rose", age: 20}
// 当一个函数不和 new 关键字连用的时候, 那么没有自动创建对象的能力
let obj2 = createObj()
console.log(obj2) // undefined
// createObj 和 new createObj 调用的是一个函数
// 但是一个和 new 关键字连用了, 一个没有连用
// 和 new 连用的时候, this 指向本次调用创建出来的对象
// 没有和 new 连用的时候, 没有自动创建对象的能力, this -> window
构造函数的使用规则和规范
规范:建议你遵守, 因为大家都一样
-
如果你写的是一个构造函数, 将来需要和 new 连用的时候
-
建议你首字母大写, 没别的意思, 就是从名字上区分一下
-
function CreateObj2(name, age) { this.name = name this.age = age }
-
-
内置构造函数
- new Object()
- new Array()
- new Date()
- new RegExp()
-
第三方构造函数
- new Swiper()
-
-
当你使用构造函数的时候, 如果不需要传递参数
-
那么结尾的小括号可以省略不写
-
但是建议你不管是不是传递参数, 都最好写上
-
function Person() { this.name = 'Jack' } // 使用 Person 构造函数创建第一个对象 let p1 = new Person() console.log(p1) // 使用 Person 构造函数创建第二个对象 // 不传递参数的时候, 可以不写, 但是建议都写上 let p2 = new Person console.log(p2)
-
规则:必须遵守,不遵守肯定是不对
-
构造函数调用的时候, 必须要和 new 关键字连用
- 因为构造函数的意义就是创造对象
- 你不和 new 连用, 那么就没有自动创建对象的能力
- 那么构造函数的意义就没有了
-
构造函数内部的 this 指向本次调用创建的对象
-
我们管这个对象叫做 实例对象
-
我们管这个创造对象的过程叫做 实例化
-
函数内部的 this 都是指向当前实例
-
function Person() { this.name = 'Jack' } // 本次调用的时候, Person 函数内部的 this 指向 p1 let p1 = new Person() // 本次调用的时候, Person 函数内部的 this 指向 p2 let p2 = new Person()
-
-
构造函数内部不要写 return
-
因为构造函数会自动 return 每次创建的对象
-
所以你就不要再写 return 了
-
如果你 return 了一个基本数据类型, 那么写了白写
-
如果你 return 了一个复杂数据类型, 那么构造函数白写
-
function Person() { this.name = 'Jack' this.age = 18 this.gender = '男' // return 基本数据类型 // return 123 // 白写 // return 'hello world' // 白写 // return true // 白写 // return undefined // 白写 // return null // 白写 // return 复杂数据类型 // return [1, 2, 3] // 构造函数自动创建的对象, 不会被返回了 // return function () {} // 构造函数自动创建的对象, 不会被返回了 // return new Date() // 构造函数自动创建的对象, 不会被返回了 } let p1 = new Person() console.log(p1)
-
-
-
构造函数的意义: 就是用来创造对象的
构造函数不合理的地方
- 直接在构造函数体内书写方法, 会造成内存的资源浪费
- 写的越多浪费的越多
function Person() {
this.name = 'Jack'
this.age = 18
this.fn = function () { console.log('我是一个方法') }
// 这个只是创建了一个函数, 有一个函数空间
// 但是这个函数并没有放在实例对象身上
// 而且也会占用一部分空间, 每 new 一次, 就会创建一个
// var fun = function () {}
}
// 本次调用的时候, 在本次的对象空间内有一个函数空间(xf0)存在
let p1 = new Person()
console.log(p1)
// 本次调用的时候, 在本次的对象空间内也有一个函数空间(xf1)存在
let p2 = new Person()
console.log(p2)
// xf1 和 xf0 两个函数空间里面是一模一样的代码
// 一模一样的函数, 但是占了两份内存空间, 有一份是浪费的
// 假设我 new 一百次, 就会有一百个对象空间
// 一百个对象空间里面既有一百个一模一样的函数, 那么其中 99 个是浪费的
console.log(p1.fn === p2.fn) // false 证明两个函数不是一个地址, 如果是一个地址就得到 true
prototype - 原型
- 定义(背下来): 每一个函数都天生自带一个属性, 叫做 prototype, 它是一个对象
- 构造函数也是函数, 所以构造函数也天生自带这个 prototype 属性
- 当你想访问这个对象空间的时候, 就直接访问 函数名.prototype
function Person() {}
// 当你定义完毕函数, 这个空间就伴生存在了
// 就已经可以访问了, 他就是一个对象空间
// 和我们写的 {} 一样, 只不过是一个函数伴生的东西
console.log(Person.prototype)
// 既然是一个对象, 我就可以使用对象的操作语法, 在里面进行增删改查
// 添加个东西, 删除个东西, ...
// 向 Person 的原型上添加了一个叫做 fn 的成员, 值是一个函数
Person.prototype.fn = function () { console.log('我是一个函数') }
// 向 Person 的原型上添加了一个叫做 str 的成员, 值是一个字符串
Person.prototype.str = 'hello world'
console.log(Person.prototype)
原型方法
/*
__proto__(原型属性)
定义(背下来): 每一个对象天生自带一个属性, 叫做 __proto__, 指向所属构造函数的 prototype
构造函数创造出来的对象, 也是对象
所以也有 __proto__ 属性
一个对象, 如果由 xxx 构造函数 new 出来的,
那么我们就说这个对象所属的构造函数就是 xxx
也就是说, new 出来的对象属于 xxx 构造函数
*/
function Person() {}
// 先向 prototype 里面添加一个成员
Person.prototype.fn = function () { cosnole.log('我是 Person 原型上的一个方法') }
// p1 是一个对象, 是由 Person 构造函数 new 出来的一个对象
// 所以 p1 就是属于 Person 这个构造函数的
// p1 所属的构造函数就是 Person
// 因为 p1 也是一个对象, 所以 p1 也有 __proto__ 属性
// p1 的 __proto__ 指向 所属构造函数(Person) 的 prototype
// p1.__proto__ === Person.prototype
let p1 = new Person()
console.log(p1)
// 因为 p1.__proto__ === Person.prototype 得到的是 true
// 说明这两个名字指向一个空间地址
console.log(p1.__proto__ === Person.prototype)
构造函数的合理化
-
不合理
- 当你的方法直接写在构造函数体内的时候, 会根据创建对象的多少
- 有很多的方法是多余的, 占用更多没必要的资源
-
合理化
-
书写构造函数的时候
-
属性全都写在构造函数体内
-
方法全都写在构造函数的 prototype 上
-
function Person() { this.name = 'Jack' } Person.prototype.fn = function () { }
-
-
对象访问机制
- 当你访问一个对象的成员的时候
- 首先, 在自己身上查找, 有就直接使用, 停止查找
- 如果自己没有, 会自动去 proto 上找
- 如果有, 就直接使用, 停止查找
- 如果没有, 就再去 proto 上查找(未完待续)
// 1. 准备一个构造函数
function Person() {
this.name = 'Jack'
}
// 2. 向构造函数的 prototype 上添加一些成员
Person.prototype.fn = function () { console.log('我是 Person 原型上的一个方法') }
Person.prototype.age = 18
Person.prototype.name = 'Rose'
// 3. 使用 Person 构造函数创建一个对象
let p1 = new Person()
console.log(p1)
// 访问 p1 对象的 name 成员
// 因为 p1 自己就有, 所以就使用自己的
console.log(p1.name) // Jack
// 访问 p1 对象的 age 成员
// 因为 p1 自己没有, 所以就去自己的 __proto__ 上找
// 因为 p1.__proto__ === Person.prototype
// 所以就是去 Person.prototype 上找
console.log(p1.age)
// 访问 p1 对象的 fn 函数调用一下
// 因为 p1 自己没有, 所以就去自己的 __proto__ 上找
// 因为 p1.__proto__ === Person.prototype
// 所以就是去 Person.prototype 上找
p1.fn()
// 4. 使用 Person 构造函数再次创建一个对象
let p2 = new Person()
// 访问 p2 的 age 成员
// 因为自己没有, 就会去自己的 __proto__ 上找
// 因为 p2.__proto__ === Person.prototype
// 所以就是去 Person.prototype 上找
console.log(p2.age) // 18
// 访问 p2 对象的 fn 函数调用一下
// 因为自己没有, 就会去自己的 __proto__ 上找
// 因为 p2.__proto__ === Person.prototype
// 所以就是去 Person.prototype 上找
p2.fn()
// 此时, 我们构造函数的实例访问的方法
// 每一个实例身上都没有方法
// 需要访问的时候, 都是按照各个对象身上的 __proto__ 找到了
// Person.prototype 上面
// 每一个实例使用的方法, 都是 构造函数原型 上的那一个方法
// 避免了多次创造函数的问题, 只创建了一个函数
console.log(p1.fn === p2.fn) // true
和构造函数相关的 this
- 构造函数体内的 this
- 只要这个构造函数和 new 关键字连用了
- 那么这个构造函数体内的 this 一定指向当前实例
- 也就是本次调用的时候, 自动创建的那个对象
- 也就是本次调用的时候, 你前面写的那个变量
- 构造函数原型上的方法里面的 this
- 看是怎么调用的 ?
- 依靠实例调用
- this 指向 当前实例
- 直接使用原型调用 (基本不会这样使用)
- this 指向 当前原型
function Person() {
this.name = 'Jack'
}
// 我们之所以写在原型上, 就是为了给实例对象使用
// 因为直接写在构造函数体内, 会出现额外占用内存的问题
// 我们为了解决问题, 才把他写在 prototype 上
Person.prototype.fn = function () {
console.log('我是 Person.prototype 上的 fn 方法')
console.log(this)
}
// 使用构造函数
// 本次调用的时候, 因为和 new 关键字连用了
// Person 函数体内的 this 就指向 p1
let p1 = new Person()
// 我想调用那个 fn 方法, 我因该怎么书写
// 依靠实例调用
// 标准的对象调用方式 -> 点前面是谁就是谁
// 本次调用的时候, this -> p1, 当前实例
p1.fn()
// 直接使用原型调用
// 标准的对象调用方式 -> 点前面是谁就是谁
// 本次调用的时候, this -> Person.prototype
Person.prototype.fn()
constructor 构造器, 构造者
- 每一个函数的天生自带的那个 prototype 里面都有一个这个属性
- 指向的当前这个 prototype 所属的构造函数
- xxx.prototype 里面的 constructor 就指向这个 xxx
- 那一个构造函数的 prototype 里面的 constructor 就指向这个构造函数
当你访问任何一个对象的 constructor 的时候
- 对象自己身上没有, 就会去 proto 中找
- 实际上就是去 构造函数.prototype 中查找
- 找到的 constructor 就是指向这个 构造函数
- 换句话说, 每一个对象的 constructor 指向当前对象所属的构造函数
function Person() {}
console.log(Person.prototype) // {constructor: ƒ}
console.log(Person.prototype.constructor === Person) //true
function Student() {}
console.log(Student.prototype) // {constructor: ƒ}
console.log(Student.prototype.constructor === Student) //true
例子:万物皆对象
- let arr = [ ]
- 数组也是一个对象, 他是属于 Array 的
- 当你访问 arr.constructor 的时候
- 会去 arr.proto 中查找, 也就是去 Array.prototype 中查找
- Array.prototype.constructor 就指向 Array
- 所以 arr.constructor 就指向 Array
- let fn = function () { }
- 函数也是一个对象, 他是属于 Function 的
- 当你访问 fn.constructor 的时候
- 会去 fn.proto 中查找, 也就是去 Function.prototype 中查找
- Function.prototype.constructor 就指向 Function
- 所以 fn.constructor 就指向 Function
- let time = new Date()
- 时间对象也是一个对象, 他是属于 Date 的
- let obj = { }
- 对象也是一个对象, 他是属于 Object 的
var num = 123
var str = 'hello world'
var boo = true
function fn() {}
var reg = /^123$/
var time = new Date()
console.log(typeof num, num.constructor) // number ƒ Number() { [native code] }
console.log(typeof str, str.constructor) // string ƒ String() { [native code] }
console.log(typeof boo, boo.constructor) // boolean ƒ Boolean() { [native code] }
console.log(typeof {}, {}.constructor) // object ƒ Object() { [native code] }
console.log(typeof [], [].constructor) // object ƒ Array() { [native code] }
console.log(typeof fn, fn.constructor) // function ƒ Function() { [native code] }
console.log(typeof reg, reg.constructor) // object ƒ RegExp() { [native code] }
console.log(typeof time, time.constructor) // object ƒ Date() { [native code] }
if (num.constructor === Number) {
console.log(' num 变量是一个数值类型的变量 ') // num 变量是一个数值类型的变量
}
作用
- 我们可以用它来判断数据类型
- typeof 只能准确的判断基本数据类型, 不能准确的判断复杂数据类型
- undefined 和 null 不能使用他来判断数据类型
准确判断所有数据类型
-
Object 是一个内置构造函数
-
那么 Object 也有自己的原型, 叫做 Object.prototype
-
在 Object 的原型上有一个方法叫做 toString()
-
这个方法就可以检测所有的数据类型,但是需要和 call 连用
-
语法: Object.prototype.toString.call(你要检测的数据类型)
-
返回值: 是一个字符串
- ‘[object 你检测的数据类型]’
console.log(Object.prototype.toString.call(123)) // [object Number]
console.log(Object.prototype.toString.call('hello world')) // [object String]
console.log(Object.prototype.toString.call(true)) // [object Boolean]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call({})) // [object Object]
console.log(Object.prototype.toString.call([])) // [object Array]
console.log(Object.prototype.toString.call(function () {})) // [object Function]
console.log(Object.prototype.toString.call(new Date())) // [object Date]
console.log(Object.prototype.toString.call(/abcd/)) // [object RegExp]
ES6 的语法 — 构造函数
-
用 ES6 的语法书写构造函数,在 ES6 里面我们不叫作 构造函数 了, 叫做 类
-
定义 类 的关键字是 class
-
语法
-
class Person { constructor () { 这个位置是 ES5 的构造函数体 属性直接写在这里 this.name = 'Jack' } // 这里开始的位置都是原型 // 方法直接写在这里就好了 // 不需要 function 关键字 fn1 () {} }
-
类 和 构造函数的区别
-
构造函数本质上还是函数, 还有 function 关键字, 所以可以被当作普通函数调用,只不过没有了创造对象的能力, 但是不会报错
-
// 构造函数 function Person() { // 构造函数体, 属性写在这里 this.name = 'Jack' } // 原型, 方法写在原型上 Person.prototype.fn1 = function () {} Person.prototype.fn2 = function () {} let p1 = new Person() console.log(p1) // Person {name: "Jack"} // 构造函数也是函数, 可以当作普通函数调用 Person()
-
-
类, 不是函数了, 根本没有 function 关键字, 所以不可以被当作普通函数调用,使用的时候必须和 new 关键字连用,如果没有 new 关键字就会报错了
-
// ES6 的类 class Person { constructor (name) { // 构造函数体 this.name = name } // 这里就是原型 fn1 () { console.log('我是 Person 原型上的方法 fn1') } fn2 () { console.log('我是 Person 原型上的方法 fn2') } } let p1 = new Person('Jack') console.log(p1) // Person {name: "Jack"} // 类不是一个函数, 不能单独调用, 必须和 new 关键字连用 Person() // 报错
-
-