原型 & 继承二 - 你不知道的 JS

  1. 默认就有 prototype
  2. 使用相同的构造函数创建一个对象
  3. 总结
  4. 练习

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') 的工作流程:

  1. 首先,它在 user 中寻找 constructor 。没找到。
  2. 然后它追溯原型链。user 的原型是 User.prototype ,它也什么都没有。
  3. User.prototype 的值是一个普通对象 {} ,该对象的原型是
    Object.prototype。并且 Object.prototype.constructor == Object
    所以实际上是
     let user2 = new Object('Pete');

总结

  1. F.prototype 属性(不要把它与 [[Prototype]] 弄混了)在 new F 被调用时为新对象
    [[Prototype]] 赋值。
  2. F.prototype 的值要么是一个对象,要么就是 null :其他值都不起作用
  3. “prototype” 属性仅在设置了一个构造函数,并通过 new 调用时,才具有这种特殊的影响

在非函数对象上,prototype 就是普普通通的属性。

练习

  1. 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.prototyperabbit[[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 中就没有这个属性了


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。
我的空间