F.prototype
如果 F 是一个函数, F.prototype
是一个对象,那么 new F()
这样的操作会使用它将把新对象的 [[Prototype]]
设置为指向 F.prototype
。
虽然 prototype 是原型的意思,但是 prototype 只是常规属性。
下面是一个例子:
let animal = {
eats: true
}
function Rabbit(name) {
this.name = name;
}
Rabbit.prototype = animal;
// 背后:为新对象设置__proto__为 F.prototype
let rab = new Rabbit("bubb");
console.log(rab.eats); // true
rab.__proto__ == Rabbit.prototype; // true
设置 Rabbit.prototype = animal
表面意思:new Rabbit
时,将接收变量的 [[ Prototype ]]
赋值为 animal
结果示意图:
默认就有 prototype
** 每个函数 ** 都有 “prototype” 属性,即使你没有显式创建。
默认的 “prototype” 是一个对象,这个对象只有一个属性,即 constructor
– 构造器 ,此属性指向函数自身。
function Rabbit() {}
/* 默认的 prototype
Rabbit.prototype = { constructor: Rabbit }
*/
Rabbit.prototype.constructor == Rabbit; // true
图示如下:
如果我们直接使用 new Rabbit()
,就是将 Rabbit.prototype
赋值给接收对象的 [[Prototypr]]
function Rabbit() {}
// 缺省值
// Rabbit.prototype = { constructor: Rabbit }
// rabbit 的[[Prototypr]] 自动赋值为 Rabbit.prototype
let rabbit = new Rabbit(); // inherits from {constructor: Rabbit}
alert(rabbit.constructor == Rabbit); // true (from prototype)
图示如下:
由于 F.prototype.constructor
指向 F 自身(上图虚线),所以可以使用 constructor 属性创建新对象。
function Rabbit(name) {
this.name = name;
alert(name);
}
let rabbit = new Rabbit("White Rabbit");
let rabbit2 = new rabbit.constructor("Black Rabbit");
当我们有一个对象,但是不知道其构造器时,可以这样做来创建新对象。
NOTE,JavaScript 自身并不能确保正确的 “constructor” 函数值。因为
constructor
属性,甚至prototype
属性都是可以更改的。
不要直接覆盖 prototype 属性指向的对象,需要时可以手动为 prototype 对象添加一个 constructor 指向函数自身。
使用相同的构造函数创建一个对象
如果我们确信 “constructor” 属性具有正确的值,那么就可以使用这种方法。
例如,如果我们不触碰默认的 “prototype” ,那么这段代码肯定可以正常运行:
function User(name) {
this.name = name;
}
let user = new User('John');
let user2 = new user.constructor('Pete');
alert( user2.name ); // Pete (worked!)
起作用了,因为 User.prototype.constructor == User
如果有人,重写了 User.prototype ,并忘记创建 constructor 属性并引用 User 这个构造函数,那么上面这段代码就会运行失败。
function User(name) {
this.name = name;
}
User.prototype = {}; // (*)
let user = new User('John');
let user2 = new user.constructor('Pete');
alert( user2.name ); // undefined
alert(user2); // "Pete"
为什么 user2.name
是 undefined ?
这是 new user.constructor('Pete')
的工作流程:
- 首先,它在
user
中寻找constructor
。没找到。 - 然后它追溯原型链。user 的原型是
User.prototype
,它也什么都没有。 User.prototype
的值是一个普通对象 {} ,该对象的原型是Object.prototype
。并且Object.prototype.constructor == Object
。
所以实际上是let user2 = new Object('Pete');
总结
F.prototype
属性(不要把它与[[Prototype]]
弄混了)在new F
被调用时为新对象
的[[Prototype]]
赋值。F.prototype
的值要么是一个对象,要么就是 null :其他值都不起作用- “prototype” 属性仅在设置了一个构造函数,并通过 new 调用时,才具有这种特殊的影响
在非函数对象上,prototype 就是普普通通的属性。
练习
function Rabbit() {} Rabbit.prototype = { eats: true }; let rabbit = new Rabbit(); Rabbit.prototype = {}; alert( rabbit.eats ); // ?
true
虽然修改了 Rabbit.prototype
的引用,不影响之前的赋值。因为 Rabbit.prototype 指向的对象还在那里,rabbit 的 [[Prototype]]
隐藏属性的值还是之前那个对象,没有被修改。
2.
function Rabbit() {}
Rabbit.prototype = {
eats: true
};
let rabbit = new Rabbit();
Rabbit.prototype.eats = false;
alert( rabbit.eats ); // ?
false
Rabbit.prototype
和 rabbit
的 [[Prototype]]
引用的是同一个对象。所以当我们通过一个引用更改其内容时,它对其他引用也是可见的。
3.
function Rabbit() {}
Rabbit.prototype = {
eats: true
};
let rabbit = new Rabbit();
delete rabbit.eats;
alert( rabbit.eats ); // ?
true
所有 delete 操作都直接应用于对象自身拥有的属性,不会顺着原形链删除。
这里的 delete rabbit.eats 试图从 rabbit 中删除 eats 属性,但 rabbit 对象并没有 eats 属性。所以这个操作不会有任何影响
4.
function Rabbit() {}
Rabbit.prototype = {
eats: true
};
let rabbit = new Rabbit();
delete Rabbit.prototype.eats;
alert( rabbit.eats ); // ?
undefined
属性 eats 被从 prototype 中删除,prototype 中就没有这个属性了
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。