创建对象的几种模式
工厂模式:
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){ 或者 //let Person = function(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
要创建 Person 的实例,应使用 new 操作符。以这种方式调用构造函数会执行如下操作。 (1) 在内存中创建一个新对象。 (2) 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
(3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。 (4) 执行构造函数内部的代码(给新对象添加属性)。 (5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
console.log(person1.constructor == Person); // true console.log(person2.constructor == Person); // true constructor 本来是用于标识对象类型的。不过,一般认为 instanceof 操作符是确定对象类型更可靠的方式。
console.log(person1 instanceof Object); // true console.log(person1 instanceof Person); // true console.log(person2 instanceof Object); // true console.log(person2 instanceof Person); // true
定义自定义构造函数可以确保实例被标识为特定类型,相比于工厂模式,这是一个很大的好处。
在实例化时,如果不想传参数,那么构造函数后面的括号可加可不加。只要有 new 操作符,就可以调用相应的构造函数
构造函数的主要问题在于,其定义的方法会在每个实例上都创建一遍。
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = new Function("console.log(this.name)"); // 逻辑等价 }
v> this 对象可以把函数与对象的绑定推迟到运行时; 可以把函数定义转移到构造函数外部: function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName() { console.log(this.name); }
这样虽然解决了相同逻辑的函数重复定义的问题,但全局作用域也因此被搞乱了,因为那个函数实际上只能在一个对象上调用。如果这个对象需要多个方法,那么就要在全局作用域中定义多个函数。这会致自定义类型引用的代码不能很好地聚集一起。这个新问题可以通过原型模式来解决。
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){ 或者 //let Person = function(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
要创建 Person 的实例,应使用 new 操作符。以这种方式调用构造函数会执行如下操作。 (1) 在内存中创建一个新对象。 (2) 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
(3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。 (4) 执行构造函数内部的代码(给新对象添加属性)。 (5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
console.log(person1.constructor == Person); // true console.log(person2.constructor == Person); // true constructor 本来是用于标识对象类型的。不过,一般认为 instanceof 操作符是确定对象类型更可靠的方式。
console.log(person1 instanceof Object); // true console.log(person1 instanceof Person); // true console.log(person2 instanceof Object); // true console.log(person2 instanceof Person); // true
定义自定义构造函数可以确保实例被标识为特定类型,相比于工厂模式,这是一个很大的好处。
在实例化时,如果不想传参数,那么构造函数后面的括号可加可不加。只要有 new 操作符,就可以调用相应的构造函数
构造函数的主要问题在于,其定义的方法会在每个实例上都创建一遍。
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = new Function("console.log(this.name)"); // 逻辑等价 }
v> this 对象可以把函数与对象的绑定推迟到运行时; 可以把函数定义转移到构造函数外部: function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName() { console.log(this.name); }
这样虽然解决了相同逻辑的函数重复定义的问题,但全局作用域也因此被搞乱了,因为那个函数实际上只能在一个对象上调用。如果这个对象需要多个方法,那么就要在全局作用域中定义多个函数。这会致自定义类型引用的代码不能很好地聚集一起。这个新问题可以通过原型模式来解决。