【笔记】JS的对象

前言

JS的对象学习笔记
JS的面向对象学习笔记

对象的分类

  • 内建对象

    • 由ES标准定义的对象,在任何的ES的实现中都可以使用
    • 比如:String、Number、Boolean、Function、Object、Math
  • 宿主对象

    • 由JS的运行时环境所提供的对象
    • 在浏览器中运行的JS代码主要指由浏览器提供的对象,比如:BOM对象、DOM对象
  • 自定义对象

    • 由开发人员自己创建的对象

创建对象

通过字面量创建对象

1
var obj = {};
  • 多个属性和方法之间用,分隔,最后一个属性或方法末尾可以添加,,也可以不添加,
1
2
3
4
var obj = {
"key1": "value",
"key2": function () {},
};
  • 如果键名中不包含特殊符号,可以省略引号
  • 如果值是函数,可以省略function关键字和键定义
1
2
3
4
var obj = {
key1: "value",
key2() {},
};

通过工厂函数创建对象(ES5)

  • 通过工厂设计模式定义函数来创建对象
1
2
3
4
5
6
7
8
9
function fn(key1, key2) {
var obj = new Object();
obj.key1 = key1;
obj.key2 = key2;
obj.method = function () {};
return obj;
}

var obj = fn("value", "value");

通过构造函数创建对象(ES5)

  • 如果一个函数,最终的目的是通过new关键字创建对象,那么这个函数被称为构造函数

    • 构造函数中只需要直接定义属性和方法即可,不需要创建对象和返回对象,实质上和通过工厂函数创建对象原理相同
    • 为了与普通函数区分,通常将构造函数的函数名首字母大写
  • new关键字在创建对象时,自动执行了以下步骤

    1. 在内存中创建一个新的空对象
    2. 对象的隐式原型指向构造函数的显式原型
    3. 对象的this指向这个对象
    4. 执行构造函数内的代码来添加属性和方法,并定义属性初始值
    5. 自动返回这个新对象

通过Object类构造函数创建对象

1
2
3
4
5
6
var obj = new Object();
obj.key1 = "value";
obj.key2 = "value";
obj.method = function () {};

var obj = new Object();

通过自定义构造函数创建对象

1
2
3
4
5
6
7
function Fn(key1, key2) {
this.key1 = key1;
this.key2 = key2;
this.method = function () {};
}

var obj = new Fn("value", "value");

通过类创建对象(ES6)

  • 类名首字母大写
  • 类中必须通过this关键字调用当前类实例的属性或方法
  • new关键字在创建对象时,自动执行了以下步骤
    1. 在内存中创建一个新的空对象
    2. 对象的隐式原型指向构造函数的显式原型
    3. 对象的this指向这个对象
    4. 执行构造方法内的代码来添加属性,并定义属性初始值
    5. 自动返回这个新对象
  • constructor()方法为构造方法
    • 构造方法通过形参为对象初始化属性值
    • 如果不写构造方法,类中会有默认的构造方法
    • JS中的构造方法只能有一个,不能定义多个,因为JS中没有方法的重载
1
2
3
4
5
6
7
8
9
10
11
class Cls {
constructor(key1, key2) {
this.key1 = key1;
this.key2 = key2;
}
method() {
...
}
}

const obj = new Cls("value", "value");

通过Object的静态方法create()创建对象(ES5)

null:定义对象指向的原型对象

1
var obj = Object.create(null);

实例属性和实例方法

实例属性

构造函数中定义实例属性(ES5)

  • 构造函数中通过this关键字定义实例属性
1
2
3
4
function Fn(key1, key2) {
this.key1 = key1;
this.key2 = key2;
}

类中定义实例属性(ES6)

  • 类中通过构造方法,并通过this关键字定义实例属性
1
2
3
4
5
6
class Cls {
constructor(key1, key2) {
this.key1 = key1;
this.key2 = key2;
}
}

实例属性的操作

新增或修改属性
  • 如果属性名不存在,则新增属性并赋值
  • 如果属性名已存在,则修改该属性的值
1
obj.key = "value";
删除属性
1
delete obj.key;
获取属性值
1
var result = obj.key;
判断属性是否存在
1
var exist = "key" in obj;

实例方法

构造函数中定义实例方法(ES5)

  • 在构造函数中定义的函数是实例方法,这种实例方法可以在对象中直接访问,这种实例方法在每次对象创建时都会创建
1
2
3
4
5
6
7
8
function Fn(key1, key2) {
function method() {
...
}
}

var obj = new Fn("value", "value");
obj.method();

类定义实例方法(ES6)

  • 在类中定义的函数是实例方法,这种实例方法可以在对象中直接访问
  • 在类中定义方法时不需要使用function关键字修饰,方法之间不需要添加,分隔
1
2
3
4
5
6
7
8
class Cls {
method() {
...
}
}

var obj = new Fn("value", "value");
obj.method();

静态属性和静态方法

构造函数中定义静态属性和静态方法(ES5)

1
2
3
4
5
6
function Fn() {}

Fn.key1 = "value";
Fn.method = function () {
...
}

类定义静态方法(ES6)

  • 类中静态方法的this指向类本身
1
2
3
4
5
class Cls {
static method() {
...
}
}

对象的属性描述符

为单个属性定义描述符

1
2
3
4
5
6
7
8
var obj = { key: "value" };

Object.defineProperty(obj, "key", {
configurable: false, // 不可被再次修改描述符
enumerable: false, // 不可被枚举
writable: false, // 不可被修改
value: "", // 定义属性的值
});
1
2
3
4
5
6
7
8
9
10
11
12
var obj = { _key: "value" };

Object.defineProperty(obj, "key", {
configurable: false, // 不可被再次修改描述符
enumerable: false, // 不可被枚举
get: function () { // 被读取时触发的回调函数
return obj._key;
},
set: function (value) { // 被修改时触发的回调函数
return obj._key = value;
},
});

为多个属性定义描述符

1
2
3
4
5
6
7
8
9
10
var obj = { key1: "value", key2: "value" };

Object.defineProperties(obj, {
key1: {
configurable: false,
},
key2: {
configurable: false,
}
})

获取指定属性的描述符

1
2
3
var obj = { key: "value" };

var result = Object.getOwnPropertyDescriptor(obj, "key");

获取所有属性的描述符

1
2
3
var obj = { key1: "value", key2: "value" };

var result = Object.getOwnPropertyDescriptors(obj);

不可添加属性

1
2
3
var obj = { key: "value" };

Object.preventExtensions(obj);

密封对象

  • 不可添加和删除属性
    • 实质上是preventExtensions()configurable: false的组合
1
2
3
var obj = { key: "value" };

Object.seal(obj);

冻结对象

  • 不可添加、删除、修改属性
    • 实质上是seal()writable: false的组合
1
2
3
var obj = { key: "value" };

Object.freeze(obj);

继承

  • 既可以继承自定义的类,也可以继承内置类

原型与原型链(ES5)

显式原型与隐式原型

  • 每当构造函数被定义,默认有prototype属性
    • prototype属性指向一个新对象,这个新对象就是原型对象
    • 原型对象中有默认有constructor属性,这个属性默认指向构造函数
    • 构造函数的原型对象是显式原型
    • 显示原型可以通过prototype属性来访问
  • 每当通过构造函数创建了一个对象,默认有[[Prototype]]内部属性
    • [[Prototype]]内部属性指向对应构造函数的显示原型
    • 对象的原型对象是隐式原型
    • JS中函数也是对象,所有构造函数也有隐式原型,其隐式原型指向的是Object的显式原型
    • 隐式原型可以通过Object.getPrototypeOf()Object.setPrototypeOf()来访问,部分浏览器也可以使用__proto__属性来访问,但是并不是所有浏览器都有这个属性
  • 显示原型与隐式原型完全相等,指向的是同一个对象
获取构造函数的显式原型
1
2
3
function Fn() {}

var result = Fn.prototype;
修改构造函数的显式原型
1
2
3
4
5
6
function Fn() {}

var prototype = new Object();
Object.defineProperty(prototype, "constructor", { configurable: true, enumerable: false, writable: true, value: Fn });

Fn.prototype = prototype;
获取对象的隐式原型
1
2
3
4
5
function Fn() {}

var obj = new Fn();

var result = Object.getPrototypeOf(obj);
修改对象的隐式原型
1
2
3
4
5
6
function Fn() {}

var prototype = new Object();
Object.defineProperty(prototype, "constructor", { configurable: true, enumerable: false, writable: true, value: Fn });

Object.setPrototypeOf(prototype, Fn.prototype);

原型链

  • Object是所有类的父类,Object的显示原型是所有函数的显示原型的上级,Object的显示原型没有上级

  • 每当访问对象的属性或方法时

    1. 如果被访问的属性或方法在对象中存在,则直接访问对象的属性或方法
    2. 如果被访问的属性或方法在对象中不存在,则访问对象隐式原型的属性或方法
    3. 如果被访问的属性或方法在对象的隐式原型中也不存在,则访问Object的显示原型
    4. 如果被访问的属性或方法在Object的显示原型中也不存在,则报错
对象通过原型链访问上级属性
1
2
3
4
5
6
function Fn() {}

var obj = new Fn();

Fn.prototype.key = "value";
var result = obj.key;
对象通过原型链访问上级方法
1
2
3
4
5
6
function Fn() {}

var obj = new Fn();

Fn.prototype.method = function () {};
obj.method();

通过原型定义实例方法(ES5)

  • 在构造函数的显式原型中定义的函数是实例方法,这种实例方法可以在对象中直接访问,这种实例方法在每次对象创建时不会重复创建
1
2
3
4
5
6
function Fn() {}

Fn.prototype.method = function () {};

var obj = new Fn();
obj.method();

通过原型链实现继承(ES5)

1
2
3
4
5
function Father() {}
function Son() {}

Son.prototype = Object.create(Father.prototype);
Object.defineProperty(Son.prototype, "constructor", { configurable: true, enumerable: false, writable: true, value: Son });

通过类实现继承(ES6)

  • ES6中通过类实现继承是一种语法糖,本质底层还是通过原型链实现的继承
1
2
3
class Father {}

class Son extends Father {}

super

继承构造方法
  • 子类中通过super()调用父类构造方法,super()方法必须写在子类构造方法的最前面,因为super()方法需要在使用this之前使用
1
2
3
4
5
6
7
8
9
10
11
12
class Father {
constructor(x) {
this.x = x;
}
}

class Son extends Father {
constructor(x, y) {
super(x);
this.y = y;
}
}
继承普通方法
  • 子类通过super关键字调用父类的普通方法
1
2
3
4
5
6
7
8
9
10
11
class Father {
methodFather() {
...
}
}

class Son extends Father {
methodSon() {
super.methodFather();
}
}
继承静态方法
  • 子类通过super关键字调用父类的静态方法
1
2
3
4
5
6
7
8
9
10
11
class Father {
static methodFather() {
...
}
}

class Son extends Father {
methodSon() {
super.methodFather();
}
}

方法的重写

  • 子类重写与父类同名的方法
1
2
3
4
5
6
7
8
9
10
11
class Father {
method() {
...
}
}

class Son extends Father {
method() {
...
}
}

判断属性是否是对象的自身属性

1
2
3
4
5
6
7
function Fn() {
key: "value"
}

var obj = new Fn();

var result = obj.hasOwnProperty("key"); // true

判断属性是否是对象的自身属性或父级属性

  • 通过in关键字,根据属性名,判断属性是否是对象的自身属性或父级属性
1
2
3
4
5
function Fn() {}

var obj = new Fn();

var result = "key" in obj; // false

遍历自身属性和父级属性

1
2
3
4
5
6
7
function Fn() {}

var obj = new Fn();

for (var key in obj) {
...
}

判断构造函数的显示原型是否出现在指定实例对象的原型链上

  • 通过instanceof关键字,根据构造函数,判断构造函数的显示原型是否出现在指定实例对象的原型链上
1
2
3
4
5
6
function Fn() {}

var obj = new Fn();

var result1 = obj instanceof Fn; // true
var result2 = obj instanceof Object; // true

判断对象是否出现在指定实例对象原型链上

  • 通过isPrototypeOf()函数,根据对象,判断对象是否出现在指定实例对象原型链上
1
2
3
4
5
6
function Fn() {}

var obj = new Fn();

var result1 = Fn.prototype.isPrototypeOf(obj); // true
var result2 = Object.prototype.isPrototypeOf(obj); // true

访问器(ES6)

  • 通过getter()方法和setter()方法定义对象的访问器

    • 每当属性值被获取时,会自动调用getter()方法
    • 每当属性值被修改时,会自动调用setter()方法
  • 访问器操作的属性可以未定义

类的访问器方法

1
2
3
4
5
6
7
8
9
10
11
class Cls {
constructor(key) {
this._key = key;
}
get key() {
return this._key;
}
set key(value) {
this._key = value;
}
}

对象的访问器方法

直接在对象中定义访问器

1
2
3
4
5
6
7
8
9
var obj = {
_key: "value",
get key() {
return this._key;
},
set key(value) {
this._key = value;
},
}

通过属性修饰符定义访问器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = {
key: "value",
}

let _key = obj.key;

Object.defineProperty(obj, "key", {
get: function () {
return _key;
},
set: function (value) {
_key = value;
},
})

遍历对象的属性

获取对象的所有属性名

  • 返回对象的所有属性名数组
1
2
3
var obj = { key: "value" };

var keyList = Object.keys(obj);

根据对象的所有属性名遍历对象的所有属性值

for…i

1
2
3
4
5
6
var obj = { key: "value" };

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

for…in

1
2
3
4
for (var key in obj) {
console.log("key:", key);
console.log("value:", obj[key]);
}

包装类型

  • JS为了能为基本类型添加属性和方法,为常见基本类型定义了对应的包装类型,如:StringNumberBooleanSymbolBigint

  • nullundefined没有包装类型

  • 当通过原始类型数据调用属性或方法时,浏览器自动执行了以下步骤

    1. 根据原始类型数据值,创建一个包装类型对象
    2. 调用包装类型的属性或方法,返回一个新的值
    • 为了优化包装类型的创建和销毁,浏览器可能会进行优化,对于原始类型数据调用某些包装类型的方法或函数时,浏览器可能不会创建对象而是直接返回结果
    1. 销毁刚刚创建的包装类型对象

包装类型创建的值与字面量创建的值

  • 通过包装类型创建的值,通过typeof得到的数据类型为对象类型
1
2
var value = new String("");
var result = typeof value; // object
  • 通过字面量创建的值,通过typeof得到的数据类型为基本类型
1
2
var value = "";
var result = typeof value; // string

完成