面向对象
什么是面向对象?
首先大家要明白,面向对象并不是一个语法,也不是一个新的语言,
它是 JS 为完成需求的一种思想
-
在我们最开始接触 JS 时,我们解决问题的方式都是
面向过程的,按步骤书写代码,这种方式注重的是过程,每一个步骤都书写详细,输入的代码都是按顺序完成
但是这种方式也是存在问题的:
(1).每一个实现的功能相互影响 (2).同样的功能,当同时存在多个同样功能时,实现JS的代码需要书写多遍 -
但是
面向对象就不一样了,面向对象注重结果,也就是得到一个对象,而这个对象就是我们的需求; -
我们以吃面为例看看两者的区别
(1).面向过程: 需要准备面粉,准备水,和面,切面,热水,煮面,吃面 可以看出来面向过程步骤虽然详细但是繁琐 (2).面向对象: 找一个面馆,下单,吃面, 可以看出来,面向对象就是将繁琐的步骤以面馆代替, 而我们只需通过下单去调用就可以实现我们吃面的需求
创建对象的方式
- 字面量方式;
- 这种方式不适用批量创建对象
let obj = {
name: 'qwe',
age:18
}
- 内置构造函数的方式:
- 这种方式不适用批量创建对象
let obj1 = new Object()
- 工厂函数方式:
- 其实就是创建一个
函数,但是函数内部可以创建对象,这种函数叫做工厂函数
function createObj(Number){
//3.1 手动创建对象
const obj = {}
//3.2给对象添加属性
obj.name = 'qwe'//添加一个固定的字符串,每次创建对象它的属性都是 'qwe'
obj.age = Number// 利用形参给对象赋值变量,这样每次创建对象都可以修改这个属性的值
//3.3 手动给对象返回
return obj
}
let obj1 = createObj(18)
let obj2 = createObj(250)
console.log(obj1)
console.log(obj2)
构造函数的方式
首先我们需要明白;所谓的构造函数,其本质上就是一个函数,
但是如果在调用时,前面加上关键字new,我们通常称之为构造函数
接下来我们从书写方式上看看构造函数和工厂函数有什么不同点
1. 工厂函数
function createObj(Number){
//3.1 手动创建对象
//3.2给对象添加属性
//3.3 手动给对象返回
}
2. 构造函数:
function createObj(Number){
//4.1 自动创建对象 // <--- 通过 this 访问
//4.2 手动给对象添加属性
//4.3 自动返回对象
}
*** 示例**
function Person(name,age){
//因为构造函数自动创建的对象通过 this 访问,
//所以添加属性时可以通过this 添加
this.a = '我是随便添加的属性,固定的内容'
this.name = name
this.age = age
}
const p1 = new Person('qwe',18)
console.log(p1)
但是在我们书写构造函数时,也有一些需要我们注意的
- 1.构造函数的函数名首字母大写
- (建议,非强制,目的就是为了和普通函数进行区分)
- 2.构造函数内不要写 return
- 如果 return 是一个基本数据类型,写了也没用
- 如果 return 是一个引用数据类型,写了会导致构造函数失效
- 3.构造函数调用时,必须 个 new 关键字连用
- 如果不练用,也可以调用,但是构造函数会失效
- 4.构造函数内部的 this 指向
- 当一个函数和 new 连用时,那么这个函数是构造函数
- 然后内部的 this 指向本次调用被自动创建的对象
- 5.构造函数不能使用 箭头函数
- 因为 箭头函数 内部没有 this
构造函数的缺点
- 内部如果有引用数据类型的,比如函数
- 在每次调用函数时,每一次都会重新创建一个函数,必然会造成空间的浪费
function Person(name,age){
this.a = '我是随便添加的属性,固定的内容'
this.name = name
this.age = age
this.fn = function(){
}
}
const p1 = new Person('qwe',18)
const p2 = new Person('asd',22)
/**
* 第一次调用Person构造函数+
* 自动创建一个对象
* 1.添加一个屈性name,值为形参name
* 2.添加一个属性age,值为形参age
* 3.添加一个屈性a,值为一个囵定的字符串
* 4.添加一个属性fn,值为一个函数,业时的函数是我们在这个函数内部定义的一个函数,假设地址为coe1
* 自动返回一个对象
*/
p1.fn()
/**
* 第二次调用Person构造函数+
* 自动创建一个对象
* 1.添加一个屈性name,值为形参name
* 2.添加一个属性age,值为形参age
* 3.添加一个屈性a,值为一个囵定的字符串
* 4.添加一个属性fn,值为一个函数,业时的函数是我们在这个函数内部定义的一个函数,假设地址为coe2
* 自动返回一个对象
*/
p2.fn()
- 对于这种问题,我们可以采取下面的方法解决
function winFn(){
console.log('我是fn函数')
}
function Person(name,age){
this.a = '我是随便添加的属性,固定的内容'
this.name = name
this.age = age
this.fn = winFn
}
const p1 = new Person('qwe',18)
const p2 = new Person('asd',22)
console.log(p1)
console.log(p2)
原型(很重要)
- 又叫 : 原型空间,原型对象
什么是原型
* 每一个函数天生都有一个属性, **prototype** 它的属性值的是一个对象
我们通常把这个对象叫做 这个函数的原型(空间 | 对象)
* 这个对象中有一个属性 **constructor**,
这个属性表示的是:
当前这个原型 是哪个 函数的原型
* 每一个对象天生拥有一个属性 __proto__ (前后各有两个下划线)
这个属性指向自己构造函数的原型
function Person(){}
//向 Person 函数的原型上 添加一个属性 age ,值为18
Person.prototype.age = 18
//向 Person 函数的原型上 添加一个属性 fn ,值为一个函数
Person.prototype.fn = function(){
console.log('我是再原型上添加的fn函数')
}
console.log(Person.prototype)
// 函数的属性,打印 {constructor: ƒ} => constructor : ƒ Person() 表示函数原型
console.log(Person)
// Person(){} 会打印出来函数
let p1 = new Person()
console.log(p1)//空对象 Person{}
/**
* 使用 new 结合 Person() ,这个过程叫做 构造函数实例化,最终会的到一个对象
* 再我们当前案例中,是将这个对象放在变量 p1 里面
*
* 所以有些开发把 p1 叫做 实例化对象,实例对象,
* 但本质上依然是一个对象
*/
console.log(p1.__proto__)
//{constructor: ƒ} => constructor : ƒ Person() 表示函数原型
/**
* __proto__ 这个属性指向自己构造函数的原型
*
* 因为 p1 这个对象的构造函数 是 Person()
* 所以打印 Person() 的原型 p1.__proto__ 指向的就是 Person() 的原型
*/
原型的作用:
-
把构造函数中的 公共方法 提取出来,放在原型中
-
为什么这么做
- 构造函数的原型上的方法或者属性。在每一个实例化对象中都能正常访问
-
对象的访问规则:
-
访问对象的缪一个属性时,会先在对象本身去查找, -
找到就直接使用,如果没有找到 -
就会去对象的 __proto__ 中去找,找到就使用 -
如果还没有找到,那么会去这个对象(原型)的 __proto__ 查找 -
知道查找到 JS 的顶层对象 Object.prototype -
如果还找不到,就不再向上查找了
-
-
代码示例如下:
function Person(name, age, exe) {
this.name = name
this.age = age
this.exe = 1
}
Person.prototype.fn = function () {
console.log('我是再原型上添加的fn函数')
}
let p1 = new Person('qwe', 10)
console.log(p1) // Person {name: 'qwe', age: 10, exe: 1}
console.log(p1.age) // 10 => 现在自身找,自身存在age,直接使用
console.log(p1.exe) // 1 => 自身没有exe,去Person,找到使用
console.log(p1.max) // undefined =>按规则查找,没有找到,输出undefined
原型经典练习题
const fn = new Function('console.log(123)')
fn()
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () {
console.log('你好~~~')
}
const p1 = new Person('QF001', 18)
/**
* 1. p1 的 __proto__ 指向谁
* __proto__ 指向自己构造函数的原型
* 所以 相当于 指向了 Person.prototype
*
* p1.__proto__ === Person.prototype
*
* 2. Person 的 __proto__ 指向谁
* Person 是一个构造函数, 构造函数本质上就是一个函数而已
* 在 JS 中, 只要是一个函数, 那么就属于构造函数 Function 的实例化对象
* __proto__ 指向自己构造函数的原型
* 所以相当于 指向了 Function.prototype
*
* Person.__proto__ === Function.prototype
*
* 3. Person.prototype 的 __proto__ 指向谁
* Person.prototype 是 Person 构造函数的原型, 本质上就是一个对象而已
* 在 JS 中, 只要是一个对象, 那么就属于构造函数 Object 的实例化对象
* __proto__ 指向自己构造函数的原型
* 所以相当于指向了 Object.prototype
*
* Person.prototype.__proto__ === Object.prototype
*
* 4. Function 的 __proto__ 指向谁
* Function 是一个构造函数, 构造函数本质上就是一个函数而已
* 在 JS 中, 只要是一个函数, 那么就属于构造函数 Function 的实例化对象
* __proto__ 指向自己构造函数的原型
* 所以相当于 指向了 Function.prototype
*
* Function.__proto__ === Function.prototype
*
* 5. Function.prototype 的 __proto__ 指向谁
* Function.prototype 是 Function 构造函数的原型, 本质上就是一个对象而已
* 在 JS 中, 只要是一个对象, 那么就属于构造函数 Object 的实例化对象
* __proto__ 指向自己构造函数的原型
* 所以相当于指向了 Object.prototype
*
* Function.prototype.__proto__ === Object.prototype
*
* 6. Object 的 __proto__ 指向谁
* Object 是一个构造函数, 构造函数本质上就是一个函数而已
* 在 JS 中, 只要是一个函数, 那么就属于构造函数 Function 的实例化对象
* __proto__ 指向自己构造函数的原型
* 所以相当于 指向了 Function.prototype
*
* Object.__proto__ === Function.prototype
*
* 7. Object.prototype 的 __proto__ 指向谁
* 因为 Object.prototype 是 JS 的顶层对象, 在往上就没有了, 所以这个值为 null
* console.log(Object.prototype.__proto__) null
*/
console.log(p1.__proto__ === Person.prototype) //true
console.log(Person.__proto__ === Function.prototype) //true
console.log(Person.prototype.__proto__ === Object.prototype) //true
console.log(Function.__proto__ === Function.prototype) //true
console.log(Function.prototype.__proto__ === Object.prototype) //true
console.log(Object.__proto__ === Function.prototype) //true
console.log(Object.prototype.__proto__) //null
ES6
模板字符串
在ES5中要么是单引导包裹要么是双引号包裹
在ES6 中推出的模板字符串是使用 反引号 包亵的
和之前的区别
1.反引号能够换行,但是单引号双引号不行
2.内部如果书写的有 ${} 然后在内部可以识别出变量
展开字符串
在ES6 推出了展开运算符 ... , 其作用主要有以下几个方面:
1.展开数组
let arr = [1, 2, 3]
console.log(arr)// [1, 2, 3]
console.log(...arr)//1 2 3
2.合并数组
let arr2 = [100, 200, 300, ...arr]
console.log(arr2) // [100, 200, 300, 1, 2, 3]
3.展开对象
let obj = {
a: 1,
b: 2
}
let obj2 = {
...obj,
c: 3,
d: 4
}
console.log(obj2) //{a: 1, b: 2, c: 3, d: 4}
4.函数传参
function fn(a, b, c) {
console.log('我是传参实现的函数')
}
// fn(arr[0], arr[1], arr[2],)//普通传参方式
fn(...arr)
对象简写语法
- 当对象的 key 与 value 相同时 , 并且 value 是变量时, 那么其中一个可以不写
- 函数是可以简写
let name = '游戏'
let obj3 = {
// name: '电脑',
// name: name,
name,
fn: function () {
console.log('我是函数')
},
fn1() {
console.log('我是简化写法的函数')
}
}
console.log(obj3)//{name: '游戏', fn: ƒ, fn1: ƒ}
obj3.fn()//我是函数
obj3.fn1()//我是简化写法的函数