第四章 对象

84 阅读5分钟

新增的对象字面量语法

成员速写

对象的成员如果它的键名称与值变量的名称相同,则可以简写:

var name = "张三";

var obj = {
    name
};

方法速写

当对象的成员是一个方法时,可以省略冒号和function关键字

var obj = {
    sayHello(){
        console.log("hello");
    }
}

注意:这种简写的函数并不是箭头函数

计算属性名

对象成员的属性名如果来自一个表达式,则需要使用计算属性名

计算属性名需要使用[]进行包括

var obj = {
    [1+2*3]: "abc"		// 7: "abc"
};

Object的新增API

  1. Object.is()

    用于判断两个数据是否相等

    它和"==="的比较基本相同,除了以下两点:

    • Object.is(NaN, NaN)得到true
    • Object.is(+0, -0)得到false
  2. Object.assign(obj1, obj2)

    将obj2自身的所有可枚举的属性混合到obj1之中,然后返回obj1

    const obj1 = {}
    
    const obj2 = {
        a: 1
    };
    
    Object.defineProperty(obj2, "b", {
        value: 2,
        enumerable: true
    });
    
    Object.defineProperty(obj2, "c", {
        value: 3,
        enumerable: false
    });
    
    Object.prototype.d = 4;
    
    const obj3 = Object.assign(obj1, obj2);
    
    console.log(obj3);              // { a: 1, b: 2 }
    
    console.log(obj1 === obj3);		// true
    
  3. Object.freeze(obj)

    浅层冻结对象或数组

    被冻结的对象或数组的直接子属性是不能被重新赋值的,也无法增加或删除直接子属性

    const obj = {
        a: 1,
        b: {
            c: 1
        }
    };
    
    Object.freeze(obj);
    
    obj.a++;			// 无效
    obj.d = 1;			// 无效
    delete obj.a;		// 无效
    obj.b.c++;			// 成功
    delete obj.a.c;		// 成功
    
  4. Object.isFrozen(obj)

    判断对象或数组是否被冻结

类语法

传统的构造函数存在的问题:

  1. 属性和原型方法定义分离,降低了代码可读性
  2. 原型成员可以被枚举
  3. 构造函数仍可以作为普通函数使用

构造函数写法:

function User(firstName, lastName){
    this.firstName = firstName;
    this.lastName = lastName;
    this.fullName = `${firstName} ${lastName}`;
}

User.isUser = function(u){
    return !!u && !!u.fullName
}

User.prototype.sayHello = function(){
    console.log(`Hello, my name is ${this.fullName}`);
}

类语法:

// 类写法
class User{
    constructor(firstName, lastName){
        this.firstName = firstName;
        this.lastName = lastName;
        this.fullName = `${firstName} ${lastName}`;
    }

    static isUser(u){
        return !!u && !!u.fullName
    }

    sayHello(){
        console.log(`Hello, my name is ${this.fullName}`);
    }
}

这种使用class关键字声明的构造函数,也称为类

类和传统构造函数的差异:

  1. 类的原型方法将自动被设置为不可枚举
  2. 类的构造器只能通过new关键字进行调用

细节

  1. 类的声明不会被提升,且不会成为全局对象的属性

    和let和const声明的变量一样,类存在暂时性死区

  2. 类中的所有代码均是在严格模式下执行的

  3. 类的原型上的方法都是不可枚举的

  4. 类的原型上的方法都不能被作为构造函数使用

  5. 类的构造器必须使用new关键字进行调用

  6. 类可以不手动设置构造器,若不手动设置,则JS会帮你设置,只是构造器中什么代码都没有

可计算的成员名

class Animal{
    constructor(){
        // ...
    }
    
    ["a" + "b"](){
        // ...
    }
}

const animal = new Animal()
animal.ab();

getter和setter

class User{
    constructor(name, age){
        this.name = name;
        this._age = age;
    }
    
    // 当获取name属性时,会调用该函数,该函数没有参数
    get age(){
        return this._age;
    }
    
    // 当设置name属性时,会调用该函数,该函数只有一个参数
    set age(newAge){
        if(typeof newAge !== "number"){
            throw new Error("property age must be a number");
        }
        if(newAge < 0){
            newAge = 0;
        }else if(newAge > 100){
            newAge = 100;
        }
        this._age = newAge;
    }
}

var user = new User("张三", 18);
/**
	User {
		name: "张三",
		_age: 18,
		age: 18
	}
*/
user.age = 2000;
console.log(user.age);

这种写法等同于使用Object.defineProperty()给user定义了一个age属性,并且该属性的属性描述符中加入getter和setter

静态成员

类中使用static关键字声明的成员即为静态成员,或称类成员

class Chess{
    constructor(){
        // ...
    }
    
    static width = 50;
    static height = 50;
    static method(){ }
}

Chess.width;				// 50
Chess.method();

字段初始化器

ES7中支持了字段初始化器,即可以在构造器外给实例本身添加成员

class User{
    name = "张三";
    age = 18;
}

其实上面的代码最终还是会转化为:

class User{
    constructor(){
        this.name = "张三";
        this.age = 18;
    }
}

类表达式

在JS中,类本质上就是一个函数,既然有函数表达式,就会有类表达式,类表达式也可以出现在任何函数表达式可以出现的地方

var A = class {
    constructor(){}
    //...
}

var a = new A();

扩展

class Test {
    construct() { }
    
    method1 = () => {							// 这其实就是一个字段初始
        return this;
    }
    
    static method2 = () => {
        return this;
    }
}

var test = new Test();
console.log(test.method1() === test);			// true
console.log(Test.method2() === Test);			// true

类的继承

class Animal{
    // ...
}

class Cat extends Animal{
    // ...
}

使用extends可以让一个类继承另一个类,被继承的类叫做父类,主动继承的类叫做子类

继承发生后,子类原型的隐式原型将指向父类的原型

extends后,Cat.prototype.__proto__Animal.prototype

注意:继承是一对多的关系,一个子类最多只能继承自一个父类

super关键字

使用super关键字,可以在子类的构造器中调用父类的构造器

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

class Cat extends Animal {
    constructor(name, age, type) {
        super(name, age);
        this.type = type;
    }
}

var cat = new Cat("咪咪", 2, "花猫");
console.log(cat);

image.png

注意:

  • super作为函数使用时,只能出现在构造器中

  • 子类调用super时,父类构造器中的this会自动指向子类的实例

    class Animal {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
    }
    
    class Cat extends Animal {
        constructor(name, age, type) {
            super(name, age);
            this.type = type;
        }
    }
    

    这相当于:

    function Animal(name, age) {
        this.name = name;
        this.age = age;
    }
    
    function Cat(name, age, typee) {
    	Animal.call(this, name, age);
        this.type = type;
    }
    
    Object.setPrototypeOf(Cat.prototype, Animal.prototype);
    
  • 如果子类中设置了构造器,则必须要在子类构造器的最开始手动调用super

    如果子类没有设置构造器,则JS会自动帮你加入构造器,并且该构造器的参数和父类构造器一致,并且还会在该构造器的开始位置调用super并传入所有参数

    class Animal {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
    }
    
    class Cat extends Animal { }
    

    这等效于:

    class Animal {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
    }
    
    class Cat extends Animal{
        constructor(name, age){
            super(name, age);
        }
    }
    

super关键字也可作为父类的原型进行使用

class Animal {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    
    print(){
        console.log(this.name);
        console.log(this.age);
    }
}

class Cat extends Animal{
    constructor(name, age, type){
        super(name, age);
        this.type = type;
    }
    
    print(){
        super.print();
        console.log(this.type);
    }
}

var cat = new Cat("咪咪", 2, "花猫");
cat.print();

注意:

  • super关键字作为原型对象使用时,可以出现在原型方法以及构造器中
  • 使用super关键字调用父类原型的方法,其this指向仍为子类的实例