JavaScript对象

JavaScript对象

一、属性类型

1.数据属性

  • configurable:表示属性是否可以通过过delete删除并定义,是否可以修改它的特性,以及是否可以把它改成访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
  • Enumerable:表示属性是否可以通过for-in循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
  • writable:表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
  • value:包含属性实际的值。读取和写入属性值的位置,默认值为undefined。

2.访问器属性

  • configurable:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
  • enumerable:表示属性是否可以通过for-in循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是true。
  • get:获取函数,在读取属性时调用。默认值为undefined。
  • set:设置函数,在写入属性时调用,默认值为undefined。

访问器属性是不能直接定义的,必须使用Object.defineProperty()。

二、Object.defineProperty和Object.defineProperties

// 定义单个属性
let a = {};
Object.defineProperty(a, 'name', {
  value: 'Mr.Yao',
  // 没有写的配置默认为false
  // configurable: false,
  // writable: false,
  // enumerable: false,
});
console.log(a);
// {}


// 定义多个属性
let a = {};
Object.defineProperties(a, {
  name_: {
    value: 'Mr.Yao',
    // 如果没有加上下面这条,name_无法写入
    // writable: true,
  },
  age: {
    value: 22,
  },
  name: {
    get () {
      return this.name_;
    },
    set (name) {
      this.name_ = name;
    },
  }
});

console.log(a)
console.log(Object.getOwnPropertyDescriptors(a))
console.log(a.name);
a.name = 'Mr.Do';
console.log(a.name);

三、读取属性的特性

使用Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。

使用Object.getOwnPropertyDescriptors()实际上会在每个自有属性上调用Object.getOwnPropertyDescriptor()并在一个新的对象中返回。

四、合并对象

Object.assign()这个方法接收一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举(Object.propertyIsEnumerable()返回true)和自有(Object.hasOwnProperty()返回true)属性复制到目标对象。Object.assign()实际上对每个源对象执行的是浅复制。如果多个源对象都有相同的属性,则使用最后一个复制的值。

五、创建对象

  1. 工厂模式

    工厂模式是一种众所周知的设计模式,广泛应用于软件工程领域,用于抽象创建特定对象的过程。

    function createPerson(name, age, job) {
      let o = new Object();
      o.name = name;
      o.age = age;
      o.job = job;
      o.sayName = function() {
        console.log(this.name);
      }
      return o;
    }
    
    let person = createPerson('Mr.Yao', 22, 'Student');
    console.log(person);
    
  2. 构造函数模式

    自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。

    function Person(name, age, job) {
      this.name = name;
      this.age = age;
      this.job = job;
      this.sayName = function() {
        console.log(this.name);
      }
    }
    
    let person1 = new Person('Mr.Yao', 22, 'Software Engineer');
    let person2 = new Person('MD', 12, 'Student');
    person1.sayName();
    person2.sayName();
    

    和工厂模式的区别:

    • 没有显式地创建对象。
    • 属性和方法直接赋值给this。
    • 没有return。

    要创建实例,应使用new操作符。

    • 在内存中创建一个对象。
    • 这个新对象内部的[[prototype]]特性被赋值为构造函数的prototype属性。
    • 构造函数内部的this被赋值为这个新对象(即this指向新对象)。
    • 执行构造函数内部的代码(给新对象添加属性)。
    • 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
  3. 原型模式

    每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。

    function Person() {}
    Person.prototype.name = 'Nicholas';
    Person.prototype.age = 29;
    Person.prototype.job = 'Software Engineer';
    Person.prototype.sayName = function() {
      console.log(this.name);
    }
    let person1 = new Person();
    let person2 = new Person();
    
    person1.sayName();
    person2.sayName();
    console.log(person1.sayName === person2.sayName)
    // true
    

    无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(即指向原型对象)。默认情况下,所有原型对象自动获得一个名为constructor的属性,指回与之关联的构造函数。Person.ptototype.constructor指向Person。Chrome,Firefox等浏览器实现,实例有一个__proto__指向原型。

六、继承

  1. 原型链

    每个构造函数都有一个原型对象,原型有个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例,那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。

    function SuperType() {
      this.superProperty = 'Super';
    }
    SuperType.prototype.saySuperName = function() {
      console.log(this.superProperty);
    }
    function SubType() {
      this.subProperty = 'Sub';
    }
    SubType.prototype = new SuperType();
    // console.log(SubType.prototype.constructor)
    // SubType原型是一个SuperType的实例,这个实例有一个指向SuperType原型的指针
    // SubType.prototype.constructor === SuperType
    SubType.prototype.saySubName = function() {
      console.log(this.subProperty);
    }
    let sub = new SubType;
    sub.saySuperName();
    sub.saySubName();
    

    子类有时候需要覆盖父类的方法,或者增加父类没有的方法。为此,这些方法必须在原型赋值之后再添加到原型上。

    以对象字面量方式创建原型方法会破坏之前的原型链,因为这相当于重写了原型链。

    
    function SuperType() {
      this.superProperty = 'Super';
    }
    SuperType.prototype.saySuperName = function() {
      console.log(this.superProperty);
    }
    function SubType() {
      this.subProperty = 'Sub';
    }
    SubType.prototype = new SuperType();
    // console.log(SubType.prototype.constructor)
    // console.log(SubType.prototype.constructor === SuperType)
    // 通过对象字面量添加新方法,这会导致上一行无效
    SubType.prototype = {
      saySuperName() {
        console.log(this.superProperty);
      },
      saySubName() {
        console.log(this.subProperty);
      }
    }
    SubType.prototype.saySubName = function() {
      console.log(this.subProperty);
    }
    let sub = new SubType;
    sub.saySuperName();
    sub.saySubName();
    
  2. 盗用构造函数

    在子类构造函数中调用父类构造函数。函数是在特定上下文中执行代码的简单对象,所以可以使用apply()和call()方法以新创建的对象对上下文执行构造函数。

    function SuperType() {
      this.colors = ['red', 'blue', 'green'];
    }
    function SubType() {
      // 继承SuperType
      SuperType.call(this);
    }
    let instance1 = new SubType();
    instance1.colors.push('black');
    console.log(instance1.colors);
    let instance2 = new SubType();
    console.log(instance2.colors);
    

    通过使用call()或者apply()方法,SuperType构造函数在为SubType的实例创建的新对象的上下文中执行了。这相当于新的SubType对象上运行了SuperType()函数中的所有初始化代码。

  3. 组合继承

    使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。

    function SuperType(name) {
      this.name = name;
      this.colors = ['red', 'blue', 'green'];
    }
    SuperType.prototype.sayName = function() {
      console.log(this.name);
    }
    function SubType(name, age) {
      // 继承属性
      SuperType.call(this, name);
      this.age = age;
    }
    // 继承方法
    SubType.prototype = new SuperType();
    SubType.prototype.sayAge = function() {
      console.log(this.age);
    }
    let instance1 = new SubType('Mr.Yao', 22);
    instance1.colors.push('black');
    console.log(instance1.colors);
    instance1.sayName();
    instance1.sayAge();
    
    let instance2 = new SubType('MD', 18);
    instance2.sayName();
    instance2.sayAge();
    console.log(instance2.colors);
    
  4. 原型式继承

    object函数会创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时类型的一个实例。

    function object(o) {
      function F() {};
      F.prototype = o;
      return new F();
    }
    let person = {
      name: 'Nicholas',
      friends: ['Shely', 'Court', 'Van'],
    };
    let anotherPerson = object(person);
    anotherPerson.name = 'Greg';
    anotherPerson.friends.push('Rob');
    console.log(person.friends)
    

    Object.create()方法将原型式继承的概念规范化了。

  5. 寄生式继承

    创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。

    
    function createAnother(original) {
      let clone = Object.create(original);
      clone.sayHi = function() {
        console.log('hi');
      };
      return clone;
    }
    let person = {
      name: 'Nichloas',
      friends: ['Shely', 'Court', 'Van'],
    };
    let anotherPerson = createAnother(person);
    anotherPerson.sayHi();
    

    object.create()函数不是寄生式继承所必须的,任何返回新对象的函数都可以在这里使用。

  6. 寄生式组合继承

    组合继承存在效率问题,父类构造函数始终会被调用两次:一次在创建子类原型时调用,另一次在子类构造函数中调用。

    function SuperType(name) {
      this.name = name;
      this.colors = ['red', 'blue', 'green'];
    }
    SuperType.prototype.sayName = function() {
      console.log(this.name);
    }
    function SubType(name, age) {
      // SubType实例化的时候第二次调用SuperType()
      SuperType.call(this, name);
      this.age = age;
    }
    // 第一次调用SuperType()
    SubType.prototype = new SuperType();
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function() {
      console.log(this.age);
    };
    

    寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。

    function inheritPrototype(subType, superType) {
      // 创建一个父类原型实例
      let prototype = Object.create(superType.prototype);
      prototype.constructor = subType;
      // 赋值给子类原型
      subType.prototype = prototype;
    }
    function SuperType(name) {
      this.name = name;
      this.colors = ['red', 'blue', 'green'];
    }
    SuperType.prototype.sayName = function() {
      console.log(this.name);
    }
    function SubType(name, age) {
      // SubType实例化的时候第二次调用SuperType()
      SuperType.call(this, name);
      this.age = age;
    }
    // 第一次调用SuperType()
    // SubType.prototype = new SuperType();
    inheritPrototype(SubType, SuperType);
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function() {
      console.log(this.age);
    };
    let instance = new SubType();
    console.log(instance.colors);
    console.log(instance instanceof SuperType);
    // true
    console.log(instance instanceof SubType);
    // true
    

七、类

1.类的构成

类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类函数,但这些都不是必须的。空的类定义照样有效。默认情况下,类定义中的代码都在严格模式下执行。

2.类的构造函数

  1. 实例化

    使用new调用类的构造函数会执行如下操作。

    1. 在内存中创建一个新的对象。
    2. 这个新对象内部的[[prototype]]指针被赋值为构造函数的prototype属性。
    3. 构造函数内部的this被赋值为这个新对象(即this指向新对象)。
    4. 执行构造函数内部的代码(给新对象添加属性)。
    5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的对象。
  2. 把类当成构造函数

    类标识符有prototype属性,而这个原型也有一个constructor属性指向类自身。

  3. 实例成员

    每次通过new调用类标识符时,都会执行类构造函数。在这个函数内部,可以为新创建的实例(this)添加”自有“属性。每个实例对应一个唯一的成员对象,这意味着所有成员都不会在原型上共享。

  4. 原型方法与访问器

    为了在实例间共享方法,类定义语法把在类块中定义的方法作为原型方法。

  5. 静态方法

    静态类成员在类定义中使用static关键字作为前缀。在静态成员中,this引用类自身。

  6. 非函数原型和类成员

    class Person {
      sayName() {
        console.log(`${Person.greeting} ${this.name}`);
      }
    }
    // 在类上定义数据成员
    Person.greeting = 'My name is';
    // 在原型上定义数据成员
    Person.prototype.name = 'jake';
    let p = new Person();
    p.sayName();
    // My name is jake
    

3.继承

  1. 继承基础

    使用extends关键字,就可以继承任何拥有[[construct]]和原型的对象。派生类都会通过原型链访问到类和原型上定义的方法。this的值会反映调用相应方法的实例或者类。

    class Vehicle {
      identifyPrototype(id) {
        console.log(id, this);
      }
      static identifyClass(id) {
        console.log(id, this);
      }
    }
    
    class Bus extends Vehicle {};
    let v = new Vehicle();
    let b = new Bus();
    b.identifyPrototype('bus');
    Bus.identifyClass('bus');
    v.identifyPrototype('vehicle');
    Vehicle.identifyClass('vehicle');
    
  2. 构造函数和super()

    派生类的方法可以通过super关键字引用它们的原型。这个关键字只能在派生类中使用,而且仅限于类构造函数、实例方法和静态方法内部。在类构造函数中使用super可以调用父类构造函数。

    class Vehicle {
      constructor() {
        this.hasEngine = true;
      }
    }
    class Bus extends Vehicle {
      constructor() {
        // 不要在调用super()之前使用this,否则会抛出ReferenceError
        super();// 相当于super.constructor()
        console.log(this instanceof Vehicle);
        console.log(this);
      }
    }
    new Bus();
    
    1. super只能在派生类构造函数和静态方法中使用
    2. 不能单独引用super关键字,要么用它调用构造函数,要么用它引用静态方法。
    3. 调用super()会调用父类构造函数,并将返回的实例赋值给this。
    4. super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需手动传入。
    5. 如果没有定义类构造函数,在实例化派生类时会调用super(),而且会传入所有传给派生类的参数。
    6. 在类构造函数中,不能在调用super()之前引用this。
    7. 如果在派生类中显式定义了构造函数,则要么在其中调用super(),要么必须在其中返回一个对象。
  3. 抽象基类

    class A {
      constructor() {
        if (new.target === A) {
          throw new Error('A不能被实例化');
        }
      }
    }
    let a = new A;
    // Error: A不能被实例化
    
  4. 类混入[Mixin]

    
    class Vehicle {};
    let FooMixin = (SuperClass) => class extends SuperClass {
      foo() {
        console.log('foo');
      }
    };
    let BarMixin = (SuperClass) => class extends SuperClass {
      bar() {
        console.log('bar');
      }
    };
    let BazMixin = (SuperClass) => class extends SuperClass {
      baz() {
        console.log('baz');
      }
    };
    class Bus extends FooMixin(BarMixin(BazMixin(Vehicle))) {};
    let b = new Bus();
    b.foo();
    b.bar();
    b.baz();
    
    class Vehicle {};
    let FooMixin = (SuperClass) => class extends SuperClass {
      foo() {
        console.log('foo');
      }
    };
    let BarMixin = (SuperClass) => class extends SuperClass {
      bar() {
        console.log('bar');
      }
    };
    let BazMixin = (SuperClass) => class extends SuperClass {
      baz() {
        console.log('baz');
      }
    };
    // class Bus extends FooMixin(BarMixin(BazMixin(Vehicle))) {}
    function mix(BaseClass, ...Mixins) {
      return Mixins.reduce((pre, cur) => {
        return cur(pre);
      }, BaseClass)
    }
    class Bus extends mix(Vehicle, FooMixin, BarMixin, BazMixin) {};
    let b = new Bus();
    b.foo();
    b.bar();
    b.baz();
    
posted @ 2021-09-01 19:20  Mr-Yao  阅读(45)  评论(0编辑  收藏  举报