一、创建对象的几种方式
(1)工厂模式
工厂模式的主要原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。但其缺点是创建出来的对象无法和某个类型联系起来,即只是简单的封装了复用·代码,而没有建立起对象和类型间的关系。
//1.工厂模式
function creatPerson(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 = creatPerson("Mary", 18, "Student");
console.log(person1);
//输出
{
name: 'Mary',
age: 18,
job: 'Student',
sayName: [Function (anonymous)]
}
【小结】原理:利用函数来封装创建对象的细节(函数的复用) 不足:没有解决对象标识问题(即创建的对象是什么类型
(2)构造函数模式
EMAScript中的构造函数是用于创建特定指定类型的对象的。如Object、Array等原生构造函数,运行时可以直接再执行环境中使用。当然也可以自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。一般地,构造函数与普通函数的区在于:1)没有显示地创建对象; 2)属性和方法直接赋值给了this; 3)没有return,且构造函数名首字母一般大写。
//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("Mary", 18, "Student");
console.log(person1);
// 输出
// Person {
// name: 'Mary',
// age: 18,
// job: 'Student',
// sayName: [Function (anonymous)]
// }
要创建Person的实例,应使用new操作符,以这种方式调用构造函数会执行如下操作:
1)在内存中创建一个新对象
2)这个新对象的原型指向构造函数的prototype属性(原型对象)
3)构造函数内部的this被赋值为这个新对象(即this指向新对象)
4)执行构造函数内部的代码(给对象添加属性)
5)如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
特别提醒:构造函数也是函数,即任何函数不适用new操作符调用的函数就是普通函数。此外由于没有自己的this,且没有prototype属性,箭头函数无法用作构造函数。
//接上述构造函数的例子
//作为函数调用且没有明确设置this值的指向,this始终指向Global对象(在浏览器中就是window对象)
Person("Mark", 18, "Student");
window.sayName(); //输出Mark
//在另一个对象的作用域中调用
//明确设置this值:1)作为对象的方法调用 2)使用call()/apply()调用
let o = new Object();
Person.call(o, "May", 18, "Student");
o.sayName(); // 输出 May
【构造函数的问题】
主要问题在于,其定义的方法会在每个实例上都创建一遍。逻辑上讲,这个构造函数实际上是这样的:
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
// this.sayName = function () { console.log(this.name);};
this,sayName = new Function("console.log(this.name);")//逻辑等价
}
所以每个Person实例都会有自己的Function实例用于显示name属性,因此不同实例上的函数虽然同名却不相等,即:
console.log(person1.sayName == person2.sayName) //false
解决:在全局作用域上定义sayName函数,person1和person2共享这个函数(this.sayName = sayName) ,从而解决逻辑上函数重复定义的问题,但全局作用域也因此被搞乱了
另外,顺便延伸一道面试题---手写new操作符~~~
// 手写new操作符
//1)在内存中创建一个新对象
//2)这个新对象的原型指向构造函数的prototype属性(原型对象)
//3)构造函数内部的this被赋值为这个新对象(即this指向新对象)
//4)执行构造函数内部的代码(给对象添加属性)
//5)如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
function newOperator(ctor) {
// 校验构造函数
if (typeof ctor !== "function") throw ctor + "is not a constructor";
// 设置new.target
newOperator.target = ctor;
// 创建实例对象
let ctor_obj = Object.create(ctor.prototype);
// 获取其他参数
let params = Array.prototype.slice.call(arguments, 1);
// 传入参数、绑定this、获取构造函数返回的结果
let instance = ctor.call(ctor_obj, ...params);
// 判断构造函数返回的类型
let isObject = typeof instance === "object" && instance !== null;
let isFunction = typeof instance === "function";
if (isObject || isFunction) {
return instance;
}
return ctor_obj;
}
function Aconstructor(a, b) {
this.a = a;
this.b = b;
}
let res = newOperator(Aconstructor, 1, 2);
## console.log(res); //输出 Aconstructor { a: 1, b: 2 }
(3)原型模式
对于原型模式,因为每一个函数都有一个prototype属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。因此可以使用原型对象来添加公用属性和方法,从而实现代码的复用。这种方式对于构造函数模式来说,解决了函数对象复用的问题。但是这种模式也存在一些问题,一个是无法通过传入参数来初始化值,另一个是如果存在一个引用类型如Array这样的值,那么所有的实例将共享一个对象,一个实例引用类型值的改变会影响所有的实例。 【小结】优势:在原型对象(就是通过调用构造函数创建的对象的原型)上面定义的属性和方法可以被对象实例共享;主要问题:原型的共享特性
// 3.原型模式
function Person() {}
Person.prototype.name = "Mary";
Person.prototype.age = 18;
Person.prototype.job = "Student";
Person.prototype.sayName = function () {
console.log(this.name);
};
let person1 = new Person();
let person2 = new Person();
console.log(person1.sayName === person2.sayName); //true
console.log(person1.name === person2.name);//true
console.log(person1 == person2); //false
//存在问题
function Person() {}
Person.prototype = {
constructor: Person,
name: "Mary",
friends: ["Mark", "May"],
};
let person1 = new Person();
let person2 = new Person();
person1.friends.push("Mimi");
console.log(person1.friends); //输出:[ "Mark", "May", "Mimi" ]
console.log(person2.friends); //输出:[ "Mark", "May", "Mimi" ]
(4)混合模式
混合模式,即组合使用构造函数模式和原型模式,具体为通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。其不足之处是:代码的封装性不够好。
// 4.混合模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor: Person,
sayName: function () {
console.log(this.name);
},
};
let person1 = new Person("Mark", 18, "Student");
console.log(Person.prototype); //输出:{ constructor: [Function: Person], sayName: [Function: sayName] }
person1.sayName(); //输出 "Mark"