一、使用Object构造函数
var person = new Object();
person.name = 'Tom';
person.age = 18;
person.say = function(){
console.log(this.name + ' ' + this.age);
}
复制代码
二、对象字面量方式
var person = {
name: 'Tom',
age: 18,
say: function(){
console.log(this.name + ' ' + this.age);
}
}
复制代码
三、工厂模式
function person(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.say = function() {
console.log(this.name + ' ' + this.age);
}
return o;
}
var person1 = person('Tom', 18);
var person2 = person('Harry', 28);
复制代码
四、构造函数模式
function Person(name, age){
this.name = name;
this.age = age;
this.say = function() {
console.log(this.name + ' ' + this.age);
}
}
var person1 = new Person('Tom', 18);
var person2 = new Person('Harry', 28);
复制代码
构造函数模式同工厂模式的不同之处:
- 没有显示的创建对象
- 直接将属性和方法赋值给this对象
- 没有明显的return语句
- 同时按照惯例,构造函数首字母大写,非构造函数首字母不大写
要创建一个构造函数的实例需要使用 new 操作符,这种方式调用构造函数其实经历了如下几个步骤:
- 创建一个新对象
- 将构造函数的作用域赋给这个新对象,同时 this 指向这个新创建的对象
- 执行构造函数,为新对象添加属性
- 返回新对象
上面的 person1 和 person2 为 Person 构造函数的两个不同实例,它们分别有一个constructor属性,指向Person。检查是否为某个构造函数的实例可以用 instanceof 操作符。
person1.constructor === Person; //true
person2.constructor === Person; //true
person1 instanceof Person; //true
person2 instanceof Person; //true
person1 instanceof Object; //true
person2 instanceof Object; //true
复制代码
构造函数本身也是函数,当它使用 new 操作符来调用时就说构造函数,如果不使用 new 操作符来调用就是普通的函数。
//作为普通函数调用,此时的方法加在了全局对象上
Person('Bob', 26);
window.say(); //Bob 26
//在另一个对象的作用域中调用,调用后 o 就有了所有的属性和方法
var o = new Object();
Person.call(o, 'Greg', '30');
o.say(); //Greg 30
复制代码
构造函数的问题 —— 在每创建一个构造函数的实例时都要重创一遍方法。
五、原型模式
我们在创建一个函数时,都会有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由由特定类型的所有实例共享的属性和方法。如下例子所示:
function Person(){}
Person.prototype.name = 'John';
Person.prototype.age = 20;
Person.prototype.say = function() {
console.log(this.name + ' ' + this.age);
};
var person1 = new Person();
person1.say();
var person2 = new Person();
person2.say();
复制代码
创建一个新函数时就会创建一个该函数的prototype属性,如上例子中,Person构造函数有一个prototype属性,该原型对象会获得一个constructor(构造函数)属性,这个属性指回构造函数,即Person.prototype.constructor指回Person。通过构造函数我们可以继续为原型对象添加属性和方法。
当调用构造函数创建实例时,该实例内部将包含一个指针([[Prototype]]),指向构造函数的原型对象。我们可以通过isPrototypeOf()方法来测试。使用Object.getPrototypeOf()方法可以返回[[Prototype]]的值。
Person.prototype.isPrototypeOf(person1); //true
Person.prototype.isPrototypeOf(person2); //true
Object.getPrototypeOf(person1) === Person.prototype; //true
Object.getPrototypeOf(person1).name; //'John'
复制代码
我们可以为实例添加与原型中同名的属性来屏蔽原型中的属性的值,同时也可以用delete 操作符删除实例属性,从而重新访问原型中的属性。 Object的hasOwnProperty()方法可以检测一个属性是存在于实例中还是原型中。而使用 in 操作符无论是在原型中还是实例中,只要能找到都返回true。 我们也可以用Object.keys()方法来查看传入对象的所有可枚举的属性。而Object.getOwnPropertyNames()方法会返回传入对象的所有属性,无论能不能枚举。
原型对象的问题跟它的共享性有关,当创建一个对象的两个实例,改变一个实例的属性,另一个会受影响。
function Person(){}
Person.prototype.name = 'John';
Person.prototype.age = 20;
Person.prototype.friends = ['Bob', 'Harry']
Person.prototype.say = function() {
console.log(this.name + ' ' + this.age);
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push('Lisa');
person2.friends; //'Bob', 'Harry', 'Lisa'
复制代码
如若此时只是想给person1的friends增加单独的值,就不能达到预期结果。
六、组合使用构造函数和原型模式
function Person(name, age){
this.name = name;
this.age = age;
this.friends = ['Bob', 'Harry']
}
Person.prototype = {
constructor: Person,
say: function() {
console.log(this.name + ' ' + this.age);
}
};
var person1 = new Person('John', 20);
var person2 = new Person('Lee', 22);
person1.friends.push('Lisa');
person2.friends; //'Bob', 'Harry'
复制代码
以上方式为实例属性在构造函数中定义,所有实例共享的属性constructor和方法在原型中定义。
七、动态原型模式
function Person(name, age){
this.name = name;
this.age = age;
this.friends = ['Bob', 'Harry'];
if(typeof this.say != 'function'){
Person.prototype.say = function() {
console.log(this.name + ' ' + this.age);
}
}
}
var g = new Person('Lucy', 18);
g.say(); //Lucy 18
复制代码