在js中,建立对象的方式有工厂模式和构造函数模式等; 而构造函数模式最大的问题在于:构造函数中的每一个方法都须要在实例对象中从新建立一遍,不能复用,因此为了解决这一个问题,就须要使用原型模式来建立对象。
原型模式是把全部实例共享的方法和属性放在一个叫作prototype(原型)的属性中 ,在建立一个函数时都会有个prototype属性, 这个属性是一个指针,指向一个对象,是经过调用构造函数而建立的那个对象实例的原型对象。数组
// 构造函数 function Person() {}; // 原型属性prototype Person.prototype.name = '张三'; Person.prototype.sayName = function() { console.log(this.name); }; let person1 = new Person(); person1.sayName(); //张三 let person2 = new Person(); person2.sayName(); // 张三 console.log(person1.sayName == person2.sayName); //true
不管何时,只要建立了一个新函数,就会根据一组特定的规则为该函数建立一个prototype属性,这个属性指向函数的原型对象,在默认的状况下,全部的原型对象都自动得到一个constructor(构造函数)属性,这是一个指针,指向prototype属性所在的函数。建立了自定义的构造函数以后,其原型对象默认只会取得constructor属性;其余的方法则是从Object继承来的。
当调用构造函数建立一个新实例对象后,该实例的内部将包含一个指针[[Prototype]],指向构造函数的原型对象。这个链接存在于实例和构造函数的原型对象之间,而不是存在实例和构造函数之间。
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具备给定名字的属性。搜索首先从对象实例自己开始。若是在实例中找到了就返回该属性的值,没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具备给定名字的属性,若是在原型对象中找到了这个属性,就返回该属性的值。
虽然能够经过实例访问保存在原型中的值,但不能经过实例对象重写原型中的值,若是在实例中添加一个在原型中的同名属性,该属性会自动屏蔽原型中的属性,可是不会修改原型中的属性,只会阻止访问原型中的属性,经过delete操做符则能够彻底删除实例属性,使得能够从新访问原型中的属性。ide
原型与in操做符函数
hasOwnProperty()方法能够检测一个属性是否存在于实例对象中,
// 构造函数
function Person() {
this.age = 16;
};
Person.prototype.name = "张三";
let person1 = new Person();
console.log(person1.hasOwnProperty('name')); // false
console.log(person1.hasOwnProperty('age')); // truethisin操做符的使用能够分为两类,单独使用和在for-in循环使用,在单独使用时,in操做符会在经过对象可以访问给定属性时返回true,不管该属性存在于实例中仍是原型中。
// 构造函数
function Person() {}
Person.prototype.name = 'zhang';
let person1 = new Person();
console.log('name' in person1); // true
person1.age = 14;
console.log('age' in person1); // true
同时使用hasOwnProperty()方法和in操做符,能够肯定该属性时在原型上仍是在存在于对象中。
// 构造函数
function Person() {}
function hasPrototypeProperty(object, name) {
return !object.hasOwnProperty(name) && (name in object);
}
Person.prototype.name = "张三";
let person = new Person();
console.log(hasPrototypeProperty(person, 'name')); // true
console.log(hasPrototypeProperty(person, 'age')); // false
使用for-in循环时,返回的是全部可以经过对象访问的、可枚举的属性,其中即包含存在于实例中的属性,也包含与存在原型中的属性。
let o = {
name: 'san',
age: 14,
};
for(let key in o) {
console.log(key);
}
要取得对象上全部可枚举的实例属性,可使用Object.keys()方法,接收一个对象做为参数,返回一个包含全部可枚举属性的字符串数组。
若是想获得全部实例属性。不管是否可枚举,均可以使用Object.getOwnPropertyNames()方法。prototype
更简单的原型语法指针
为了减小没必要要的输入和从视觉上更好的封装原型的功能,常见的作法是用一个包含全部属性和方法的对象字面量来重写整个原型对象。
// 构造函数
function Person() {};
Person.prototype = {
sayHi: function() {
console.log(hi);
},
name: '张三',
};
经过这个方式会致使原型对象中的constructor属性不在指向Person了。若是constructor的值真的很重要,能够像下面这样特地将它设置回适当的值。
// 构造函数
function Person() {};
Person.prototype = {
constructor: Person,
sayHi: function() {
console.log(hi);
},
name: '张三',
};
可是经过这种方式会致使对象的[[Enumerable]]特性被设置为ture,默认状况下,constructor属性时不可枚举的,能够经过Object.defineProperty()解决这个问题。
// 构造函数
function Person() {};
Person.prototype = {
sayHi: function() {
console.log(hi);
},
name: '张三',
};
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
}code
原型的动态性对象
当对原型对象所作的任何修改都可以当即从实例上反应出来。
function Person() {};
var friend = new Person();
Person.prototype.sayHi = function() {
console.log('hi');
};
friend.sayHi(); // hi 继承可是若是是重写整个原型对象,那么状况就不同了。调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针,而把原型修改成另一个对象 就至关于切断了构造函数与最初原型之间的联系。 实例中的指针仅指向原型,而不是指向构造函数。
// 构造函数
function Person() {};
var friend = new Person();
Person.prototype = {
constructor: Person,
sayHi: function() {
console.log(hi);
}
};
friend.sayHi(); // Uncaught TypeError: friend.sayHi is not a function字符串建立了一个Person的实例,而后又重写了其原型对象。可是在使用sayHi()时发生了错误,这个时候实例所指向的原型对象是一个新的对象。重写原型对象切断了现有原型与以前已经存在的对象实例直接的联系。因此报错了。
原型模式的重要性不只体如今建立自定义类型方面,就连全部原生的引用类型,都采用这种模式,全部的原生引用类型(Object、Array、String)等,都在其构造函数的原型上定义了方法。能够像修改自定义对象的原型同样修改原生对象的原型。
对于包含引用类型值的属性来讲,全部实例在默认的状况下都会取得相同的属性值。
// 构造函数
function Person() {};
// 原型属性prototype
Person.prototype = {
constructor: Person,
friends: ['张三', '李四'],
}
let person1 = new Person();
let person2 = new Person();
person1.friends.push('王五');
console.log(person1.friends); // ["张三", "李四", "王五"]
console.log(person2.friends); // ["张三", "李四", "王五"]因为friends存在于Person的原型对象中,因此person1对friends的修改也会经过person2反应出来,可是实例对象通常都是要有属于本身的所有属性,正由于如此,不多有人单独使用原型模式来建立对象。