创建对象
虽然Object构造函数与对象字面量都能创建单个对象, 但这些方式都有明显的缺点: 使用同一个接口创建很多对象, 会产生大量重复代码。
var obj = {}; //对象字面量
var obj = new Object(); //对象构造函数(对象构造器)
工厂模式
这种模式抽象了对象具体创建的过程(类似其它语言的类)
function createPerson (name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
alert (this.name);
}
return o;
}
var person1 = createPerson("汪淼", 50, "纳米");
var person2 = createPerson("杨冬", 20, "基础物理学");
工厂模式解决了创建多个对象的问题, 确没有解决对象识别问题(如何知道一个对象的类型)因为使用该模式并没有给出对象的类型
构造函数模式
创建自定义构造函数意味着将来可以将它的实列类型标识为一种特定的类型。(更优点)
这种方式定义的函数是定义在global中的
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
alert (this.name);
}
}
var person1 = new Person("汪淼", 50, "纳米");
var person2 = new Person("杨冬", 20, "基础物理学");
与工厂模式区别
- 没有显示的创建对象
- 直接将属性和方法赋给 this 对象
- 没有return语句
- 使用new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行[[原型]]连接。
- 这个新对象会绑定到函数调用的this 。
- 如果函数没有返回其他对象,那么new 表达式中的函数调用会自动返回这个新对象。
高级编程对象处的说法
1.创建(或者说构造)一个全新的对象。
2.将构造函数的作用域赋值给新对象(因此this指向了新对象)
3.执行构造函数的代码(为这个新对象添加属性)
4.返回对象
person1.constructor == Person;
person2.constructor == Person;
constructor 最初是用来标识对象类型的。但提到检测对象类型,还是使用 instanceof
person1 instanceof Person // true
person2 instanceof Person // true
将构造函数当作函数
构造函数与其他函数唯一区别。调用方式的不同。 用new 操作符调用就是作为构造函数,不用则为普通函数
// 构造函数
var person = new Person('罗辑', 20, '宇宙社会学');
person.sayName(); //罗辑
// 普通函数
Person('叶文洁', 20, '基础物理学');
window.sayName(); //叶文洁
// 在另一个对象作用域中调用
var o = new Object();
Person.call(o, '泰勒', 25, '面壁者'); //call() apply() 是会立即调用函数的 而bind() 则不会
o.sayName(); // 泰勒
构造函数问题
使用构造函数的主要问题是每个方法都要在每个实例上重新创建一遍,创建多个完成相同任务的方法完全没有必要,浪费内存空间
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function("alert(this.name)");
// 等价于
// this.sayName = function () {
// alert (this.name);
// }
}
差的尝试
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
alert (this.name);
}
var person1 = new Person("汪淼", 50, "纳米");
var person2 = new Person("杨冬", 20, "基础物理");
sayName 放到了构造函数外部, 构造函数内部通过sayName指针指向sayName函数;
问题: 创建了全局函数sayName
于是这个自定义引用类型就没有丝毫封装性可言
原型模式
原型模式
我们创建的每个函数都有一个 prototype(原型属性),该属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型所有实例共享的属性和方法。
用原型对象,可以让所有实例共享它的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中
function Person() {
}
Person.prototype.name = '维德';
Person.prototype.age = '24';
Person.prototype.sayName = function() {
alert(this.name);
}
var person1 = new Person();
person1.sayName(); // 维德
var person2 = new Person();
person2.sayName(); // 维德
alert(person1.sayName == person2.sayName); //true
// @todo 原型链图
更简单的原型模式
function Person(){};
Person.prototype = {
name: "丁怡",
age: 50,
job: '物流',
sayName : function(){
console.log(this.name);
}
};
var person1 = new Person();
person1.sayName();// '丁怡'
console.log(person1.constructor === Person);// false
console.log(person1.constructor === Object);// true
这种语法 constructor 不再指向Person,(每创建一个函数,就会同时创建prototype, 同时这个对象也会自动获取constructor),而该方法本质上相当于完全重写了prototype的默认属性,constructor 也变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。此时尽管instanceof操作符还能返回正确的结果,但通过constructor已经无法确定对象的类型了
var friend = new Person();
friend instanceof Object; // true
friend instanceof Person; // true
friend constructor Person; // false
friend constructor Object; // true
修正 constructor 方法
function Person(){};
Person.prototype = {
// 方法1:添加 constructor
constructor: 'Person',
name: "丁怡",
age: 50,
job: '物流',
sayName : function(){
console.log(this.name);
}
};
// 方法2:通过defineProperty() 添加
Object.defineProperty(Person.prototype,'constructor',{
enumerable: false,
value: Person
});
原型
1.理解原型对象
无论什么时候,只要创建了一个新函数,就会根据一组特定的的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有的原型对象都会自动获取constructor(构造函数)属性,这个属性包含了一个指向prototype属性所在函数数的指针
2.原型与 in 操作符
in 会查找 原型链 hasOwnProperty() 则只查找实例本身
3.更简单的原型模式
function Person(){};
Person.prototype = {
name: "丁怡",
age: 50,
job: '物流',
sayName : function(){
console.log(this.name);
}
};
var person1 = new Person();
person1.sayName();// '丁怡'
console.log(person1.constructor === Person);// false
console.log(person1.constructor === Object);// true
4.原型的动态性 && 重写原型
var friend = new Person();
Person.prototype.sayHi = function() {
alert('hi');
}
friend.sayHi(); // hi (ok!)
重写function原型之后
function Person() {}
var friend = new Person();
Person.prototype = {
constructor: Person,
name: '程心',
age: 21,
job: '天体物理',
sayName: fucntion() {
alert(this.name);
}
};
friend.sayName(); //error 原型链重写 @todo 原型链图
5.原生对象的原型
js 所有原生的引用类型,都是采用这种模式(Object, Array, String)
原型对象的问题
原型模式问题在于引用类型值属性会被所有的实例对象共享并修改,这也是很少有人单独使用原型模式的原因
function Person(){}
Person.prototype = {
constructor: Person,
name: "bai",
age: 29,
job: "Software Engineer",
friend : ["shelby","Court"],
sayName: function(){
console.log(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends);//["shelby","Court","Van"];
alert(person2.friends);//["shelby","Court","Van"];
alert(person1.friends === person2.friends);//true
组合使用构造函数模式和原型模式
组合使用构造函数模式和原型模式是创建自定义类型的最常见方式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,这种组合模式还支持向构造函数传递参数。实例对象都有自己的一份实例属性的副本,同时又共享对方法的引用,最大限度地节省了内存。该模式是目前使用最广泛、认同度最高的一种创建自定义对象的模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ['丁怡', '汪淼'];
}
Person.prototype = {
constructor: Person,
sayName: function() {
alert(this.name);
}
}
var person1 = new Person("bai",29,"Software Engineer");
var person2 = new Person("hu",25,"Software Engineer");
person1.friends.push('杨冬');
alert(person1.friends);// ['丁怡', '汪淼', '杨冬'];
alert(person2.friends);// ['丁怡', '汪淼'];
alert(person1.friends === person2.friends);//false
alert(person1.sayName === person2.sayName);//true
动态原型模式
动态原型模式将组合模式中分开使用的构造函数和原型对象都封装到了构造函数中,然后通过检查方法是否被创建,来决定是否初始化原型对象
function Person(name, age, job) {
// 属性
this.name = name;
this.age = age;
this.job = job;
// 方法
if(typeof this.sayName != 'function') {
Person.prototype.sayName = function(){
console.log(this.name);
};
}
}
var friend = new Person("bai",29,"Software Engineer");
friend.sayName();//'bai'
寄生构造函数模式
该模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。该模式是工厂模式和构造函数模式的结合
寄生构造函数模式与构造函数模式有相同的问题,每个方法都要在每个实例上重新创建一遍,创建多个完成相同任务的方法完全没有必要,浪费内存空间
function Person(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
};
// o.sayName = new Function('console.log(this.name);');
return o;
}
var person1 = new Person("bai", 29, "software Engineer");
var person2 = new Person("hu", 25, "software Engineer");
//具有相同作用的sayName()方法在person1和person2这两个实例中却占用了不同的内存空间
console.log(person1.sayName === person2.sayName);//false
使用new 操作符会默认返回新对象实例,这里return 重写了调用构造函数的时返回的值
稳妥构造函数模式
所谓稳妥对象指没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全环境中(这些环境会禁止使用this和new)或者在防止数据被其他应用程序改动时使用
稳妥构造函数与寄生构造函数模式相似,但有两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数
function Person(name,age,job){
//创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function(){
console.log(name);
};
//返回对象
return o;
}
//在稳妥模式创建的对象中,除了使用sayName()方法之外,没有其他方法访问name的值
var friend = Person("bai",29,"Software Engineer");
friend.sayName();//"bai"
与寄生构造函数模式相似,使用稳妥构造函数模式创建的对象与构造函数之间也没有什么关系,因此instanceof操作符对这种对象也没有什么意义
最后
本文从使用Object构造函数与对象字面量创建一个对象开始说起,创建多个对象会造成代码冗余;使用工厂模式可以解决该问题,但存在对象识别的问题;接着介绍了构造函数模式,该模式解决了对象识别的问题,但存在关于方法的重复创建问题;接着介绍了原型模式,该模式的特点就在于共享,但引出了引用类型值属性会被所有的实例对象共享并修改的问题;最后,提出了构造函数和原型组合模式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,这种组合模式还支持向构造函数传递参数,该模式是目前使用最广泛的一种模式。此外,一些模式下面还有一些解决特殊需求的拓展模式(寄生构造函数模式, 稳妥寄生模式)
- Object构造函数与对象字面量创建一个对象 创建多个对象会造成代码冗余
- 使用工厂模式可以解决该问题 存在对象识别的问题
- 介绍了构造函数模式,该模式解决了对象识别的问题 但存在关于方法的重复创建问题
- 介绍了原型模式,该模式的特点就在于共享 但引出了引用类型值属性会被所有的实例对象共享并修改的问题
- 提出了构造函数和原型组合模式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,这种组合模式还支持向构造函数传递参数,该模式是目前使用最广泛的一种模式
- 一些模式下面还有一些解决特殊需求的拓展模式(寄生构造函数模式, 稳妥寄生模式)
参考
- javascript面向对象系列第二篇——创建对象的5种模式
- javascript高级编程
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。