5.对象

158 阅读7分钟

5-1. 新增的对象字面量语法

  1. 成员速写

如果对象字面量初始化时,成员的名称来自于一个变量,并且和变量的名称相同,则可以进行简写

//速写前
function createUser(loginId, loginPwd, nickName) {
    const sayHello = function () {
        console.log("loginId", this.loginId, "nickname", this.nickName)
    }
    return {
        loginId:loginId,
        loginPwd:loginPwd,
        nickName:nickName,
        sayHello:sayHello,
        id: Math.random()
    }
}
const u = createUser("abc", "123", "aaa");
u.sayHello();

//速写后
function createUser(loginId, loginPwd, nickName) {
    const sayHello = function () {
        console.log("loginId", this.loginId, "nickname", this.nickName)
    }
    return {
        //下面是属性速写
        loginId,
        loginPwd,
        nickName,
        sayHello,
        id: Math.random()
    }
}
const u = createUser("abc", "123", "aaa");
u.sayHello();

此处的page也是完全符合上面的规则,因此page属性可以速写。

export async function getMovies(page,limit){
    const resp = await axios.get('http://study.duyiedu.com/api/movies',{
        params:{
            page,
            size:limit,
        },
    });
    return resp.data;
}

  1. 方法速写

对象字面初始化时,方法可以省略冒号和function关键字

//方法速写前

const user = {
    name: "姬成",
    age: 100,
    sayHello: function(){
        console.log(this.name, this.age)
    }
}

user.sayHello();

//方法速写后

const user = {
    name: "姬成",
    age: 100,
    sayHello(){
        console.log(this.name, this.age)
    }
}

user.sayHello();
  1. 计算属性名

有的时候,初始化对象时,某些属性名可能来自于某个表达式的值,在ES6,可以使用中括号来表示该属性名是通过计算得到的。

对象的属性名可以是一个表达式

const prop1 = "name2";
const prop2 = "age2";
const prop3 = "sayHello2";
const user = {
    [prop1]: "姬成",
    [prop2]: 100,
    [prop3](){
        console.log(this[prop1], this[prop2])
    }
}
user[prop3]();

console.log(user)

5-2. Object的新增API

  1. Object.is

用于判断两个数据是否相等,基本上跟严格相等(===)是一致的,除了以下两点:

  1. NaN和NaN相等
  2. +0和-0不相等
console.log(NaN === NaN); // false
console.log(+0 === -0);  // true

console.log(Object.is(NaN, NaN)) //true
console.log(Object.is(+0, -0)) //false
  1. Object.assign

用于混合对象 用法:

const obj1 = {
    a: 123,
    b: 456,
    c: "abc"
}

const obj2 = {
    a: 789,
    d: "kkk"
}

/*
{
    a: 789,
    b: 456,
    c: "abc",
    d: "kkk"
}
*/

//将obj2的数据,覆盖到obj1,并且会对obj1产生改动,然后返回obj1
// const obj = Object.assign(obj1, obj2);


const obj = Object.assign({}, obj1, obj2);

console.log(obj)

console.log(obj===obj1)

console.log(obj1)

console.log(obj2)
  1. Object.getOwnPropertyNames 的枚举顺序

Object.getOwnPropertyNames方法之前就存在,只不过,官方没有明确要求,对属性的顺序如何排序,如何排序,完全由浏览器厂商决定。

ES6规定了该方法返回的数组的排序方式如下:

  • 先排数字,并按照升序排序
  • 再排其他,按照书写顺序排序
const obj = {
    d: 1,
    b: 2,
    a: 3,
    0: 6,
    5: 2,
    4: 1
}
const props = Object.getOwnPropertyNames(obj)
console.log(props)

  1. Object.setPrototypeOf

该函数用于设置某个对象的隐式原型

比如: Object.setPrototypeOf(obj1, obj2), 相当于: obj1.__proto__ = obj2

const obj1 = {
    a: 1
}

const obj2 = {
    b: 2
}

// obj1.__proto__ = obj2

Object.setPrototypeOf(obj1, obj2)

console.log(obj1)

5-3.面向对象简介

面向对象:一种编程思想,跟具体的语言

对比面向过程:

  • 面向过程:思考的切入点是功能的步骤
  • 面向对象:思考的切入点是对象的划分

5-4. 类:构造函数的语法糖

传统的构造函数的问题

  1. 属性和原型方法定义分离,降低了可读性
  2. 原型成员可以被枚举
  3. 默认情况下,构造函数仍然可以被当作普通函数使用

//旧的写法

//面向对象中,将 下面对一个对象的所有成员的定义,统称为类

//构造函数  构造器
function Animal(type, name, age, sex) {
    this.type = type;
    this.name = name;
    this.age = age;
    this.sex = sex;
}

//定义实例方法(原型方法)
Animal.prototype.print = function () {
    console.log(`【种类】:${this.type}`);
    console.log(`【名字】:${this.name}`);
    console.log(`【年龄】:${this.age}`);
    console.log(`【性别】:${this.sex}`);
}

const a = new Animal("狗", "旺财", 3, "男");
a.print();

for (const prop in a) {
    console.log(prop)
}

//新的写法

class Animal {
    constructor(type, name, age, sex) {
        this.type = type;
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    print() {
        console.log(`【种类】:${this.type}`);
        console.log(`【名字】:${this.name}`);
        console.log(`【年龄】:${this.age}`);
        console.log(`【性别】:${this.sex}`);
    }
}

const a = new Animal("狗", "旺财", 3, "男");
a.print();

for (const prop in a) {
    console.log(prop)
}

类的特点

  1. 类声明不会被提升,与 let 和 const 一样,存在暂时性死区
  2. 类中的所有代码均在严格模式下执行
  3. 类的所有方法都是不可枚举的
  4. 类的所有方法都无法被当作构造函数使用
  5. 类的构造器必须使用 new 来调用

5-5. 类的其他书写方式

  1. 可计算的成员名 //类的成员的名称可以用表达式
const printName = "print";

class Animal {
    constructor(type, name, age, sex) {
        this.type = type;
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    [printName]() {
        console.log(`【种类】:${this.type}`);
        console.log(`【名字】:${this.name}`);
        console.log(`【年龄】:${this.age}`);
        console.log(`【性别】:${this.sex}`);
    }
}

const a = new Animal("狗", "旺财", 3, "男");
a[printName]();
  1. getter和setter

Object.defineProperty 可定义某个对象成员属性的读取和设置

使用getter和setter控制的属性,不在原型上

class Animal {
    constructor(type, name, age, sex) {
        this.type = type;
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    //创建一个age属性,并给它加上getter,读取该属性时,会运行该函数
    get age() {
        return this._age + "岁";
    }

    //创建一个age属性,并给它加上setter,给该属性赋值时,会运行该函数
    set age(age) {
        if (typeof age !== "number") {
            throw new TypeError("age property must be a number");
        }
        if (age < 0) {
            age = 0;
        }
        else if (age > 1000) {
            age = 1000;
        }
        this._age = age;
    }
}

var a = new Animal("狗", "旺财", 3, "男");

疑问:_age 成员属性是什么时间创建的? 是在 this._age = age;的时候生成的,js 语言中使用 对象.属性 ,如果该属性不存在,就会创建一个这样的属性。

  1. 静态成员 静态方法

构造函数本身的成员

使用static关键字定义的成员即静态成员 使用static关键字定义的方法即静态方法 静态成员是类本身去访问 静态方法也是类去调用

class Chess {
    constructor(name) {
        this.name = name;
    }

    static width = 50;

    static height = 50;

    static method() {

    }
}

console.log(Chess.width)
console.log(Chess.height)

Chess.method();


  1. 字段初始化器(ES7)

当属性一开始有初始值的情况,ES6 是下面这种写法,而到了ES7有了新的写法。如果是要从构造函数初始化获取的还是要按照下面老的写法去写。

老的写法

class Test {
    constructor() {
        this.a = 1;
        this.b = 2;
        this.c = 3;
     }
}

ES7是下面新的写法

class Test {
    a = 1;
    b = 2;
    c = 3;
}

当然下面的这种混着写的方法也是没有问题的,注意的是a仍然是静态变量

class Test {
  static a = 1;
  b = 2;
  c = 3;
 constructor(){
  this.d = this.b + this.c;
 }
}

注意:

1). 使用static的字段初始化器,添加的是静态成员

2). 没有使用static的字段初始化器,添加的成员位于对象上

3). 箭头函数在字段初始化器位置上,指向当前对象

下面的例子解释了下第三条

class Test {
    constructor() {
        this.a = 123;
        this.print = () => {
            console.log(this);
        };
    }
    
    //下面的这种写法类似于上面在constructor 中的一样
       print = () => {
          console.log(this.a)
       }
       
       //上面的写法可以防止出现 var p= new Test().print;  p(); 这种调用会导致this出问题。
       print1 (){
        console.log(this);
        }
}

const t1 = new Test();
const t2 = new Test();
console.log(t1.print === t2.print)

  1. 类表达式
const A = class { //匿名类,类表达式
    a = 1;
    b = 2;
}

const a = new A();
console.log(a)

上面的写法当然也可以给匿名类起一个名字,不过没啥意义。

const A = class B{ //匿名类,类表达式
    a = 1;
    b = 2;
}
  1. [扩展]装饰器(ES7)(Decorator)(暂时不去了解)

横切关注点

装饰器的本质是一个函数

5-6. 类的继承

如果两个类A和B,如果可以描述为:B 是 A,则,A和B形成继承关系

如果B是A,则:

  1. B继承自A
  2. A派生B
  3. B是A的子类
  4. A是B的父类

如果A是B的父类,则B会自动拥有A中的所有实例成员。

新的关键字:

  • extends:继承,用于类的定义
  • super
    • 直接当作函数调用,表示父类构造函数
    • 如果当作对象使用,则表示父类的原型

注意:ES6要求,如果定义了constructor,并且该类是子类,则必须在constructor的第一行手动调用父类的构造函数

如果子类不写constructor,则会有默认的构造器,该构造器需要的参数和父类一致,并且自动调用父类构造器

class Animal {
    constructor(type, name, age, sex) {
        if (new.target === Animal) {
            throw new TypeError("你不能直接创建Animal的对象,应该通过子类创建")
        }
        this.type = type;
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    print() {
        console.log(`【种类】:${this.type}`);
        console.log(`【名字】:${this.name}`);
        console.log(`【年龄】:${this.age}`);
        console.log(`【性别】:${this.sex}`);
    }

    jiao() {
        throw new Error("动物怎么叫的?");
    }
}

class Dog extends Animal {
    constructor(name, age, sex) {
        super("犬类", name, age, sex);
        // 子类特有的属性
        this.loves = "吃骨头";
    }

    print() {
        //调用父类的print
        super.print();
        //自己特有的代码
        console.log(`【爱好】:${this.loves}`);
    }


    //同名方法,会覆盖父类
    jiao() {
        console.log("旺旺!");
    }
}
const a = new Dog("旺财", 3, "公")
a.print();

【冷知识】

  • 用JS制作抽象类
    • 抽象类:一般是父类,不能通过该类创建对象
  • 正常情况下,this的指向,this始终指向具体的类的对象