JavaScript的面向对象
对象类型是一种存储键值对(key-value)的复杂的数据类型
基本数据类型可以存储一些简单的值,但是现实世界的事物抽象成程序时,往往比较复杂
这个时候,我们需要一种新的类型将这些特性和行为组织在一起,这种类型就是对象类型
对象类型可以使用{...}来创建的复杂类型,里面包含的是键值对(“key: value”)
键值对可以是属性和方法(在对象中的函数称之为方法)
其中key是字符串(也叫做属性名property name ,ES6之后也可以是Symbol类型,后续学习);
其中value可以是任意类型,包括基本数据类型、函数类型、对象类型等;
/*
两个术语: 函数/方法
函数(function): 如果在JavaScript代码中通过function默认定义一个结构, 称之为是函数.
方法(method): 如果将一个函数放到对象中, 作为对象的一个属性, 那么将这个函数称之为方法.
*/
function foo() {
}
// key: 字符串类型, 但是在定义对象的属性名时, 大部分情况下引号都是可以省略的
// 对象的多个键值对之间使用逗号进行分割
var person = {
// key: value
// name是属性名
// wjl是属性值
name: "wjl",
// 对象的属性名可以是字符串或symbol类型的值
// 如果key是字符串类型,且是合法的js变量对应的字符串的时候,key的引号一般会省略
age: 18,
height: 1.88,
// 但是如果key的类型是字符串,且key不是一个合法的js变量的时候,key的引号不可以省略
'my friend': 'kobe',
"my friend": {
name: "kobe",
age: 30
},
// 对象的值可以是任何的合法数据类型
// 如果对应的属性值是一个函数(function)的时候,我们就称这种函数为方法(method)
run: function() {
console.log("running")
},
eat: function() {
console.log("eat foods")
},
study: function() {
console.log("studying")
}
}
1.创建对象和使用对象
- 对象字面量(Object Literal)
// 1.对象字面量
var obj1 = {
name: "wjl"
}
- new Object+动态添加属性
// 2.new Object()
// Object构造函数
var obj2 = new Object()
obj2.name = "kobe"
- new 其他类 (可以是JS内置的类也可以是用户自定义的类)
function Person() {}
var obj3 = new Person()
2. 对象的常见操作
// 1.定义了一个对象
var info = {
name: "why",
age: 18,
friend: {
name: "kobe",
age: 30
},
running: function() {
console.log("running~")
}
}
// 2.访问对象中的属性
// 访问属性
// 如果访问一个对象上没有定义的属性的时候
// 那么获取到的属性值就是undefined
// console.log(info.name)
// console.log(info.friend.name)
// info.running()
// 3.修改对象中的属性
// info.age = 25
// info.running = function() {
// alert("I am running~")
// }
// console.log(info.age)
// info.running()
// 4.添加对象中的属性
info.height = 1.88
info.studying = function() {
console.log("I am studying~")
}
console.log(info)
// 5.删除对象中的属性
// delete关键字(操作符)
delete info.age
delete info.height
console.log(info)
2.1访问对象点语法和中括号语法
方括号在对象中的使用,使我们在定义或者操作属性时更加的灵活
let address = Symbol('address')
var message = 'Hello World'
var obj = {
// 在对象中,如果key的类型不是字符串或Symbol类型值的时候
// js会尽可能将key所对应的值转换为字符串类型 如 1 -> '1', true -> 'true'
name: "why",
"my friend": "kobe",
[message]: '你好, 世界',
// 计算属性: 属性值使用方括号进行包裹
// 方括号中的值 可以是变量, js表达式 或 普通变量
[address]: 'shanghai',
"eating something": function() {
console.log("eating~")
}
}
// 也可以使用中括号方式去访问对象的属性
// 此时这个属性名可以任意,可以是变量,合法的js变量对应的字符串,或是其它
console.log(obj["my friend"]) // 不合法js变量对应的字符串
console.log(obj[message]) // 普通变量
console.log(obj[address]) // symbol类型的变量
console.log(obj.name)
console.log(obj["name"])// 合法js变量对应的字符串
// obj["eating something"]()
var eatKey = "eating something"
obj[eatKey]()
2.2对象定义案例
// 定义商品对象
var product = {
name: "鞋子",
desc: "鞋子非常棒!!!",
price: 99,
brand: "nike"
}
// 定义手机对象
var phone = {
name: "iPhone 13 Pro Max",
desc: "对iPhone的描述信息",
price: 888,
callPhone: function(phoneNum) {
console.log("打电话给某人:", phoneNum)
},
playGame: function(gameName) {
console.log("玩游戏:", gameName)
}
}
// 定义用户对象
var user = {
id: 1111111,
account: "coderwjl",
nickname: "coderwjl",
password: "xxx123",
avatarURL: "图片地址",
role: {
id: 110,
name: "管理员",
createTime: "2033-03-03"
}
}
3.遍历
对象的遍历(迭代):表示获取对象中所有的属性和方法
Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组;
注意: 以下方式进行遍历只能遍历自身的可枚举属性,不可枚举属性和定义在原型中的属性和方法是无法获取的
| 方法名 | 功能 |
|---|---|
| Object.key() | 给定对象的自身可枚举属性key组成的数组 |
| Object.values() | 给定对象的自身可枚举属性value组成的数组 |
| Object.entries() | 给定对象的自身可枚举属性key和value组成的二维数组 |
var info = {
name: "wjl",
age: 18,
height: 1.88
}
// console.log(Object.keys(info)) // =>['name', 'age', 'height']
// console.log(Object.values(info)) // => ['wjl', 18, 1.88]
// console.log(Object.entries(info)) //=> [ [ 'name', 'wjl' ], [ 'age', 18 ], [ 'height', ' 1.88' ] ]
// 对对象进行遍历
// 1.普通for循环
var infoKeys = Object.keys(info)
for (var i = 0; i < infoKeys.length; i++) {
var key = infoKeys[i]
var value = info[key]
console.log(`key: ${key}, value: ${value}`)
}
// 2.for..in..: 遍历对象
for (var key in info) {
var value = info[key]
console.log(`key: ${key}, value: ${value}`)
}
// 对象不支持: for..of..: 默认是不能遍历对象
// for (var foo of info) {
// }
4.堆内存和栈内存
程序的运行是需要加载到内存中来执行的,我们可以将内存划分为两个区域:栈内存和堆内存
- 原始类型占据的空间是在栈内存中分配的
- 对象类型占据的空间是在堆内存中分配的
5.值类型和引用类型
原始类型的保存方式:在变量中保存的是值本身, 所以原始类型也被称之为值类型
对象类型的保存方式:在变量中保存的是对象的“引用”(指针,地址),所以对象类型也被称之为引用类型
1.在JS中,每定义一个对象,就会在堆中新建一个内存空间来进行存储
const obj1 = {}
const obj2 = {}
console.log(obj1 === obj2) // => false
2.现象二: 引用的赋值
// 2.现象二: 引用的赋值
var info = {0
name: "wjl",
friend: {
name: "kobe"
}
}
var friend = info.friend
friend.name = "james"
console.log(info.friend.name) // james
3.现象三: 值传递和引用传递的区别
// 3.现象三: 值传递和引用传递的区别
function foo(a) {
a = 200
}
var num = 100
foo(num)
console.log(num) // 100
4.现象四: 引用传递, 但是在函数中创建了一个新对象, 没有对传入对象进行修改
// 4.现象四: 引用传递, 但是在函数中创建了一个新对象, 没有对传入对象进行修改
function foo(a) {
a = {
name: "why"
}
}
var obj = {
name: "obj"
}
foo(obj)
console.log(obj)
5.现象五: 引用传递, 但是对传入的对象进行修改
// 5.现象五: 引用传递, 但是对传入的对象进行修改
function foo(a) {
a.name = "why"
}
var obj = {
name: "obj"
}
foo(obj)
console.log(obj)
6.类和对象的思维方式
在开发中经常需要创建一系列的相似的对象,如果我们一个个都通过字面量方式手动进行创建,必然会十分的麻烦,而且存在大量的重复性代码
// 一系列的学生对象
// 重复代码的复用: for/函数
var stu1 = {
name: "why",
age: 18,
height: 1.88,
running: function() {
console.log("running~")
}
}
var stu2 = {
name: "kobe",
age: 30,
height: 1.98,
running: function() {
console.log("running~")
}
}
var stu3 = {
name: "james",
age: 25,
height: 2.05,
running: function() {
console.log("running~")
}
}
// for循环
// for (var i = 0; i < 3; i++) {
// var stu = {
// name: "why",
// age: 18,
// height: 1.88,
// running: function() {
// }
// }
// }
7 创建对象的方案 – 工厂函数
为了我们可以便于我们批量创建相似对象,我们可以使用工厂函数
我们可以封装一个函数,这个函数用于帮助我们创建一个对象,我们只需要重复调用这个函数即可;
工厂模式其实是一种常见的设计模式;
但工厂函数存在一个比较大的问题,工厂函数内部其实是使用new Obejct()来创建我们对于的实例对象
所以使用工厂函数返回的对象的类型都是Obejct类型
// 工厂函数(工厂生产student对象) -> 一种设计模式
// 通过工厂设计模式, 自己来定义了一个这样的函数
// 工厂函数创建
function createStudent(name, age, height) {
var stu = {}
stu.name = name
stu.age = age
stu.height = height
stu.running = function() {
console.log("running~")
}
return stu
}
// stu1 和 stu2 和 stu3的类型是Object
var stu1 = createStudent("why", 18, 1.88)
var stu2 = createStudent("kobe", 30, 1.98)
var stu3 = createStudent("james", 25, 2.05)
console.log(stu1)
console.log(stu2)
console.log(stu3)
但是我们更希望的时候可以细分不同实例对象的类型,例如学生实例都是Student类型的,水果实例都是Fruit类型的
为此JavaScript为我们提供了一种更为简便的方式去批量创建相似对象,那就是构造函数
8构造函数
我们先理解什么是构造函数?
-
构造函数也称之为
构造器(constructor),通常是我们在创建对象时会调用的函数 -
在其他面向的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法
-
但是JavaScript中的构造函数有点不太一样,构造函数扮演了其他语言中类的角色
也就是在JavaScript中,构造函数其实就是其它编程语言中的类
-
在ES5之前,我们都是
通过function来声明一个构造函数(类)的,之后通过new关键字来对其进行调用 -
在ES6之后,JavaScript可以像别的语言一样,通过class来声明一个类
// JavaScript已经默认提供给了我们可以更加符合JavaScript思维方式(面向对象的思维方式)的一种创建对象的规则
// 在函数中的this一般指向某一个对象
/*
如果一个函数被new操作符调用
1.创建出来一个新的空对象
2.让this指向这个空对象
3.执行函数体的代码块
4.如果没有明确的返回一个非空对象, 那么this指向的对象会自动返回
*/
function coder(name, age, height) {
this.name = name
this.age = age
this.height = height
this.running = function() {
console.log("running~")
}
}
// 在函数调用的前面加 new 关键字(操作符)
// stu1 和 stu2 的类型是 coder
var stu1 = new coder("why", 18, 1.88)
var stu2 = new coder("kobe", 30, 1.98)
console.log(stu1, stu2)
9. 类和对象的关系
现实生活中往往是根据一份描述/一个模板来描述一个个实体对象的
编程语言也是一样, 也必须先有一份描述, 在这份描述中说明将来创建出来的对象有哪些属性(成员变量)和行为(成员方法)
其实就是将多个对象中共有的属性和方法进行抽取(抽象),形成一个对于相似对象的具体描述信息
而这份描述就被称之为类,所描述的对象一般就被称之为实例对象
比如水果fruits是一类事物的统称,苹果、橘子、葡萄等是具体的对象
比如人person是一类事物的统称,而Jim、Lucy、Lily、李雷、韩梅梅是具体的对象
在JavaScript中类的表示形式就是构造函数
- 构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别
- 如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数
如果一个函数被使用new操作符调用了,那么它会执行如下操作:
- 在内存中创建一个新的对象(空对象)
- 这个对象内部的[[prototype]]属性(也就是
__proto__属性)会被赋值为该构造函数的prototype属性 - 构造函数内部的this,会指向创建出来的新对象
- 执行函数的内部代码(函数体代码)
- 如果构造函数没有返回非空对象,则返回创建出来的新对象
// 创建一系列的对象
// 构造函数的名称: 使用大驼峰
function Person(name, age, height) {
this.name = name
this.age = age
this.height = height
this.running = function() {
console.log("running~")
}
}
var p1 = new Person('wjl',18,1.88)
console.log(p1)
// 平时创建普通的对象
// new Object()
var obj1 = {}
var obj2 = new Object()
var obj3 = new Person()
// 普通函数: 使用小驼峰
function sayHello() {
}
补充
globalThis
// 浏览器中存在一个全局对象object -> window
// 作用一: 查找变量时, 最终会找到window头上
// 作用二: 将一些浏览器全局提供给我们的变量/函数/对象, 放在window对象上面
// 作用三(了解): 使用var定义的变量会被默认添加到window上面
console.log(window)
// 使用var定义变量
var message = "Hello World"
function foo() {
// 自己的作用域
// abc()
// alert("Hello World")
console.log(window.console === console)
// 创建一个对象
// var obj = new Object()
console.log(window.Object === Object)
// DOM
console.log(document)
// window.message
console.log(window.message)
}
foo()
在JavaScript的宿主环境(宿主环境又叫运行环境)中,会提供一个全局对象
在浏览器中,这个对象就是window
在node中,这个对象就是global
在web worker中,这个对象就是self
所以ECMA提供了一个特殊的全局变量就在globalThis
该变量会根据JavaScript的不同宿主环境被映射成对应宿主环境下的全局对象
全局对象的作用
-
在查找变量的时候,如果一直找不到,最终会在globalThis上进行查找
-
因为globalThis作为全局对象,可以用来被挂载全局变量,全局函数和全局对象
例如: 在浏览器中console, alert, document等都会被挂载到全局对象window上
-
使用var定义的变量或隐式全局变量默认会被自动挂载到globalThis上 --- ES6开始已经不被推荐
globalThis作为一个对象,在这个对象中有一个特殊的属性叫做globalThis,指向其自身
所以我们可以使用 globalThis.globalThis.glpbalThis.... 进行无限调用
最终的结果都是指向globalThis自身
函数是一个特殊的对象
在JavaScript中,函数是一种特殊的可以执行的对象
所以当我们创建一个函数的时候,对应的函数代码会被存放到堆内存中
在执行的时候,会在栈中开辟对应的函数执行上下文,执行对应的函数
// 定义原始类型的变量
var name = "wjl"
var age = 18
// 定义对象类型的变量
// 地址 - 指针 - 引用
var obj = {} // 堆内存
var foo = function() {} // 堆内存
function bar() {} // 堆内存
console.log(typeof obj) // object
console.log(typeof foo) // function -> object
// var stu = new Student() // stu是一个Student -> Person
// 引申一些别的知识(了解)
var info = {}
info.name = "abc"
function sayHello() {
}
sayHello.age = 18
console.log(sayHello.age)
function Dog() {
}
// 构造函数上(类上面)添加的函数, 称之为类方法
Dog.running = function() {}
Dog.running()