创建对象方式
工厂模式:用于抽象创建特定对象的过程。可以解决创建多个类似对象的问题,但没有解决对象标识问题。(即新创建的对象是什么类型)
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 person1 = createPerson("Nicholas", 29, "Software Engineer"); let person2 = createPerson("Greg", 27, "Doctor");
构造函数模式:用于创建特定类型对象。构造函数名称的首字母都是要大写的,用来区分非构造函数。
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("Nicholas", 29, "Software Engineer"); let person2 = new Person("Greg", 27, "Doctor"); person1.sayName(); // Nicholas person2.sayName(); // Greg
原型模式:每个函数都会创建一个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(); person1.sayName(); // "Nicholas" let person2 = new Person(); person2.sayName(); // "Nicholas" console.log(person1.sayName == person2.sayName); // true
继承的方式:接口继承和实现继承。接口继承在 ECMAScript 中是不可能的,因为函数没有签名。实现继承是 ECMAScript 唯一支持的继承方式,而这主要是通过原型链实现的。
原型链
其基本思想就是通过原型继承多个引用类型的属性和方法。function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; }; function SubType() { this.subproperty = false; } // 继承 SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.subproperty; }; let instance = new SubType(); console.log(instance.getSuperValue()); // true
原型链虽然是实现继承的强大工具,但它也有问题。问题一:出现在原型中包含引用值的时候。前面在谈到原型的问题时也提到过,原型中包含的引用值会在所有实例间共享,这也是为什么属性通常会在构造函数中定义而不会定义在原型上的原因。在使用原型实现继承时,原型实际上变成了另一个类型的实例。这意味着原先的实例属性摇身一变成为了原型属性。问题二:子类型在实例化时不能给父类型的构造函数传参。function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() {} // 继承 SuperType SubType.prototype = new SuperType(); let instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black" let instance2 = new SubType(); console.log(instance2.colors); // "red,blue,green,black"
解决原型包含引用值导致的继承问题
盗用构造函数:在子类构造函数中调用父类构造函数。优点可以在子类构造函数中向父类构造函数传参。缺点:必须在构造函数中定义方法,因此函数不能重用。
function SuperType(name){ this.name = name; } function SubType() { // 继承 SuperType 并传参 SuperType.call(this, "Nicholas"); // 实例属性 this.age = 29; } let instance = new SubType(); console.log(instance.name); // "Nicholas"; console.log(instance.age); // 29
组合继承:综合了原型链和盗用构造函数,使用原型链继承原型上的属性和方法,通过盗用构造函数继承实例属性。
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("Nicholas", 29); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black" instance1.sayName(); // "Nicholas"; instance1.sayAge(); // 29 let instance2 = new SubType("Greg", 27); console.log(instance2.colors); // "red,blue,green" instance2.sayName(); // "Greg"; instance2.sayAge(); // 27
原型式继承
ES5通过增加 Object.create()方法将原型式继承的概念规范化了,这个方法接收两个
参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个可选)。原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。let person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; let anotherPerson = Object.create(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); let yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
寄生式继承
function createAnother(original){ let clone = object(original); // 通过调用函数创建一个新对象 clone.sayHi = function() { // 以某种方式增强这个对象 console.log("hi"); }; return clone; // 返回这个对象 }
寄生式组合继承
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); // 第二次调用 SuperType() this.age = age; } SubType.prototype = new SuperType(); // 第一次调用 SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); };
寄生式组合继承算是引用类型继承的最佳模式!