建立单个对象的缺点:用同一个接口建立不少对象,会产生大量的重复代码。javascript
工厂模式就是为了解决这个问题。java
解决了建立多个类似对象的问题es6
function createPerson(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function() { console.log(this.name) } return o; } var person1 = createPerson('Mike', 28, 'xxx'); console.log(person1); person1.sayName(); var person2 = createPerson('Mike', 24, 'aaa'); console.log(person2); person2.sayName();
缺点:没法解决对象识别的问题——怎样知道一个对象的类型浏览器
ECMAScript中的构造函数能够用来建立特定类型的对象。安全
function Person(name, age , job) { this.name = name; this.age = age; this.job = job; this.sayName = function() { console.log(this.name); } } var person1 = new Person('Mike', 28, 'teacher'); console.log(person1); person1.sayName(); var person2 = new Person('Danie', 24, 'doctor'); console.log(person2); person2.sayName();
与工厂模式的区别:函数
构造函数自己也是函数,只不过能够用来建立对象this
用new操做符新建构造函数的实例,经历4个步骤:spa
person1 和 person2 分别保存着 Person 的两个不一样实例,都有一个 constructor (构造函数) 属性,指向 Personprototype
console.log(person1.constructor == Person); // true console.log(person2.constructor == Person); // true
console.log(person1.constructor == Object); // false console.log(person2.constructor == Object); // false
person1 和 person2 都是 Person 的实例,也是 Object 的实例,能够经过 instanceof 操做符来检验。code
console.log(person1 instanceof Person); // true console.log(person2 instanceof Person); // true console.log(person1 instanceof Object); // true console.log(person2 instanceof Object); // true
缺点:每一个构造函数中的方法都要在新的实例上建立一遍。
console.log(person1.sayName == person2.sayName); // false
将相同的方法移到外部:
function sayName() { // 将相同的方法移到构造函数的外部 console.log(this.name); } function Person(name, age , job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName; } var person1 = new Person('Mike', 28, 'teacher'); console.log(person1); person1.sayName(); var person2 = new Person('Danie', 24, 'doctor'); console.log(person2); person2.sayName();
// 此时不一样实例上的方法就相等了 console.log(person1.sayName == person2.sayName); // true
缺点:须要在全局做用域定义不少函数,没有封装性可言
好处: 全部对象实例可共享它所包含的属性和方法
function Person() { } Person.prototype.name = 'Mike'; Person.prototype.age = 28; Person.prototype.job = 'teacher'; Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person(); var person2 = new Person(); person1.sayName(); person2.sayName(); console.log(person1.sayName == person2.sayName); // true
下图以上面代码为例,展现了Person构造函数、Person的原型属性,及两个实例之间的关系。
在实现中,没法访问 [[Prototype]],能够用 isPrototypeOf() 方法来肯定对象之间是否有这种关系。
console.log(Person.prototype.isPrototypeOf(person1)); // true console.log(Person.prototype.isPrototypeOf(person2)); // true
经过 Object.getPrototypeOf() 方法,访问原型对象上的属性
console.log(Object.getPrototypeOf(person1) == Person.prototype); // true console.log(Object.getPrototypeOf(person1).name); // Mike
修改实例属性
// 原型模式 修改实例属性 function Person() { } Person.prototype.name = 'Mike'; Person.prototype.age = 28; Person.prototype.job = 'teacher'; Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person(); person1.name = 'Gray'; person1.job = 'doctor'; var person2 = new Person(); person1.sayName(); // Gray person2.sayName(); // Mike console.log(person1); // Person {name: "Gray", job: "doctor", __proto__: Object} console.log(person2); // Person {__proto__: Object}
修改实例属性为 null, 不会恢复指向原型的连接
function Person() { } Person.prototype.name = 'Mike'; Person.prototype.age = 28; Person.prototype.job = 'teacher'; Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person(); person1.name = 'Gray'; var person2 = new Person(); person1.sayName(); // Gray person2.sayName(); // Mike person1.name = null; // 不会恢复指向原型的连接 person1.sayName(); // null
删除实例属性 会从新恢复指向原型对象的连接
// 原型模式 删除实例属性 会从新恢复指向原型对象的连接 function Person() { } Person.prototype.name = 'Mike'; Person.prototype.age = 28; Person.prototype.job = 'teacher'; Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person(); person1.name = 'Gray'; var person2 = new Person(); person1.sayName(); // Gray person2.sayName(); // Mike delete person1.name; // 会从新恢复指向原型对象的连接 person1.sayName(); // Mike
hasOwnProperty() 检测一个属性存在于实例中仍是存在于原型中。
// 原型模式 hasOwnProperty() function Person() { } Person.prototype.name = 'Mike'; Person.prototype.age = 28; Person.prototype.job = 'teacher'; Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person(); console.log(person1.hasOwnProperty('name')); // false person1.name = 'Gray'; console.log(person1.hasOwnProperty('name')); // true var person2 = new Person(); console.log(person2.hasOwnProperty('name')); // false delete person1.name; console.log(person1.hasOwnProperty('name')); // false
Object.keys() 得到对象上全部可枚举的实例属性
// Object.keys() function Person() { } Person.prototype.name = 'Mike'; Person.prototype.age = 28; Person.prototype.job = 'teacher'; Person.prototype.sayName = function() { console.log(this.name); } console.log(Object.keys(Person.prototype)); // ["name", "age", "job", "sayName"] var person1 = new Person(); console.log(Object.keys(person1)); // [] person1.name = 'Gray'; console.log(Object.keys(person1)); // ["name"]
更简单的原型语法
function Person() { } Person.prototype = { // prototype 的 constructor 属性再也不指向 Person name: 'Mike', age: 28, job: 'Teacher', sayName: function() { console.log(this.name); } } var person1 = new Person(); console.log(person1)
以上方式,Person.prototype 的 constructor 属性将再也不指向 Person。
对比下面两张图:
Person.prototype.name = 'Mike'; // 方式建立的
constructor 会指向 Person
Person.prototype = {}; // 对象字面量方式建立的
constructor 不会指向 Person
经过 instanceof 还能返回正确的结果,可是 constructor 已经不能肯定对象的类型了。
console.log(person1 instanceof Object); // true console.log(person1 instanceof Person); // true console.log(person1.constructor == Object); // true console.log(person1.constructor == Person); // false
若是 constructor 很重要,能够显示指定
// 显示指定 constructor function Person() { } Person.prototype = { // 经过指定 constructor属性,指向 Person constructor: Person, // 以这种方式重设,会使它的[[Enumerable]]特性被设置为true name: 'Mike', age: 28, job: 'Teacher', sayName: function() { console.log(this.name); } } var person1 = new Person(); console.log(person1); // 打印出的结果见下图
兼容 ECMAScript5的浏览器引擎
Object.defineProperty()
function Person() { } Person.prototype = { name: 'Mike', age: 28, job: 'Teacher', sayName: function() { console.log(this.name); } } Object.defineProperty(Person.prototype, 'constructor', { enumerable: false, value: Person });
先建立实例,再在Person的原型对象上加方法,实例也能够调用。
// 原型的动态性 function Person() { } var person1 = new Person(); Person.prototype.sayHi = function() { console.log('Hi'); } person1.sayHi(); // Hi
重写原型对象
function Person() { } var person1 = new Person(); Person.prototype = { // 经过指定 constructor属性,指向 Person constructor: Person, name: 'Mike', age: 28, job: 'Teacher', sayName: function() { console.log(this.name); } } console.log(person1); person1.sayName(); // person1.sayName is not a function
能够看到,person1的原型对象上没有Person的新原型对象上的属性,由于他们是两个不一样的对象。
下图展现了重写原型以前和重写原型以后各个对象之间的关系。
全部原生引用类型(Object, Array, String等)都是在其构造函数的原型上定义了方法。
console.log(typeof Array.prototype.sort); // "function"
在原生对象的原型上添加方法
// 注意 es6 中已经实现了该方法 String.prototype.startsWith = function(text) { return this.indexOf(text) === 0; } console.log('Hello javascript'.startsWith('Hello')); // true
不推荐在原生对象的原型上添加方法,可能会意外的重写原生方法。
缺点:
- 省略了为构造函数传递初始化参数
- 全部属性被实例共享——主要针对引用类型的属性
看下面代码
// 原型对象缺点 function Person() { } Person.prototype = { constructor: Person, name: 'Mike', age: 28, job: 'Teacher', friends: ['Emily', 'David'], sayName: function() { console.log(this.name); } } var person1 = new Person(); var person2 = new Person(); person1.friends.push('Jennifer'); console.log(person1.friends); // ["Emily", "David", "Jennifer"] console.log(person2.friends); // ["Emily", "David", "Jennifer"] console.log(person1.friends == person2.friends); // true
person1 的friends 和 person2 的 friends 共享了,实际应用中,每一个实例应该都有本身的 friends才对。
因此,出现了如下这种模式。
优势:
改写前面的例子:
// 组合模式 function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ['Emily', 'David']; } Person.prototype = { constructor: Person, sayName: function() { console.log(this.name); } } var person1 = new Person('Mike', 28, 'teacher'); var person2 = new Person('Gray', 24, 'doctor'); person1.friends.push('Jennifer'); console.log(person1.friends); // ["Emily", "David", "Jennifer"] console.log(person2.friends); // ["Emily", "David"] console.log(person1.friends == person2.friends); // false person1.sayName(); // Mike person2.sayName(); // Gray
如今修改任一实例的 friends 属性,都只会影响自身,不会影响到其余实例的。
组合模式中,构造函数和原型是分离的。
动态原型模式,将原型在构造函数初始化时,就在构造函数中初始化了。
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 person1 = new Person('Mike', 28, 'teacher'); person1.sayName(); // Mike
先来看一段代码:
function Person(name, age, job) { var obj = new Object(); // <-- 建立一个 obj obj.name = name; obj.age = age; obj.job = job; obj.sayName = function() { console.log(this.name); } return obj; // <-- return obj } var person1 = new Person('Mike', 28, 'teacher'); person1.sayName(); // Mike
看起来和构造函数没什么区别,就是将属性都绑定在内部新建的 obj 上,而后返回这个 obj。
可是,请继续看:
console.log(person1.constructor); // ƒ Object() { [native code] } 实例的构造函数是 Object 不是 Person console.log(person1 instanceof Person); // false <-- 实例
构造函数中返回的对象与构造函数或构造函数的原型属性之间没有关系。不能依赖 instanceof 来肯定对象的类型。
建议:在可使用其余模式时,不要用这种模式。
稳妥对象(durable objects):没有公共属性;方法不引用 this。
稳妥对象适合在一些安全环境(禁止使用 this 和 new)和防止数据被其余应用程序改动时使用。
function Person(name, age, job) { var obj = new Object(); obj.name = name; obj.age = age; obj.job = job; obj.sayName = function() { console.log(name); // <-- 方法不引用 this } return obj; } var person1 = Person('Mike', 28, 'teacher'); // 不使用 new 关键字 person1.sayName(); // Mike
其中 name 的值,只能经过 sayName 方法访问。
注:以上全部的文字、代码都是本人一个字一个字敲上去的,图片也是一张一张画出来的,转载请注明出处,谢谢!