文章目录
1. 什么是对象
- 万物皆是对象,对象有属性和方法,拿人来举例:人的姓名、年龄、性别等这些都是属性,属性可以通过点属性的形式去获取,人可以吃东西、走路、唱歌等这些都是方法,方法可以通过点方法括号的形式去调用
- 举例来说
- 操作浏览器要使用window对象
- 操作网页要使用document对象
- 操作控制台要使用console对象
- 在
JS
中,对象是一组无序的相关属性和方法的集合- 对象是由属性和方法组成的
- 属性:事物的特征,在对象中用属性来表示(常用名词)
- 方法:事物的行为,在对象中用方法来表示(常用动词)
2. 创建对象的三种方式
-
字面量创建
var obj = {}
-
内置构造函数创建
// 一般情况new关键字不能省略 var obj = new Object() // 1.如果括号中传入的是null或undefined,将会创建并返回一个空对象 // 2.如果传进去的是一个基本类型的值,则会对这个基本数据类型进行包装 // 3.如果是引用数据类型,仍然会返回这个类型的值
-
工厂函数
// 第一种 // 采用内置构造函数封装 function getPreson(name, age) { var temp = new Object() temp.name = name temp.age = age return temp } getPreson("小红", 18) getPreson("小白", 19) // 第二种 // 自定义构造函数 function Person(name, age) { this.name = name this.age = age } var person1 = new Person("小红", 18) var person2 = new Person("小白", 19)
3. this
window上的属性和方法默认不用window点去寻找,可以直接使用
- 全局作用域中
this
默认指向window
- 在函数中的谁调用该函数,
this
就指向谁(谁点他就指向谁) - 在执行上下文中,
this
指向当前上下文 - 定时器和 Function构造处来的函数中的 this 默认指向 window
4. 对象的增删改查
// !!访问对象中没有的属性会得到undefined
var obj = {}
// 增
obj.name = "小红"
obj.age = 18
// 删,删除成功后会返回布尔值(即使没有删除到也会返回true)
delete obj.name
// 改
obj.age = 20
// 查,也可以查询原型上的属性
// 注意:如果对象上没有该属性,会返回undefined,并不会报错
obj.age
// 其他查询方式,都是返回布尔值
// 1.in:查询自己或原型上是否有该属性
"age" in obj
// 2.hasOwnProperty():查询自己是否有该属性
obj.hasOwnProperty("age")
// 3.propertyIsEnumerable():查询自己是否有该属性,并且要可枚举的属性
obj.propertyIsEnumerable("age")
5. new做了什么事情
- 在内存中创建了一个新的空对象
- 让这个空对象的
__proto__
指向构造函数的原型对象 - 让构造函数的
this
指向这个新的对象 - 执行构造函数里面的代码,给这个新对象添加属性和方法
- 如果构造函数中没有return,那么就返回这个新对象,如果有return,并且返回的为引用数据类型,那么就返回引用数据类型
6. 枚举对象
// 键值对:键(key)、值(value)
// 数组关联法:obj["key"] = value
// 数组也可以for……in遍历
// for……in是遍历的键或索引(返回的字符串)
for (let i in obj) { // 也会遍历继承的属性
// 遍历后的键值
// i:获取对象obj的键
// obj[i]:获取对象obj的值
}
// 只枚举自身可枚举的属性,返回对应的key或value存放数组中
Object.keys(obj)
Object.values(obj)
7. 对象常用API
静态方法 | 作用 | 返回值 |
---|---|---|
1. create(obj) | 使返回值的值,继承括号中的值 | 要被继承的对象 |
2. assign(A,B…) | 将一个或多个可枚举属性分配给A,如果B和A有相同属性,A的会被覆盖。A会改变,B和B之后的不会 | A对象 |
3. defineProperty(obj, prop, descriptor) | 给一个对象添加属性和约束obj:对象,prop:属性,descriptor:约束 | 原对象 |
4. entries(obj) | 把自身可枚举的key和value存放到数组中 | 数组 |
5. freeze(obj) | 冻结一个对象,使该对象不能增、删、改,只能查询 | 该对象 |
6. keys(obj) | 把自身可枚举的key存放到数组中 | 数组 |
7. values(obj) | 把自身可枚举的value存放到数组中 | 数组 |
8. 面向对象的三大特征
- 封装:对相同对象的属性和方法进行封装,成为一个类
- 继承:子类可以继承父类的属性和方法
- 多态:相当于子类对父类方法的重写,可以使用父类的,也可以自己重写
9. 原型与继承
9.1 为什么要有原型
因为如果把方法全部写在构造函数中,那么每次 new 的时候都会新创建一个方法,特别的浪费空间,可以通过原型把方法添加到原型上,这样每次 new 的时候这些实例都会继承原型上的方法,就可以公用一个方法了
9.2 原型对象之间的关系
function Person(name, age) {
this.name = name
this.age = age
this.say = function () {
console.log("你好!")
}
}
let p1 = new Person("小红", 18)
// 实例也叫实例对象
// 实例通过隐式原型查找原型对象
p1.__proto__ => 原型对象
// 构造函数通过显式原型查找原型对象
Person.prototype => 原型对象
// 实例通过构造器指向构造函数也就是Person
p1.constructor => 构造函数
// 这一点要注意构造器是原型上的属性,实例可以使用原型上面的属性
// 所以说:
Person.prototype.constructor => 构造函数
// 或者
p1.__proto__.constructor => 构造函数
9.3 继承
9.3.1 基本概念
概念 | 其他语言 | JavaScript |
---|---|---|
类 class | 模板 | 构造函数,类名就是函数名 |
子类 subclass | 派生的模板 | 原型设置为指定对象的构造函数 |
实例 instance | 某个类的对象 | 构造函数new的对象 |
实例成员(方法,属性) | 对象的方法和属性 | 对象的方法和属性 |
静态成员(方法,属性) | 类上的方法和属性 | 函数上的方法和属性 |
9.3.2 属性访问原则
- 对象在调用方法或访问属性的时候,首先在当前对象中查询,如果有该成员直接使用并停止查找
- 如果没有该成员,就会在其原型对象中查找,如果有该成员则使用
- 如果还没有就会一直向上层查找,最后会查到Object.prototype上,如果没有就返回undefined
9.3.3 实现继承的几种方法
-
原型式继承
// 1.继承原型对象的实例 // 这种继承直接在原型上添加一个对象,但是会改变构造函数 // 需要在对象中把构造器,指向需要new的构造函数 function Person() { } Person.prototype = { constructor: Person, name: "小红", age: 18 } let p1 = new Person() console.log(p1); // 名为Person的实例 console.log(p1.constructor); // Person构造函数 //2.继承实例 function Person(name, age) { this.name = name this.age = age } function Student(gender) { this.gender = gender } // 这里Student的constructor也是Person Student.prototype = new Person("小红", 18) let stud = new Student("男") console.log(stud);
-
混入式继承
let obj1 = { name: "小红", age: 18 } let obj2 = { gender: "male" } // 直接通过实例合并 function __mixin__(obj1, obj2) { for (let i in obj2) { obj1[i] = obj2[i] } return obj1 } let obj3 = __mixin__(obj1, obj2) console.log(obj3);
-
混合式继承
// 这种继承和混入一样,不算真正意义上的继承,只是合并实例 // 混合 = 混入 + 原型 function Person(name, age) { this.name = name this.age = age } function Student(gender) { this.gender = gender } // 在Student原型上添加一个混入的方法 // 直接通过实例调用即可 Student.prototype.__mixin__ = function (obj) { for (let i in obj) { this[i] = obj[i] } return this } let p1 = new Person("小红", 18) let stud = new Student("男") console.log(stud.__mixin__(p1));
-
Object.create():这种也是原型式继承
let person = { name: "Xiaong", age: 18 } // 使用自带的方法实现 // p1.__proto__ === person // person.__proto__ === Object.prototype let p1 = Object.create(person) console.log(p1); // 简单实现 function Create(obj) { let o = {} o.__proto__ = obj return o } let p2 = Create(person) console.log(p2);
-
构造函数继承
// 这种方式只是可以用父构造函数的属性和方法,并没有继承父类的原型 // 父构造函数 function Parent(name) { this.name = name } Parent.prototype.run = function () { return "我是父类方法" } // 子构造函数 function Child(name, age) { Parent.call(this, name) this.age = age } Child.prototype.play = function () { return "我是子类方法" } let parent = new Parent("我是爹") console.log(parent); // Parent {name: '我是爹'} console.log(parent.run()); // 我是父类方法 let child = new Child("我是儿", 18) console.log(child); // Child {name: '我是儿', age: 18} console.log(child.play()) // 我是子类方法 console.log(child.run()); // child.run is not a function
-
寄生组合式继承
// 构造函数继承的升级版,可以有效的继承父类的方法 // 使子类的原型,继承父类的原型 function clone(Parent, Child) { Child.prototype = Object.create(Parent.prototype) // 还要把构造器指向自己 Child.prototype.constructor = Child } // 父构造函数 function Parent(name) { this.name = name } Parent.prototype.run = function () { return "我是父类方法" } // 子构造函数 function Child(name, age) { Parent.call(this, name) this.age = age } // 一定要先继承父类原型,在向自己的原型上添加方法,不然会被覆盖 clone(Parent, Child) Child.prototype.play = function () { return "我是子类方法" } let parent = new Parent("我是爹") console.log(parent); // Parent {name: '我是爹'} console.log(parent.run()); // 我是父类方法 let child = new Child("我是儿", 18) console.log(child); // Child {name: '我是儿', age: 18} console.log(child.play()) // 我是子类方法 console.log(child.run()); // 我是父类方法
-
extends
9.4 原型链
因为原型对象也是一个对象,只要是对象就会存在自己的原型对象,所以原型对象也有自己的原型对象,通过这样一级一级查找出的原型,就叫做对象的原型链
完成原型链如下图:
这张图看起来很复杂,但是只需要记住几句话就会好理解一些,
- Function 内置构造函数是所有函数的爹,包括构造函数和其他内置构造函数
- 除了 Function 实例化之后为函数,其他的实例都是对象
- Object 的原型对象是所有实例对象的爹
- Object 原型对象的隐式原型指向 null,代表它就是最顶层
- 实例的隐式原型
__proto__
和 构造函数的显示原型 prototype,都指向构造函数的原型对象 - 所有对象的
__proto__
和所有函数的 prototype 都为对象,所有函数的__proto__
都为函数 - 上面所有函数中不包括 Function,它的
__proto__
和 prototype 都为函数,并且相等
10. class
10.1 class基本语法
// constructor必不可少,类名也不能与其他变量名、函数名、类名重复
class 类名 {
// 构造函数
constructor(){
}
// 原型方法
sayHi(){
}
}
10.2 class与构造函数区别
// class是ES6新增的,为构造函数的语法糖
// 构造函数
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () {
console.log("说");
}
// class
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
// 相当于直接把方法添加到原型上
sayHi() {
console.log("说");
}
}
10.3 静态成员,私有成员
// 静态成员,static
// 静态成员是类(Person)的成员
class Person {
static str = "我是静态属性"
constructor(name, age) {
this.name = name
this.age = age
}
sayHi() {
console.log("我是实例方法");
}
static hi() {
console.log("我是静态方法");
}
}
// 私有成员,#
class Person {
#str = "我是私有属性"
constructor(name, age) {
this.name = name
this.age = age
}
#sayHi() {
console.log("我是私有方法");
}
}
10.4 访问器,修改器
// set、get,私有属性,可以通过这种方式添加约束
// set必须要有参数,get必须要有返回值
// 比如银行存钱取钱只能为一百的倍数
class Person {
#num = 100
constructor() {
}
set num(value) {
if (value % 100 === 0) {
console.log("修改成功");
this.#num = value
} else {
console.log("很抱歉,金额必须为100的倍数");
}
}
get num() {
return `您的余额为: ${this.#num}`
}
}
let p1 = new Person()
console.log(p1.num); // 您的余额为: 100
console.log(p1.num = 150); // 很抱歉,金额必须为100的倍数
console.log(p1.num = 200); // 修改成功
console.log(p1.num); // 您的余额为: 200
10.5 extends,super
class // 父类:动物类
class Animal {
constructor(name, age) {
this.name = name
this.age = age
}
call() {
console.log(`${this.name}叫`)
}
}
let animal = new Animal("动物", "未知")
console.log(animal); // Animal {name: '动物', age: '未知'}
animal.call() // 动物叫
// 子类:猫类,使用extends继承,super调用父类构造器
// super中也可以不写参数,相当于调用父类构造函数不传参,属性会为undefined
class Cat extends Animal {
constructor(name, age, weight) {
super(name, age)
this.weight = weight
}
leap() {
console.log("小猫会跳");
}
}
let cat = new Cat("小猫", 6, 2n)
console.log(cat); // Cat {name: '小猫', age: 6, weight: 2n}
cat.call() // 小猫叫
cat.leap() // 小猫会跳
// 子类:狗类,使用extends继承,不写constructor和super
// 但是默认会使用super调用父类的构造器
// 这种写法属性是固定的,不能自己添加想要的属性,不太好
class Dog extends Animal {
run() {
console.log("小狗会跑")
}
}
let dog = new Dog("小狗", 5)
console.log(dog); // Dog {name: '小狗', age: 5}
dog.call() // 小狗叫
dog.run() // 小狗会跑
10.6 特殊用法
class Demo {
constructor(){
this.arr = []
}
list = []
add() { }
}
let demo = new Demo()
// 此时,arr 和 list 都在 demo 的属性上,add 在 demo 原型上
// 相当于如果不是 constructor 里的函数会添加到原型上,而属性会添加到当前属性上