zoukankan      html  css  js  c++  java
  • 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();
      
  • 相关阅读:
    JS高级程序设计 第三章笔记
    JS高级程序设计第二章
    JS高级程序设计 第一章读书笔记
    markdown 尝试
    实验九
    第六章总结
    实验五
    第五章总结
    实验四
    实验三
  • 原文地址:https://www.cnblogs.com/1328497946TS/p/15216094.html
Copyright © 2011-2022 走看看