一、prototype(原型)是什么?
在JavaScript中,我们每创建一个函数,都会有一个prototype(原型)属性:这个属性是一个指针,指向一个对象,对象的用途是包含可以由特定类型的所有实例共享的属性和方法
prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型可以让所有对象实例共享它所包含的属性和方法
function Person(){}
Person.prototype.name = "lilei";
Person.prototype.age = 26;
Person.prototype.sayHello = function(){
console.log(this.name+":Hello!");
};
var person1 = new Person();
person1.sayHello(); //"lilei:hello"
var person2 = new Person();
person2.sayHello(); //"lilei:hello"
console.log(person1.sayHello == person2.sayHello); //true
二、原型、构造函数、实例对象之间的关系:
所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。如图3-1:Person.prototype.constructor 指向 Person。
Person.prototype.constructor === Person // true
实例化的对象内部有一个看不见的__proto__指针,指向原型对象,__proto__只在浏览器内部使用,__对脚本则是完全不可见的。虽然在所有实现中都无法访问到__proto__,但可以通过 isPrototypeOf()方法来确定对象之 间是否存在这种关系:
console.log(Person.prototype.isPrototypeOf(person1)); //true
console.log(Person.prototype.isPrototypeOf(person2)); //true
所有对象的__proto__都指向其构造函数的prototype
person1.__proto__ === Person.prototype //true
三、__proto__与prototype的关系
- 所有构造函数的__proto__都指向Function.prototype,它是一个空函数(Empty function)
Person.__proto__ === Function.prototype // true
Number.__proto__ === Function.prototype // true
Boolean.__proto__ === Function.prototype // true
String.__proto__ === Function.prototype // true
Object.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototype // true
Array.__proto__ === Function.prototype // true
RegExp.__proto__ === Function.prototype // true
Error.__proto__ === Function.prototype // true
Date.__proto__ === Function.prototype // true
所以所有构造函数都继承了Function.prototype的属性及方法
- Function.prototype也是唯一一个typeof 为 “function”的prototype。其它的构造函数的prototype都是一个对象
console.log(typeof Function.prototype) // function
console.log(typeof Object.prototype) // object
console.log(typeof Number.prototype) // object
console.log(typeof Boolean.prototype) // object
console.log(typeof String.prototype) // object
console.log(typeof Array.prototype) // object
console.log(typeof RegExp.prototype) // object
console.log(typeof Error.prototype) // object
console.log(typeof Date.prototype) // object
console.log(typeof Object.prototype) // object
- 除Object之外所有构造函数的原型的__proto__都指向Object.prototype
Function.prototype.__proto__ === Object.prototype // true
Person.prototype.__proto__ === Object.prototype // true
Number.prototype.__proto__ === Object.prototype // true
Boolean.prototype.__proto__ === Object.prototype // true
String.prototype.__proto__ === Object.prototype // true
Array.prototype.__proto__ === Object.prototype // true
Function.prototype.__proto__ === Object.prototype // true
RegExp.prototype.__proto__ === Object.prototype // true
Error.prototype.__proto__ === Object.prototype // true
Date.prototype.__proto__ === Object.prototype // true
Object.prototype的__proto__?
Object.prototype.__proto__ === null // true
四、实例属性与原型属性
- 原型包含 constructor 属性,而该属性也是共享的,因此可以通过对象实例访问。
person1.constructor === Person.prototype.constructor //true
person1.constructor === Person //true
person1.name === Person.prototype.name //true
person1.age === Person.prototype.age //true
person1.sayHello === Person.prototype.sayHello //true
- 虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值:重写的属性是保存在实例上的属性
function Person(){}
Person.prototype.name = "lilei";
Person.prototype.age = 26;
Person.prototype.sayHello = function(){
console.log(this.name+":Hello!");
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
console.log(person1.name); // "Greg" ——来自实例
console.log(person2.name); // "lilei" ——来自原型
访问person1.name时会在这个实例上搜索一个名为 name的属性。这个属性如果存在,就返回,不存在 就到原型上去找。当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性;添加这 个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性
- 使用 delete 操作符则可以删除实例属性,从而让我们能够重新访问原型中的属性
function Person(){}
Person.prototype.name = "lilei";
Person.prototype.age = 26;
Person.prototype.sayHello = function(){
console.log(this.name+":Hello!");
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
console.log(person1.name); // "Greg" ——来自实例
console.log(person2.name); // "lilei" ——来自原型
delete person1.name;
console.log(person1.name); // "lilei" ——来自原型
- 通过hasOwnProperty()方法,可以判断访问属性是否为 实例属性
person1.name = "Greg";
person1.hasOwnProperty("name") //true
person2.hasOwnProperty("name") //false
当 person1 重写name 属性后返回 true,因为这时候 name 是一个实例属性,而非原型属性
- 通过in可以判断对象是否具有某个属性,包括对象实例及其原型的属性;
person1.name = "Greg";
person1.hasOwnProperty("name") //true
person2.hasOwnProperty("name") //false
console.log("name" in person1) //true
console.log("name" in person2) //true
- 通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。但是,对于引用类型来说,在未重写的情况下,修改引用类型值的属性,就是在原型属性值的基础上修改 原型属性值的属性
function Person(){}
Person.prototype = {
constructor: Person,
name : "lilei",
age : 26,
friends : ["Jim", "Tom"]
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("hanmeimei");
console.log(person1.friends); //"Jim,Tom,hanmeimei"
console.log(person2.friends); //"Jim,Tom,hanmeimei"
console.log(person1.friends === person2.friends); //true
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型 对象的内部指针。如果我们让原型对象等于另一个类型的实例,会发生什么?所以下一节《继承》
文章参考:
《JavaScript 高级程序设计》中文译本 第三版