第八章:对象、类与面向对象编程

第八章:对象、类与面向对象编程

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);
    }
}
  • 调用过程:
      1. 在内存中创建新对象
      1. 新对象内部的 [[Prototype]]特性被赋值为构造函数的 prototype属性
      1. 构造函数内部的 this被赋值为这个新对象(指向新对象)
      1. 执行构造函数内部的代码
      1. 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
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-inObject.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');
        }
    }
}
4. 继承内置类型

5. 类的混入

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值