JavaScript对象机制

JavaScript对象机制

ECMAScript规范明确定义了这样的概念:对象是零到多个的属性的集合。JavaScript是基于原型编程,ES5中使用了很多特性来模拟类的行为,虽然ES6新引入了class关键字正式定义类,但本质仍是原型和构造函数。

一、创建对象的方式

​ JavaScript提供了多种创建对象的方式:

1.工厂模式

function createPerson(first, last, age, gender, interests) {
    let o = new Object();
    o.name = {
        first,
        last
    };
    o.age = age;
    o.gender = gender;
    o.interests = interests;
    o.greeting = function () {
        alert('Hi I\'m' + o.name.first + '.');
    }
}

​ 但是工厂模式存在一个问题,即无法表示新创建的对象是什么类型(所有创建出的对象都是Object,不具有唯一性)。

2.构造函数模式

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

function Person(first, last, age, gender, interests) {
    this.name = {
        first,
        last
    };
    this.age = age;
    this.gender = gender;
    this.interests = interests;
    this.greeting = function () {
        alert('Hi, I\'m' + o.name.first + '.');
    }
}

let person1 = new Person('Tammi', 'Smith', 17, 'female', ['music', 'skiing', 'kickboxing']);

​ 由于构造函数就是能创建对象的函数,因此为了区分,构造函数首字母为大写,非构造函数首字母为小写。事实上该函数是以类的身份来声明了一系列的属性。创建Person的实例,需要使用new操作符。使用构造函数模式会带来如下操作:

  1. 内存中创建新对象
  2. 新对象[[Prototype]]特性被赋值为构造函数的Prototype特性
  3. this指向这个新对象
  4. 函数可以返回非空对象。如果没有则返回创建的新对象

​ 构造函数也是普通的函数,因此可以将其作为普通函数调用。没有使用new操作符时,结果会将属性和方法添加到Global对象上。而在浏览器中Global对象为window对象。

// 作为函数调用
Person('Tammi', 'Smith', 17, 'female', ['music', 'skiing', 'kickboxing']);
console.log(window.age); // 17

// 在另一个对象的作用域中调用
// 通过Call()调用时,将对象o指定为Person()内部的this值,因此执行完函数后所有属性和方法将添加到对象o上
let o = new Object();
Person.call(o, 'Tammi', 'Smith', 17, 'female', ['music', 'skiing', 'kickboxing']);
console.log(o.first.name);  // Tammi

​ 使用构造函数模式时存在的问题是会定义两个不同的Function实例。不同实例上的函数同名却不等价。解决这个问题需要将函数定义转到构造函数外部:

function Person(first, last, age, gender, interests) {
    this.name = {
        first,
        last
    };
    this.age = age;
    this.gender = gender;
    this.interests = interests;
    this.greeting = greeting;
}

function greeting () {
    alert('Hi, I\'m' + this.name.first + '.'); 
}

​ 在构造函数内greeting属性指向了全局的greeting()函数。如此以来person1person2共享了全局作用域上的greeting()函数。但这样带来的问题也显而易见,如果该对象需要多个方法就要在全局作用域中定义多个函数。从面向对象编程的观点来看,这样的做法破坏掉了“封装性”。原型模式可以解决这一问题。

3.原型模式

​ 使用原型对象的好处是,定义的所有属性和方法都可以被所有的对象实例共享。

function Person() {}

Person.prototype.name = ['Tammi', 'Smith'];
Person.prototype.age = 17;
Person.prototype.gender = 'female';
Person.prototype.interests = ['music', 'skiing', 'kickboxing'];
Person.prototype.greeting = function () {
    alert('Hi, I\'m' + this.name.first + '.'); 
}

let person1 = new Person();
console.log(person1.age);  // 17

let person2 = new Person();
console.log(person2.age);  // 17

​ 因此,person1和person2访问的都是相同属性和相同的greeting()函数。

二、类

	### 1.类的定义及构造函数

​ ES6引入了class关键字来具备正式的定义类的能力。表面上看可以正式支持面向对象编程,但实际上仍是使用原型和构造函数的概念。constructor关键字用于在类定义块内部创建类的构造函数,并告诉解释器在使用new操作符创建类的新的实例时应调用这个函数。

class Person {
  constructor(first, last, age, gender, interests) {
    this.name = {
      first,
      last
    };
    this.age = age;
    this.gender = gender;
    this.interests = interests;
  }

  greeting() {
    console.log(`Hi! I'm ${this.name.first}`);
  };

  farewell() {
    console.log(`${this.name.first} has left the building. Bye for now!`);
  };
}

2.实例化

实例化过程与构造函数模式相似:

let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling']);
han.greeting();
// Hi! I'm Han

let leia = new Person('Leia', 'Organa', 19, 'female', ['Government']);
leia.farewell();
// Leia has left the building. Bye for now

​ 事实上在后台这些类也将被转为原型模式。但ES6提供了更为便利的语法糖,降低了编写的难度。

class Person {
    constructor() {
        this.name = new String('Hellcat');
        this.greeting = () => console.log(`Hi! I'm ${this.name}`);
        this.interests = ['music', 'programming'];
    }
}

​ 而每一个实例都对应一个唯一的成员对象,这意味着所有成员都不会在原型上共享:

let p1 = new Person(), p2 = new Person();
p1.name === p2.name;    // false
p1.greeting();    // Hi! I'm Hellcat
p2.greeting();    // Hi! I'm Hellcat

三、继承

​ 面向对象的语言(如Java)提供两种继承:接口继承和实现继承。前者只继承方法的函数签名,后者继承实际的方法。但在ECMAScript中没有函数签名这一概念,因此无法实现接口继承。ECMAScript唯一支持的继承方式是实现继承,主要通过原型链来实现。

1. 原型链

​ 每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。

2.盗用构造函数

​ 在子类构造函数中调用父类构造函数。

3.基于类的继承

上一篇:Shell学习笔记


下一篇:webpack高级概念,typeScript的打包配置(系列十三)