第八章:对象、类与面向对象编程
8.1 理解对象
8.1.1 属性的类型
- 通过两个中括号访问内部特性
1. 数据属性
- 数据属性包含一个保存数据值的位置
- 有 4 个特性描述它们的行为
[[Configurable]]
:表示属性是否可以通过delete
删除并重新定义,是否可以修改其特性,是否可以把它改为访问器属性。默认所有直接定义在对象上的属性均为 true[[Enumerable]]
:表示属性是否可以通过for-in
循环返回。默认所有直接定义在对象上的属性均为 true[[Writable]]]
:表示属性的值是否可被修改。默认所有直接定义在对象上的属性均为 true[[Value]]]
:包含属性实际的值。默认为 undefined
- 修改默认特性必须使用
Object.defineProperty()
,调用该方法时未指定特性默认为 false
let person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "jiy"
});
console.log(person.name); // jiy
person.name = "lx";
console.log(person.name); // jiy
2. 访问器属性
- 访问器属性不包含数据值,而是包含
getter函数
和setter函数
,这两个方法不是必须的 - 有 4 个特性描述它们的行为
[[Configurable]]
:表示属性是否可以通过delete
删除并重新定义,是否可以修改其特性,是否可以把它改为访问器属性。默认所有直接定义在对象上的属性均为 true[[Enumerable]]
:表示属性是否可以通过for-in
循环返回。默认所有直接定义在对象上的属性均为 true[[Get]]
:获取函数,默认为 undefined[[Set]]
:设置函数,默认为 undefined
- 访问器属性不能直接定义,必须使用
Object.defineProperty()
let book = {
year_: 2017,
edition: 1
}
Object.defineProperty(book, "year", {
get() {
return this.year_;
},
set(newValue) {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
});
book.year = 2018;
console.log(book.edition); // 2
8.1.2 定义多个属性
let book = {};
Object.defineProperties(book, {
year_: {
value: 2017
},
edition: {
value: 1
},
year: {
get() {
return this.year_;
},
set(newValue) {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
}
});
console.log(book.edition); // 1
book.year = 2019;
console.log(book.edition); // 1
// 定义属性时未指定内部特性,默认为 false
8.1.3 读取属性的特性
let descriptor = Object.getOwnPropertyDescriptor(book, "year_");
console.log(descriptor.value); // 2017
console.log(descriptor.configurable); // false
- ES 2017 新增
Object.getOwnPropertyDescriptors()
8.1.4 合并对象
-
Object.assign()
- 浅复制
- 可枚举(
Object.propertyIsEnumerable()
)和自有(Object.hasOwnProperty()
)属性会被复制 - 以字符串和符号为键的属性会被复制
- 从访问器属性获取的值(比如
获取函数
)会作为静态值被赋值 - 出错时会保留已完成的修改
-
另外
console.log(Object.assign([1, 2, 3], [4, 5, 6])); // [4, 5, 6]
8.1.5 对象标识及相等判定
- ES6 新增
Object.is()
console.log(Object.is(true, 1)); // false
console.log(Object.is("2", 2)); // false
console.log(Object.is({}, {})); // false
console.log({} === {}); // false
console.log(Object.is(+0, -0)); // false
console.log(+0 === -0); // true
console.log(Object.is(+0, 0)); // true
console.log(+0 === 0); // true
console.log(Object.is(-0, 0)); // false
console.log(-0 === 0); // true
console.log(NaN === NaN); // false
console.log(isNaN(NaN)); // true
console.log(isNaN(undefined)); // true
console.log(Object.is(undefined, NaN)); // false
console.log(Object.is(NaN, NaN)); // true
8.1.6 增强的对象语法
1. 属性值简写
2. 可计算属性
const nameKey = "nam";
let person = {
[nameKey+'e']: "jiy"
}
console.log(person); // { name: 'jiy' }
3. 简写方法名
8.1.7 对象解构
nulll
和 ```undefined`` 不能被解构
let person = {
name: "jiy",
money: "999"
};
let { name: personName, money: personMoney } = person;
console.log(personName); // jiy
console.log(personMoney); // 999
// 特殊情况
let pname,pmoney;
let person = {
name: "jiy",
money: "999"
};
({ name: pName, money: pMoney } = person); // 必须用括号包裹
1. 嵌套解构
2. 部分解构
3. 参数上下文匹配
function foo(obj, { name, age }, obj2) {
console.log(name, age);
}
8.2 创建对象
8.2.1 概述
8.2.2 工厂模式
function createPerson(name, age, job) {
let o = new Object();
o.name = name;
o.job = job;
o.age = age;
o.sayName = function () {
console.log(this.name);
};
return o;
}
8.2.3 构造函数模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
}
}
- 调用过程:
-
- 在内存中创建新对象
-
- 新对象内部的
[[Prototype]]
特性被赋值为构造函数的prototype
属性
- 新对象内部的
-
- 构造函数内部的
this
被赋值为这个新对象(指向新对象)
- 构造函数内部的
-
- 执行构造函数内部的代码
-
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
-
1. 构造函数也是函数
- 直接调用、未使用 new,
this
会指向 Global(window) 对象
2. 构造函数的问题
- 定义的方法在每个实例上都创建一遍
console.log(p1.sayName == p2.sayName); // false
8.2.4 原型模式
- 每个函数都会创建一个
prototype
属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法- 实际上,这个对象就是通过调用构造函数创建的对象的原型
function Person(name, age, job) { }
Person.prototype.name = "jiy";
Person.prototype.age = 21;
Person.prototype.job = 'student';
Person.prototype.sayName = function () {
console.log(this.name);
};
1. 理解原型
- 只要创建一个函数,就会按特定规则为这个函数创建一个
prototype
属性,指向原型对象 - 默认情况下,所有原型对象自动获得
constructor
属性,指回与之关联的构造函数- 自定义构造函数时,原型对象默认只获得
constructor
属性,其他所有方法都继承自 Object
- 自定义构造函数时,原型对象默认只获得
- 每创建一个新实例,其内部的
[[Prototype]]
指针都被赋值为构造函数的原型对象- 部分浏览器可以通过
_proto_
访问
- 部分浏览器可以通过
- 总结:
- 构造函数:
prototype
-> 原型对象 - 原型对象:
constructor
-> 构造函数 - 实例:
[[Prototype]]
(_proto_
) -> 原型对象 - Object 原型的原型是 null
- 构造函数:
- 方法
isPrototypeOf()
Object.getPrototypeOf()
Object.setPrototypeOf()
const p1 = new Person();
// Person.prototype == new Person()._proto_
console.log(Person.prototype.isPrototypeOf(p1)); // true
console.log(Person.prototype == Object.getPrototypeOf(p1));
const biped = {
numLegs: 2
};
Object.setPrototypeOf(p1,biped);
console.log(p1.numLegs); // 2
// 避免该方法造成的性能下降
let person = Object.create(biped);
2. 原型层级
hasOwnProperty()
可以确定属性在实例上还是原型对象上
3. 原型和 in 操作符
console.log("name" in p1); // true
for-in
:可通过对象访问且可被枚举的属性,包括实例属性和原型属性Object.keys()
:获取可枚举的实例属性Object.getOwnPropertyNames()
:获取全部实例属性Object.getOwnPropertySymbols()
4. 属性枚举顺序
for-in
和Object.keys()
不确定,取决于引擎Object.getOwnPropertyNames / Symbols()
和Object.assign()
顺序确定- 先以升序枚举数值键,再以插入顺序枚举字符串和符号键
8.2.5 对象迭代
Object.values()
:浅复制,忽略符号属性Object.entries()
:浅复制,忽略符号属性
1. 其他原型语法
function Person(name, age, job) { }
Person.prototype = {
name: "jiy",
age: 21,
job: "student",
sayName() {
console.log(this.name);
}
}
// constructor 不可枚举
Object.defineProperty(Peroson.prototype, "constructor", {
enumerable: false,
value: Person
});
2. 原型的动态性
- 实例只有指向原型的指针,没有指向构造函数的指针
- 重写整个原型不会改变实例的引用
3. 原生对象原型
4. 原型的问题
- 多个实例会共享一个引用值属性
8.3 继承
8.3.1 原型链
- p239(第四版)
1. 默认原型
- 默认情况下,所有引用类型都继承自 Object。任何函数的默认原型都是一个 Object 的实例
2. 原型和继承关系
instanceof()
isPrototypeOf()
3. 关于方法
4. 原型链的问题
- 共享引用值属性
- 子类型在实例化时不能给父类型的构造函数传参
8.3.2 盗用构造函数(对象伪装、经典继承)
- 必须在构造函数中定义方法,方法不能重用
- 无法访问父类原型上定义的方法
function SuperType() {
this.name = 'super';
}
function SubType() {
SuperType.call(this);
}
8.3.3 组合继承
function SuperType() {
this.name = 'super';
}
SuperType.prototype.sayName = function () {
console.log(this.name);
}
function SubType() {
// 继承属性
SuperType.call(this);
}
// 继承方法
SubType.prototype = new SuperType();
8.3.4 原型式继承
Object.create()
:会共享引用值属性
8.3.5 寄生式继承
function createAnother(original) {
const clone = Object.create(original);
console.sayHi = function () {
console.log("hi");
};
return clone;
}
8.3.6 寄生式组合继承
function test(subType, superType) {
const prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
8.4 类
8.4.1 类定义
- 与函数不同,不能提升
- 类的构成:
- 构造函数方法
- 实例方法
- 获取函数
- 设置函数
- 静态类方法
8.4.2 类构造函数
1. 实例化
- 过程:看这里
2. 把类当成特殊函数
- 类中定义的
constructor方法
不会被当成构造函数
8.4.3 实例、原型和类成员
1. 实例成员
2. 原型方法与访问器
3. 静态类方法
4. 非函数原型和类方法
5. 迭代器与生成器方法
8.4.4 继承
1. 继承基础
- 也可以继承构造函数
2. 构造函数、HoneObject()
和 super()
super()
只能在静态函数或构造函数中使用- 在构造函数中使用时,前面不能使用
this
- 派生类显示定义了构造函数,必须调用
supe()
或者 返回一个对象
- 在构造函数中使用时,前面不能使用
3. 抽象基类
class Test {
constructor() {
// Test 不能直接实例化
if (new.target === Test) {
throw ('error');
}
// 派生类必须实现 foo 方法
if (!this.foo) {
throw ('error');
}
}
}