JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。
准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype
属性上,而非对象实例本身。
注意: 理解对象的原型(可以通过Object.getPrototypeOf(obj)
或者已被弃用的__proto__
属性获得)与构造函数的prototype
属性之间的区别是很重要的。前者是每个实例上都有的属性,后者是构造函数的属性。也就是说,Object.getPrototypeOf(new Foobar())
和Foobar.prototype
指向着同一个对象。
使用Javascript中的原型
在javascript中,函数可以有属性。 每个函数都有一个特殊的属性叫作原型(prototype)
里面放置的是共有、公有的属性或者方法。注意,只有函数才有prototyoe属性
function doSomething(){}
console.log( doSomething.prototype );
var doSomething = function(){};
console.log( doSomething.prototype );
doSomething
函数有一个默认的原型属性,它在控制台上面呈现了出来
{
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
我们可以添加一些属性到 doSomething 的原型上面
function doSomething(){}
doSomething.prototype.foo = "bar";
console.log( doSomething.prototype );
结果:
{
foo: "bar",
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
然后,我们可以使用 new 运算符来在现在的这个原型基础之上,创建一个 doSomething
的实例
function doSomething(){}
doSomething.prototype.foo = "bar"; // add a property onto the prototype
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value"; // add a property onto the object
console.log( doSomeInstancing );
结果:
{
prop: "some value",
__proto__: {
foo: "bar",
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
}
doSomeInstancing
的 __proto__
属性就是doSomething.prototype
.
如果 doSomeInstancing
没有这个属性, 然后浏览器就会在 doSomeInstancing
的 __proto__
中查找这个属性(也就是 doSomething.prototype). 如果 doSomeInstancing 的 __proto__
有这个属性, 那么 doSomeInstancing 的 __proto__
上的这个属性就会被使用.否则, 如果 doSomeInstancing 的 __proto__
没有这个属性, 浏览器就会去查找 doSomeInstancing 的 __proto__
的 __proto__
,看它是否有这个属性. 默认情况下, 所有函数的原型属性的 __proto__
就是 window.Object.prototype
. 所以 doSomeInstancing 的 __proto__
的 __proto__
(也就是 doSomething.prototype 的 __proto__
(也就是 Object.prototype
)) 会被查找是否有这个属性. 如果没有在它里面找到这个属性, 然后就会在 doSomeInstancing 的 __proto__
的 __proto__
的 __proto__
里面查找. 然而这有一个问题: doSomeInstancing 的 __proto__
的 __proto__
的 __proto__
不存在. 最后, 原型链上面的所有的 __proto__
都被找完了, 浏览器所有已经声明了的 __proto__
上都不存在这个属性,然后就得出结论,这个属性是 undefined
.
原型链结构的特点:
(1)每个构造函数都有一个原型对象:通过prototype属性访问
(2)原型对象通过constructor属性指回构造函数
(3)实例对象通过__proto__属性指向原型对象
(4)Object的原型对象的__proto__是null
实例对象与原型对象的关系:
构造函数、原型对象和实例对象之间的关系:
原型链结构图
函数在原型链中的结构:
原型链的成员查找机制
1.JavaScript首先会判断实例对象有没有这个成员
2.如果没有找到,就继续查找原型对象的原型对象
3.如果直到最后都没有找到,则返回undefined。
function Person() { this.name = '张三';}
Person.prototype.name = '李四';
var p = new Person();
console.log(p.name); // 输出结果:张三
delete p.name; // 删除对象p的name属性
console.log(p.name); // 输出结果:李四
delete Person.prototype.name; // 删除原型对象的name属性
console.log(p.name); // 输出结果:undefined