创建对象学习 —— 《高级教程》
创建单个对象的缺点:用同一个接口创建很多对象,会产生大量的重复代码。
工厂模式就是为了解决这个问题。
工厂模式
解决了创建多个相似对象的问题
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对象
- 没有return语句
构造函数本身也是函数,只不过可以用来创建对象
用new操作符新建构造函数的实例,经历4个步骤:
person1 和 person2 分别保存着 Person 的两个不同实例,都有一个 constructor (构造函数) 属性,指向 Person
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 操作符来检验。
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的新原型对象上的属性,因为他们是两个不同的对象。
下图展示了重写原型之前和重写原型之后各个对象之间的关系。
未完待续…
注:以上所有的文字、代码都是本人一个字一个字敲上去的,图片也是一张一张画出来的,转载请注明出处,谢谢!