JS创建对象的方式
JS创建对象是一个老生常谈的面试题,非常考验JS的基础功底。可能有些同学会想,创建对象?不就是const obj = {};么?
这里我们将讲述其他的几种方式,来进行对象的创建。
前置知识
我们知道对象实例中,都有一个指针[[prototype]]指向对象构造函数的原型;
而构造函数的原型中有一个constructor属性指向构造函数;
构造函数可以通过prototype访问到对应的原型;
原型链就是访问对象的属性,当对象内部不存在,则去原型对象中寻找,如果还是不存在,再去原型对象的原型去寻找,然后一直找下去就是原型链。
创建对象的6种模式
1.工厂模式
方式: 通过一个普通的函数,内部通过new Object()创建一个对象,依次给创建的对象添加新的属性后return 该对象。
优点: 实现了一个简单的对象创建,每次调用该函数,都会创建一个新的对象。
问题:
- constructor始终指向了Object
- 工厂函数内的方法始终都是一样的,但是确重复声明了多次
function factory() {
const obj = new Object();
obj.a = 1;
obj.say = function () {
console.log("我是一个工厂函数");
};
return obj;
}
const factoryObj1 = factory();
const factoryObj2 = factory();
factoryObj1.say(); // 我是一个工厂函数
console.log(factoryObj1 === factoryObj2); // false
2.构造函数模式
方式: 通过创建一个构造函数来实现,首字母大写!!!通过new来创建实例
优点: 能够通过constructor和instanceof来识别出实例的类型
问题: 多个实例的方法都是实现一样的效果确存储多次
注意 构造函数中,默认是返回this的(一般这个this指向的是新的实例),如果不通过new 来调用中,则会给全局对象添加方法和属性
function ConstructFn(name, age) {
this.name = name;
this.age = age;
this.say = function () {
console.log(this.name, "这是构造函数创建的对象");
};
}
const constructObj = new ConstructFn("test", 1);
console.log(constructObj); // {name:'test',age:1,say:f()}
console.log(constructObj instanceof ConstructFn); // true
当我们直接执行构造函数的时候ConstructFn("window", "all");我们能在window中找到对应的属性。
3.原型模式
方式: 创建一个构造函数,构造函数函数体内不进行操作,在外部给构造函数的原型增加属性和方法。
优点: 每个实例都共享原型上的方法和属性
问题:
- 如果出现引用属性的时候,因为引用类型是按照引用地址来访问的,多个实例共享了同个引用地址,其中一个实例更改了这个引用的值,同样也会反馈到其他实例上。
- 实例调用方法或者属性的时候,会搜索两次。第一次搜索实例本身,第二次根据原型链去搜索实例的原型。
- 没法创建实例自己的属性和方法
注意: 如果直接用字面量的方式给构造函数的原型赋值会造成原型constructor属性的改变,所以需要将constructor指回构造函数。
function PrototypeFn() {}
PrototypeFn.prototype.name = "hanmeimei";
PrototypeFn.prototype.say = function () {
console.log("这是原型模式创建的对象");
};
PrototypeFn.prototype.arr = [1, 2];
const obj1 = new PrototypeFn();
const obj2 = new PrototypeFn();
obj1.say(); // 这是原型模式创建的对象
obj1.arr.push(3); // 通过实例obj1更改原型上的引用类型
console.log(obj2.arr); // [1,2,3]
4.构造函数和原型组合模式
方式: 结合构造函数模式和原型模式,方法使用原型模式来创建,属性使用构造函数模式来创建
优点: 这是一个相对比较完美的实现
缺点: 每次实例化都会为构造函数的原型重新赋值
function ConstructAndPrototypeFn(name) {
this.name = name;
this.friends = ["lilei"];
ConstructAndPrototypeFn.prototype.say = function () {
console.log("这是组合模式创建的");
};
}
const obj = new ConstructAndPrototypeFn("hanmeimei");
obj.say(); //这是组合模式创建的
5.动态原型模式
方式: 在上述组合模式的基础上,给原型添加方法时增加一层判断,如果已经存在某个方法或者属性则不进行添加。
优点: 检查某个应该存在的方法是否有效,来决定是否需要初始化原型
注意: 只需要检查一次就可以了,不能用对象字面量的方式对原型进行赋值操作。
function dynamicFn(name) {
this.name = name;
if (typeof this.say !== "function") {
dynamicFn.prototype.say = function () {
alert(this.name);
};
}
}
6.寄生构造函数模式
方式: 这种模式和工厂模式比较像,返回的对象与构造函数或者与构造函数的原型属性之间没有关系,与构造函数内部的new的构造函数有关系。
优点: 可以在特殊的情况下用来为对象创建构造函数
缺点: 不能用instanceof来判断类型
function ParasitismConstructorFn(name) {
var o = new Object();
o.name = name;
o.say = function () {
alert(this.name);
};
return o;
}
var obj1 = new ParasitismConstructorFn("hanmeimei");
console.log(obj1);
7.稳妥构造函数模式
方式: 和寄生构造函数有点类似,但是不会将实参赋值给构造函数内创建的实例,而是通过构造函数内的方法去访问。
优点: 安全,那么好像成为了私有变量,只能通过构造函数内的方法去进行访问。
缺点: 不能区分实例的类型
function Person(name) {
var o = new Object();
o.say = function () {
console.log(name);
};
return o
}
var person1 = new Person("hanmeimei");
person1.name; // undefined
person1.say(); // hanmeimei
总结
总体对象的创建有以上7种方式,有问题的小伙伴可以给我留言,感谢大家的支持。