五、JavaScript的面向对象

2022/6/29 JS基础对象类型的使用对象属性方括号的使用对象遍历栈内存堆内存值类型引用类型this工厂方法创建对象构造函数new创建对象全局对象

# 1、认识对象类型

  • 在数据类型中我们提到还有一种特别的类型:对象类型。

    • 对象类型涉及到JavaScript的各个方面,所以掌握对象类型非常重要;
    • 对象类型是一种存储键值对(key-value)的更复杂的数据类型;
  • 为什么需要对象类型呢?

    • 基本数据类型可以存储一些简单的值,但是现实世界的事物抽象成程序时,往往比较复杂;
    • 比如一个人,有自己的特性(比如姓名、年龄、身高),有一些行为(比如跑步、学习、工作);
    • 比如一辆车,有自己的特性(比如颜色、重量、速度),有一些行为(比如行驶);
  • 这个时候,我们需要一种新的类型将这些特性和行为组织在一起,这种类型就是对象类型

    • 对象类型可以使用{…}来创建的复杂类型,里面包含的是键值对(“key: value”);
    • 键值对可以是属性和方法(在对象中的函数称之为方法);
    • 其中key是字符串(也叫做属性名property name ,ES6之后也可以是Symbol类型,后续学习);
    • 其中value可以是任意类型,包括基本数据类型、函数类型、对象类型等;
//{'key':'value','key':'value''key':'value', ...}
var obj = {
    name:'lyk',
    age:18,
    flag:true,
    running:function () {
        console.log(obj.name+'正在跑步!')
    }
}

obj.running()
1
2
3
4
5
6
7
8
9
10
11

# 2、创建对象和使用对象

  • 对象的创建方法有很多,包括三种:
    • 对象字面量(Object Literal):通过{}
    • new Object+动态添加属性;
    • new 其他类;
// 1、对象字面量(Object Literal):通过{}
var obj = {
    name:'lyk',
    eating:function() {
        console.log(obj.name+'在吃东西!')
    }
}
obj.eating()
console.log(obj)


///2、new Object+动态添加属性;
var obj1 = new Object()
obj1.name = 'lyk1'
obj1.eating1 = function() {
    console.log(obj1.name+'在吃东西!')
}
obj1.eating1()
console.log(obj1)


// 3、new 其他类; 
function Person(name,age) {
    this.name = name
    this.age = age
    this.eating = function() {
      console.log(this.name+'在吃东西!')
    }
}
var p = new Person('lyka',25)
p.eating()
console.log(p)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  • 目前我们主要掌握对象字面量的方式,后续我们学习其他两种方式。

    • 属性之间是以逗号( comma )分割的;
  • 对象的使用过程包括如下操作:

    • 访问对象的属性;
    • 修改对象的属性;
    • 添加对象的属性;
    • 删除对象的属性;
// - 对象字面量(Object Literal):通过{}
var obj = {
    name:'lyk',
    eating:function() {
        console.log(obj.name+'在吃东西!')
    }
}
// - 访问对象的属性;
console.log(obj.name)
// - 修改对象的属性;
obj.name = '孙悟空'
console.log(obj)
// - 添加对象的属性;
obj.age = 99
console.log(obj)
// - 删除对象的属性;
delete obj.name
console.log(obj)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 3、方括号和引用的使用

  • 为什么需要使用方括号呢?
    • 对于多次属性来说,JavaScript是无法理解的。
//无法理解的属性
info.good friend = 'why'
1
2
  • 这是因为点符号要求 key 是有效的变量标识符

    • 不包含空格,不以数字开头,也不包含特殊字符(允许使用 $ 和 _);
  • 这个时候我们可以使用方括号:

    • 方括号运行我们在定义或者操作属性时更加的灵活;
var message = "Hello World"
var obj = {
    "good friend": "why",
    [1+2]: "你好,世界"
}

console.log(obj["good friend"])//why
console.log(obj[1+2])//你好,世界


var friend = "good friend"
console.log(obj.friend)//undefined
console.log(obj[friend])//why
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4、对象的遍历

  • 对象的遍历(迭代):表示获取对象中所有的属性和方法。

    • Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组;
  • 遍历方式一:普通for循环

var obj = {
    name: 'lyk',
    age:21,
    height:1.88
}
console.log(Object.keys(obj))//["name","age","height"]

var objKeys = Object.keys(obj)

for(var i =0;i<objKeys.length;i++) {
    var key = objKeys[i]
    var value = obj[key]
    console.log(`key:${key},value:${value}`)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 遍历方式二:for in 遍历方法
var obj = {
    name: 'lyk',
    age:21,
    height:1.88
}


for(var key in obj) {
    console.log(key)
    var value = obj[key]
    console.log(`key:${key},value:${value}`)
}
1
2
3
4
5
6
7
8
9
10
11
12

# 5、栈内存和堆内存

  • 我们知道程序是需要加载到内存中来执行的,我们可以将内存划分为两个区域:栈内存和堆内存。
    • 原始类型占据的空间是在栈内存中分配的
    • 对象类型占据的空间是在堆内存中分配的;

jmUSCF.png

  • 栈/堆内存之图解案例一:JavaScript内存分配

jmqKDH.png

# 6、值类型和引用类型

  • 原始类型的保存方式:在变量中保存的是值本身

    • 所以原始类型也被称之为值类型;
  • 对象类型的保存方式:在变量中保存的是对象的“引用”

    • 所以对象类型也被称之为引用类型;
  • 下面的 【JavaScript中的一些现象】 中有 值类型和引用类型 的 相关案例图解(方便理解)

# 7、JavaScript中的一些现象(值类型和引用类型的理解)值传递,引用传递

  • 现象一:两个对象的比较
// var num1 = 123
// var num2 = 123
// console.log(num1 === num2)//true

// 1.现象一: 两个对象的比较
var obj1 = {}
var obj2 = {}
console.log(obj1 === obj2)//false
1
2
3
4
5
6
7
8

jnTuIP.png

  • 现象二:引用赋值
// // 2.现象二: 引用的赋值
var info = {
  name: "why",
  friend: {
    name: "kobe"
  }
}

var friend = info.friend
friend.name = "james"
console.log(info.friend.name) // james

1
2
3
4
5
6
7
8
9
10
11
12

jnHPje.png

  • 现象三:值传递
 // 3.现象三: 值传递
  function foo(num) {
    console.log(num)//100
    num = 200
    console.log(num)//200
  }
  var num = 100
  foo(num)
  console.log(num) // 100
1
2
3
4
5
6
7
8
9

jnbBz8.png

  • 现象四:引用传递, 但是在函数中创建了一个新对象, 没有对传入对象进行修改
// 4.现象四: 引用传递, 但是在函数中
//创建了一个新对象, 没有对传入对象进行修改
function foo(a) {
  a = {
    name: "why"
  }
}
var obj = { 
  name: "obj" 
}
foo(obj)
console.log(obj)//{ name: 'obj' }
1
2
3
4
5
6
7
8
9
10
11
12

jnLu36.png

  • 现象五:引用传递, 但是对传入的对象进行修改
// 5.现象五: 引用传递, 但是对传入的对象进行修改
function foo(a) {
  a.name = "why"
}

var obj = {
  name: "obj"
}
foo(obj)
console.log(obj)
1
2
3
4
5
6
7
8
9
10

jnLxqe.png

# 8、为什么需要this?

  • 在常见的编程语言中,几乎都有this这个关键字(Objective-C中使用的是self),但是JavaScript中的this和常见的面向对象语言中的this不太一样:

    • 常见面向对象的编程语言中,比如Java、C++、Swift、Dart等等一系列语言中,this通常只会出现在类的方法中。
    • 也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象;
  • 但是JavaScript中的this更加灵活,无论是它出现的位置还是它代表的含义;

  • 我们来看一下编写一个obj的对象,有this和没有this的区别:(有this)

// 1、没有this的情况
var obj = { 
    name: "lyka",
    age: 18,
    running: function() {
        console.log("running~", obj.name)
    },
    eating: function() {
        console.log("eating~", obj.name)
    },
    studying: function() {
        console.log("studying~", obj.name)
    }
}

obj.running()
obj.eating()
obj.studying()


///2、有this的情况
var info = {
    name: "lyk",
    age: 18,
    running: function() {
        console.log("running~", this.name)
    },
    eating: function() {
        console.log("eating~", this.name)
    },
    studying: function() {
        console.log("studying~", this.name)
    }
}

info.running()
info.eating()
info.studying()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 9、this指向什么?

  • 目前掌握两个this的判断方法:

    • 在全局环境下面,this指向window;

    • 通过对象调用,this指向调用的对象;

 // 函数中是有一个this的变量, this变量在大多数情况下会指向一个对象
    // arguments保存的是传入的所有参数

    // 情况一: 如果普通的函数被默认调用, 那么this指向的就是window
    function foo(name, age) {
      console.log(arguments)// {"0":"abc","1":123}
      console.log(this)//Window
    }
    foo("abc", 123)


    function sayHello(name) {
      console.log(this)//Window
    }
    sayHello()


    // 情况二: 如果函数它是被某一个对象来引用并且调用它, 那么this会指向这个对象(调用的那个调用)
    var obj = {
      name: "lyk",
      running: function() {
        console.log(this)//obj对象
        console.log(obj)//obj对象
        console.log(this === obj)//true
      }
    }
    obj.running()

// 考验题目
  // 1.题目一:
    var obj1 = {
      name: "lyk1",
      running: function() {
        console.log(this)
        console.log(obj1)
        console.log(this === obj1)
      }
    }
    obj1.running()//调用running方法时方法内部打印情况:obj1对象  obj1对象  true
    var fn = obj1.running
    fn() //调用fn函数时内部打印情况: window  obj1对象  false



  // 2.题目二:
    function bar() {
      console.log(this) // obj对象
    }
    var obj = {
      name: "why",
      bar: bar
    }
    obj.bar()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
  • 后续我们还会学习其他,也会给大家总结this的规律;

# 10、类和对象的思维方式

  • 我们来思考一个问题:如果需要在开发中创建一系列的相似的对象**,我们应该如何操作呢?**

  • 比如下面的例子:

    • 游戏中创建一系列的英雄(英雄具备的特性是相似的,比如都有名字、技能、价格,但是具体的值又不相同)
    • 学生系统中创建一系列的学生(学生都有学号、姓名、年龄等,但是具体的值又不相同)
  • 当然,一种办法是我们创建一系列的对象:如下

var p1 = {
  name:'张三',
  age:18,
  height:1.78,
  address:'深圳市'
}

var p2 = {
  name:'李四',
  age:28,
  height:1.72,
  address:'上海市'
}

var p3 = {
  name:'王五',
  age:19,
  height:1.68,
  address:'北京市'
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • 这种方式有一个很大的弊端:创建同样的对象时,需要编写重复的代码;
    • 我们是否有可以批量创建对象,但是又让它们的属性不一样呢?工厂函数

# 11、创建对象的方案 – 工厂函数

  • 我们可以想到的一种创建对象的方式:工厂函数
    • 我们可以封装一个函数,这个函数用于帮助我们创建一个对象,我们只需要重复调用这个函数即可;
    • 工厂模式其实是一种常见的设计模式;
function createPerson(name,age,height,address) {
    var p = {}
    p.name = name
    p.age = age
    p.height = height
    p.address = address
    
    p.running = function() {
        console.log(`${this.name}正在跑步`)
    }

    return p
}

var p1 = createPerson('张三',18,1.88,'天津市')
var p2 = createPerson('李四',28,1.78,'北京市')
var p3 = createPerson('王二',19,1.98,'深圳市')
var p4 = createPerson('麻子',21,1.65,'上海市')
p1.running()
console.log(p1,p2,p3,p4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 12、认识构造函数

  • 工厂方法创建对象有一个比较大的问题:我们在打印对象时,对象的类型都是Object类型

    • 但是从某些角度来说,这些对象应该有一个他们共同的类型;
    • 下面我们来看一下另外一种模式:构造函数的方式;
  • 我们先理解什么是构造函数?

    • 构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数;
    • 在其他面向的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法;
    • 但是JavaScript中的构造函数有点不太一样,构造函数扮演了其他语言中类的角色;
  • 也就是在JavaScript中,构造函数其实就是类的扮演者**:**

    • 比如系统默认给我们提供的Date就是一个构造函数,也可以看成是一个类;
    • 在ES5之前,我们都是通过function来声明一个构造函数(类)的,之后通过new关键字来对其进行调用;
    • 在ES6之后,JavaScript可以像别的语言一样,通过class来声明一个类;
  • 那么类和对象到底是什么关系呢?

# 13、类和对象的关系

  • 那么什么是类(构造函数)呢?

    • 现实生活中往往是根据一份描述/一个模板来创建一个实体对象的.
    • 编程语言也是一样, 也必须先有一份描述, 在这份描述中说明将来创建出来的对象有哪些属性(成员变量)和行为(成员方法)
  • 比如现实生活中,我们会如此来描述一些事物:

    • 比如水果fruits是一类事物的统称,苹果、橘子、葡萄等是具体的对象;
    • 比如人person是一类事物的统称,而Jim、Lucy、Lily、李雷、韩梅梅是具体的对象;

ju2Lh4.png

# 14、JavaScript中的类(ES5)

  • 我们前面说过,在JavaScript中类的表示形式就是构造函数。

  • JavaScript中的构造函数是怎么样的?

    • 构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别;
    • 那么如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数;
  • 如果一个函数被使用new操作符调用了,那么它会执行如下操作:

    • 在内存中创建一个新的对象(空对象);
    • 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;(后面详细讲);var p = new Person 则 p.__proto__ === Person.prototype
    • 构造函数内部的this,会指向创建出来的新对象;
    • 执行函数的内部代码(函数体代码);
    • 如果构造函数没有返回非空对象,则返回创建出来的新对象;
  • 接下来,我们可以用构造函数的方式来实现一下批量创建学生。

# 15、创建对象的方案 – 构造函数(类)

  • 我们来通过构造函数实现一下:
function Person(name,age,height,address) {
    this.name = name
    this.age = age
    this.height = height
    this.address = address
    
    this.running = function() {
        console.log(`${this.name}正在跑步`)
    }
}

var p1 = new Person('张三',18,1.88,'天津市')
var p2 = new Person('李四',28,1.78,'北京市')
console.log(p1,p2)
console.log(p1.__proto__ === Person.prototype)//true
console.log(p2.__proto__ === Person.prototype)//true
console.log(p1.__proto__ === p2.__proto__)//true
p1.running()//张三正在跑步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • 这个构造函数可以确保我们的对象是有Person的类型的(实际是constructor的属性,这个我们后续再探讨);
  • 事实上构造函数还有很多其他的特性:
    • 比如原型、原型链、实现继承的方案
  • 构造函数命名的补充
// 构造函数的名称: 使用大驼峰
function Person() {

}

var p1 = new Person()
console.log(p1)

// 普通函数: 使用小驼峰
function sayHello() {

}

// 平时创建普通的对象
// new Object()
var obj1 = {}
var obj2 = new Object()
var obj3 = new Person()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 16、全局对象(补充)

// 浏览器中存在一个全局对象object -> window
    // 作用一: 查找变量时, 最终会找到window头上
    // 作用二: 将一些浏览器全局提供给我们的变量/函数/对象, 放在window对象上面
    // 作用三(了解): 使用var定义的变量会被默认添加到window上面

    console.log(window)//Window

    // 使用var定义变量
    var message = "Hello World"

    function foo() {
    // foo函数自己的作用域
      //在window上的一些函数和对象
      console.log(Object.prototype.toString.call(window.console))// [object Object]
      console.log(Object.prototype.toString.call(window.console.log))// [object Function]
      console.log(Object.prototype.toString.call(window.alert))// [object Function]
      alert("Hello World")//window上的函数alert
      console.log(window.console === console)//true       window上的对象console

      // 创建一个对象
      var obj = new Object()
      console.log(window.Object === Object)//true

      // DOM
      console.log(document)

      // window.message
      console.log(window.message) //Hello World
    }

    foo()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 17、函数也是对象(理解)

 // 定义原始类型的变量
    var name = "why"
    var age = 18

    // 定义对象类型的变量
    // 地址 - 指针 - 引用
    var obj = {} // 堆内存
    var foo = function() {} // 堆内存
    function bar() {} // 堆内存

    console.log(typeof obj) // object
    console.log(Object.prototype.toString.call(obj))// [object Object]
    console.log(typeof foo) // function --> object
    console.log(Object.prototype.toString.call(foo))// [object Function]

    // var stu = new Student() // stu是一个Student -> Person

    // 引申一些别的知识(了解)
    var info = {}
    info.name = "abc"

    function sayHello() {
    }
    sayHello.age = 18
    console.log(sayHello.age)//18

    function Dog() {

    }

    // 构造函数上(类上面)添加的函数, 称之为类方法
    Dog.running = function() {}
    Dog.running()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
最后更新时间: 2022/07/18, 18:32:30
彩虹
周杰伦