js创建对象的方式
方式一:利用对象字面量的方式
var obj = {
name: '影子',
age: 18,
job: '前端开发爱好者',
sayName: function () {
console.log(this.name + '今年' + this.age + '岁。她是一名' + this.job);
}
}
- 适用场景:起始时对象的数据确定;
- 使用对象字面量虽然可以很方便的创建对象,但是当创建具有相同接口的多个对象时需要重复编写很多相同的代码。
方式二:工厂函数方式
function createFunction(name, age, job) {
let obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayName = function () {
console.log(this.name + '今年' + this.age + '岁。她是一名' + this.job)
}
return obj;
}
// 这里创建对象不使用new
let person1 = createFunction('影子', 18, '前端开发爱好者');
let person2 = createFunction('小古', 19, 'C++爱好者')
person1.sayName(); //影子今年18岁。她是一名前端开发爱好者
person2.sayName(); //小古今年19岁。她是一名C++爱好者
console.log(person1 instanceof createFunction); //false
console.log(person1.__proto__ == createFunction.prototype); //false
- 适用场景:需要创建多个对象,可以用不同的参数多次调用这个函数;
- 工厂模式的主要步骤就是在函数里面创建一个新对象,然后对这个新对象赋予属性和方法,最后再返回这个新对象;
- 工厂模式相比对象字面量的虽然可以解决重复创建多个类似对象的问题,但是没有解决对象标识问题,即创建出来的对象与工厂函数没有什么关联。
方式三:构造函数方式(1)
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name + '今年' + this.age + '岁。她是一名' + this.job);
}
}
let person1 = new Person('影子', 18, '前端开发爱好者');
let person2 = new Person('小古', 19, 'C++爱好者');
console.log(person1 instanceof Person); //true
console.log(person1.__proto__ == Person.prototype); //true
- 构造函数相对于工厂函数的区别: (1)没有显示地创建对象;
(2)属性和方法直接赋值给this;
(3)没有return;
(4)使用构造函数可以确保实例被标识为特定类型;
- 构造函数存在的问题:其定义的方法会在每个实例上都创建一遍,重复创建方法会开辟新的内存进行存储。
方式四、构造函数方式(2)
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
console.log(this.name + '今年' + this.age + '岁。她是一名' + this.job);
}
let person1 = new Person('影子', 18, '前端开发爱好者');
let person2 = new Person('小古', 19, 'C++爱好者');
person1.sayName(); //影子今年18岁。她是一名前端开发爱好者
person2.sayName(); //小古今年19岁。她是一名C++爱好者
- 在这里,函数sayName被定义在构造函数外面,函数sayName相当于是全局上的函数;
- 构造函数Person中的sayName属性包含的只是一个指向外部函数的指针,person1和person2共享这个全局作用域上的sayName函数;
- 这种方法虽然解决了方式三中的相同逻辑的函数被重复定义的问题,但存在的问题是全局作用域容易混乱, 当一个对象需要调用多个方法时,那么就需要在全局作用域上定义多个函数,这会导致自定义类型引用的代码不能很好地聚集在一起。
方式五、原型模式(1)
每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。
function Person(name, age, job) {
Person.prototype.name = name;
Person.prototype.age = age;
Person.prototype.job = job;
Person.prototype.sayName = function () {
console.log(this.name + '今年' + this.age + '岁。她是一名' + this.job);
};
}
let person1 = new Person('影子', 18, '前端开发爱好者');
let person2 = new Person('小古', 19, 'C++爱好者');
person1.sayName(); //小古今年19岁。她是一名C++爱好者
person2.sayName(); //小古今年19岁。她是一名C++爱好者(person2的属性覆盖了person1的属性)
- 优点:原型方式的好处是每一个实例都共享同一个方法,避免了重复创建相同的方法;
- 缺点:当某一个实例更改原型里的属性时,后面创建的对象的属性值会覆盖上一次创建的对象的属性值。
方式六、原型模式(2)
function Person() { }
Person.prototype.name = '影子';
Person.prototype.age = 18;
Person.prototype.job = '前端开发爱好者';
Person.prototype.sayName = function () {
console.log(this.name + '今年' + this.age + '岁。她是一名' + this.job);
};
let person1 = new Person();
let person2 = new Person();
person1.sayName(); //影子今年18岁。她是一名前端开发爱好者
person2.sayName(); //影子今年18岁。她是一名前端开发爱好者
- 这种方式,构造函数体中什么都没有,所有的属性和方法都直接添加到了Person的prototype属性上;
- 优点:省略了为构造函数传递初始化参数;
- 缺点:所有实例在默认情况下都取得相同的值;
方式七、组合模式(构造函数+原型模式)(使用最广泛的一种模式)
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job
}
Person.prototype.sayName = function () {
console.log(this.name + '今年' + this.age + '岁。她是一名' + this.job)
}
let person1 = new Person('影子', 18, '前端开发爱好者');
let person2 = new Person('小古', 19, 'C++爱好者');
person1.sayName(); //影子今年18岁。她是一名前端开发爱好者
person2.sayName(); //小古今年19岁。她是一名C++爱好者
- 组合模式在构造函数中定义实例属性,在原型上定义方法和共享的属性(constructor等),该私有的私有,该共享的共享;
- 优点: (1)每个实例都会有自己的一份实例属性的副本,同时又共享对方法的引用,节省了内存;
(2)组合模式解决了原型模式没有办法传递参数的缺点,解决了构造函数模式不能共享方法的缺点;
方式八、动态原型模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
// sayName函数只有在第一次没有创建的时候在原型上加添加该方法;
if (typeof this.sayName != 'function') {
Person.prototype.sayName = function () {
console.log(this.name + '今年' + this.age + '岁。她是一名' + this.job);
}
}
}
let person1 = new Person('影子', 18, '前端开发爱好者');
let person2 = new Person('小古', 19, 'C++爱好者');
person1.sayName();//影子今年18岁。她是一名前端开发爱好者
person2.sayName();//小古今年19岁。她是一名C++爱好者
- 每次new一个对象时,构造函数里面的代码都会执行一遍,都会重新定义一个新的函数(sayName),然后挂载到Person.prototype属性上,在这里只定义了一次,因为所有的实例都会共享此属性。
- 不能使用对象字面量的写法,因为对象字面量会生成一个新的对象,切断了与构造函数的关系。
if (typeof this.sayName != 'function') {
Person.prototype.sayName = function () {
console.log(this.name + '今年' + this.age + '岁。她是一名' + this.job);
}
// 除了sayName之外还有其他方法,那么将这些方法放在对sayName的if判断里面
Person.prototype.sayName1 = function () { }
Person.prototype.sayName2 = function () { }
Person.prototype.sayName3 = function () { }
...
}
这样的方式使得这些方法的存在性是一致的,要么就全都没有定义,要么就全都定义了。
方式九、寄生构造函数模式
function Person(name, age, job) {
let obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayName = function () {
console.log(this.name + '今年' + this.age + '岁。她是一名' + this.job)
}
return obj;
}
let person1 = new Person('影子', 18, '前端开发爱好者');
let person2 = new Person('小古', 19, 'C++爱好者')
person1.sayName(); //影子今年18岁。她是一名前端开发爱好者
person2.sayName(); //小古今年19岁。她是一名C++爱好者
console.log(person1 instanceof Person); //false
console.log(person1.__proto__ == Person.prototype); //false
- 寄生构造函数模式与工厂模式类似,只不过寄生构造函数模式使用new来创建一个对象。
- 与工厂模式一样,不能区分创建的对象的类别,即创建出来的对象与寄生构造函数没有什么关联。