认识对象类型

在数据类型中我们知道还有一种特别的类型:对象类型
- 对象类型涉及到 JavaScript 的各个方面
- 对象类型是一种存储键值对(key-value)的更复杂的数据类型

为什么需要对象类型
- 基本数据类型可以存储一些简单的值,但是现实世界的事物抽象成程序时,往往比较复杂
- 比如一个人,有自己的特性(比如姓名、年龄、身高),有一些行为(比如跑步、学习、工作)
- 比如一辆车,有自己的特性(比如颜色、重量、速度),也有一些行为(比如行驶)

这时,我们需要一种新的数据类型将这些特性和行为组织到一起,这就是对象类型
- 对象类型可以使用 {...} 来创建,里面包含的是键值对("key:value"
- 键值对可以是属性和方法(在对象中的函数称之为方法)
- 其中 key 是字符串(也叫作属性名 property name,ES6之后也可以是 Symbol 类型)
- 其中 value 可以是任意类型,包括基本数据类型、函数类型、对象类型等

/*
函数/方法
  函数(function):在JavaScript代码中通过function直接定义一个结构称之为函数
  方法(method):如果将一个函数,放在对象中作为一个对象的属性,称之为方法
*/

// key:字符串类型。但是在定义对象的属性名时,大部分情况下是可以省略的
var person = {
  name: 'why',
  age: 18,
  height: 1.87,
  "my friend": {
	name: "kobe",
	age: 46
  },
  run: function () {
	console.log('running')
  },
  eat: function () {
	console.log('eat foods')
  },
  study: function () {
	console.log('studying')
  }
}

创建对象和使用对象

对象的创建方法有很多,包括三种
- 对象字面量(Object Literal):通过 {} 创建
- new Object + 动态添加属性
- new 其他类

属性之间是以逗号(comma)分隔的

对象的使用包括如下操作
- 访问对象的属性
- 修改对象的属性
- 添加对象的属性
- 删除对象的属性

var info = {
  name: 'why',
  age: 17,
  friend: {
	name: 'kobe',
	age: 30
  },
  running: function () {
	console.log('running~')
  }
}

// 访问对象中的属性
/*   console.log(info.name)
  console.log(info.friend.name)
  info.running() */
  
// 修改对象中的属性
/* info.age = 34
info.running = function () {
  alert('I am running')
}
console.log(info.age)
info.running() */

// 给对象添加新的属性
info.height = 1.88
info.studying = function () {
  console.log('I am studying')
}

console.log(info)

// 删除属性 delete 关键字(操作符)
delete info.age
delete info.name
console.log(info);

方括号和引用的使用

为什么需要方括号
- 对于如下这种属性的写法,JavaScript是无法理解的

这是因为点符号后面要求key是有效的变量标识符
- 不包括空格,不以数字开头,也不包含特殊符号(允许$和_)

这个时候我们可以使用方括号
- 方括号可以让我们在定义或者操作属性时更加的灵活

对象的遍历

对象的遍历(迭代):表示获取对象中所有的属性和方法
- Object.keys() 方法会返回一个由给定对象的其自身可枚举属性组成的数组

遍历方式一:普通 for 循环

var infoKeys = Object.keys(info)
 for (var i = 0; i < infoKeys.length; i++) {
   var key = infoKeys[i]
   var value = infoKeys[key]
   console.log(`key:${key}, value:${value}`)
 }

遍历方式二: for in 遍历方法

for (var key in info) {
  var value = info[key]
  console.log(`key:${key},value: ${value}`)
}

栈内存和堆内存

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

值类型和引用类型

原始类型的保存方式:在变量中保存的是值本身
- 所以原始类型也被称为值类型

对象类型的保存方式:在变量中保存的是对象的 “引用”
- 所以对象类型也被称为引用类型

为什么需要this

在常见的编程语言中,几乎都有 this 这个关键字(有些语言使用的是self),但是 JavaScript中的 this 和常见的面向对象语言中的 this 不太一样
- 常见的面向对象编程语言,比如 javaC++SwiftDart 等等一系列语言中,this 通常只会出现在类的方法中
- 也就是你需要有一个类,类中的方法(特别是示例方法)

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

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

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

// obj.running()

// var fn = obj.running
// fn()  默认调用,指向 window

function bar () {
  console.log(this) // obj
}

var friend = {

}

var obj = {
  name: 'why',
  friend: friend,
  bar: bar
}

obj.bar()
var info = {
  name: 'why',
  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()

javascript 中的 this 更加灵活,无论是它出现的位置还是它代表的含义

编写一个 obj 对象,有 this 和没有 this 的区别
- 没有 this 的话里面需要写上变量的名称,变量名修改了全部都需要修改
-

this指向什么

先掌握两个 this 的判断方法
- 以默认的方式调用一个函数,this 指向 windows
- 通过对象调用,this 指向调用的对象

类和对象的思维方式

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

比如下面的例子
- 游戏中创建一系列的英雄(英雄具备的特性是相似的,比如都有名字、技能、价格、但是具体的值又不相同)
- 学习系统中创建一系列的学生(学生都有学号、姓名、年龄等,但是具体的值又不相同)

当然其中一种方式是我们手动创建一系列的对象:

这种方式是最笨的,创建相似的对象需要编写如此多重复的代码
- 需要可以批量创建对象但是又让它们的属性不一样

创建对象的方案=工厂函数

我们可以想到一种创建对象的方式:工厂函数
- 先封装一个函数,这个函数用于帮助我们创建一个对象,我们只需要重复调用这个函数即可
- 工厂模式是一种很常见的设计模式

构造函数

工厂方法创建对象有一个比较大的弊端,我们在打印对象时,对象的类型都是 Object 类型
- 但是从某些角度来说,这些对象应该都有一个它们共同的类型

我们先理解一下什么是构造函数
- 构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数
- 在其他面向对象编程语言中,构造函数是存在于类中的一个方法,称之为构造方法
- 但是 JavaScript 中的构造函数有点不太一样,构造函数扮演了其他语言中类的角色

也就是在 JavaScript 中,构造函数其实就是类的扮演者
- 比如系统默认给我们提供的 Date 就是一个构造函数,也可以看成是一个类
- 在 ES5 之前,我们都过 function 来声明一个构造函数(类)的,之后通过 new 关键字来对其进行调用
- 在 ES6 之后,JavaScript 可以想别的语言一样,通过 class 来声明一个类。

那么什么是类(构造函数)呢?
- 现实生活中往往是根据一份描述、一个模板来创建一个实体对象的
- 编程语言也是一样,也必须先有一份描述,在这份描述中说明将来创建出来的对象有哪些属性(成员变量)和行为(成员方法)

比如在现实生活中,我们会如此来描述一些事物
- 比如水果 fruits 是一类事物的统称,苹果、橘子、葡萄等是具体的对象
- 比如人 person 是一类事物的统称,而 Jim、Lucy、Lily、李磊、韩梅梅是具体的对象

植物大战僵尸

JavaScript中的类(ES5)

JavaScript中类的表现形式就是构造函数

JavaScript中的构造函数是怎样的?
- 构造函数也是一个普通的函数,从表现形式上来说,和千千万万个普通函数没有任何区别
- 那么如果一个普通的函数被使用 new 操作符来调用了,这个函数就可以称之为是一个构造函数

如果一个函数被 new 操作符调用了,那么它会执行如下操作
1. 在内存中创建一个新的对象(空对象)
2. 这个对象内部的 [[prototype]] 属性会被赋值为该构造函数的 prototype 属性
3. 构造函数内部的 this,会指向创造出来的新对象
4. 执行函数的内部代码(函数体代码)
5. 如果构造函数没有返回非空对象,则返回创建出来的新对象

创建对象的方案-构造函数

我们通过构造函数来实现

 // JavaScript 已经提供了我们可以更加符合JavaScript思维方式(面向对象的思维方式)的一种创建对象的规则
// 在函数中的 this 一般指向某个对象
function Student (name, age, height) {
  this.name = name
  this.age = age
  this.height = height
  this.running = function () {
	console.log('running~')
  }
}

// 在函数调用前面加上 new 关键字(操作符)
var stu1 = new Student('rin', 18, 1.88)

这个构造函数可以确保我们的对象时具有 Student 类型的(实际是 constructor 的属性)

事实上构造函数还有很多其他的特性
- 比如原型、原型链、实现继承的方案
- 比如 ES6中类、继承的实现

全局对象 window

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

function foo () {
  // 自己的作用域
  // alert('Hello World')
  console.log(window.console === console)

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

foo()

Q.E.D.