CD's blog CD's blog
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

CD_wOw

内卷的行情,到不了的梦
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 简介

  • JavaScript 基础知识

  • 数据类型

  • 函数进阶内容

  • 对象属性配置

  • 原型,继承

    • 原型,继承
    • 原生的原型
    • F.prototype
      • 默认的 F.prototype,构造器属性
      • 总结
      • 实例
        • 修改 "prototype"
        • 使用相同的构造函数创建一个对象
    • 原型方法,没有 __proto__ 的对象
  • 类

  • 错误处理

  • Promise,asyncawait

  • Generator,高级 iteration

  • 模块

  • 杂项

  • 《现代JavaScript》教程
  • 原型,继承
CD_wOw
2020-12-13
目录

F.prototype

我们还记得,可以使用诸如 new F() 这样的构造函数来创建一个新对象。

如果 F.prototype 是一个对象,那么 new 操作符会使用它为新对象设置 [[Prototype]]。

请注意:

JavaScript 从一开始就有了原型继承。这是 JavaScript 编程语言的核心特性之一。

但是在过去,没有直接对其进行访问的方式。唯一可靠的方法是本章中会介绍的构造函数的 "prototype" 属性。目前仍有许多脚本仍在使用它。

请注意,这里的 F.prototype 指的是 F 的一个名为 "prototype" 的常规属性。这听起来与“原型”这个术语很类似,但这里我们实际上指的是具有该名字的常规属性。

下面是一个例子:

let animal = {
  eats: true
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = animal;

let rabbit = new Rabbit("White Rabbit"); //  rabbit.__proto__ == animal

alert( rabbit.eats ); // true
1
2
3
4
5
6
7
8
9
10
11
12
13

设置 Rabbit.prototype = animal 的字面意思是:“当创建了一个 new Rabbit 时,把它的 [[Prototype]] 赋值为 animal”。

这是结果示意图:

在上图中,"prototype" 是一个水平箭头,表示一个常规属性,[[Prototype]] 是垂直的,表示 rabbit 继承自 animal。

F.prototype 仅用在 new F 时

F.prototype 属性仅在 new F 被调用时使用,它为新对象的 [[Prototype]] 赋值。

如果在创建之后,F.prototype 属性有了变化(F.prototype = <another object>),那么通过 new F 创建的新对象也将随之拥有新的对象作为 [[Prototype]],但已经存在的对象将保持旧有的值。

# 默认的 F.prototype,构造器属性

每个函数都有 "prototype" 属性,即使我们没有提供它。

默认的 "prototype" 是一个只有属性 constructor 的对象,属性 constructor 指向函数自身。

像这样:

function Rabbit() {}

/* default prototype
Rabbit.prototype = { constructor: Rabbit };
*/
1
2
3
4
5

我们可以检查一下:

function Rabbit() {}
// by default:
// Rabbit.prototype = { constructor: Rabbit }

alert( Rabbit.prototype.constructor == Rabbit ); // true
1
2
3
4
5

通常,如果我们什么都不做,constructor 属性可以通过 [[Prototype]] 给所有 rabbits 使用:

function Rabbit() {}
// by default:
// Rabbit.prototype = { constructor: Rabbit }

let rabbit = new Rabbit(); // inherits from {constructor: Rabbit}

alert(rabbit.constructor == Rabbit); // true (from prototype)
1
2
3
4
5
6
7

我们可以使用 constructor 属性来创建一个新对象,该对象使用与现有对象相同的构造器。

像这样:

function Rabbit(name) {
  this.name = name;
  alert(name);
}

let rabbit = new Rabbit("White Rabbit");

let rabbit2 = new rabbit.constructor("Black Rabbit");
1
2
3
4
5
6
7
8

当我们有一个对象,但不知道它使用了哪个构造器(例如它来自第三方库),并且我们需要创建另一个类似的对象时,用这种方法就很方便。

但是,关于 "constructor" 最重要的是……

……JavaScript 自身并不能确保正确的 "constructor" 函数值。

是的,它存在于函数的默认 "prototype" 中,但仅此而已。之后会发生什么 —— 完全取决于我们。

特别是,如果我们将整个默认 prototype 替换掉,那么其中就不会有 "constructor" 了。

例如:

function Rabbit() {}
Rabbit.prototype = {
  jumps: true
};

let rabbit = new Rabbit();
alert(rabbit.constructor === Rabbit); // false
1
2
3
4
5
6
7

因此,为了确保正确的 "constructor",我们可以选择添加/删除属性到默认 "prototype",而不是将其整个覆盖:

function Rabbit() {}

// 不要将 Rabbit.prototype 整个覆盖
// 可以向其中添加内容
Rabbit.prototype.jumps = true
// 默认的 Rabbit.prototype.constructor 被保留了下来
1
2
3
4
5
6

或者,也可以手动重新创建 constructor 属性:

Rabbit.prototype = {
  jumps: true,
  constructor: Rabbit
};

// 这样的 constructor 也是正确的,因为我们手动添加了它
1
2
3
4
5
6

# 总结

简要介绍了为通过构造函数创建的对象设置 [[Prototype]] 的方法。稍后我们将看到更多依赖于此的高级编程模式。

一切都很简单,只需要记住几条重点就可以清晰地掌握了:

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

在常规对象上,prototype 没什么特别的:

let user = {
  name: "John",
  prototype: "Bla-bla" // 这里只是普通的属性
};
1
2
3
4

默认情况下,所有函数都有 F.prototype = {constructor:F},所以我们可以通过访问它的 "constructor" 属性来获取一个对象的构造器。

# 实例

# 修改 "prototype"

在下面的代码中,我们创建了 new Rabbit,然后尝试修改它的 prototype。

最初,我们有以下代码:

function Rabbit() {}
Rabbit.prototype = {
  eats: true
};

let rabbit = new Rabbit();

alert( rabbit.eats ); // true
1
2
3
4
5
6
7
8
  1. 我们增加了一个字符串(强调)。现在 alert 会显示什么?

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    Rabbit.prototype = {};
    
    alert( rabbit.eats ); // ?
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  2. ……如果代码是这样的(修改了一行)?

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    Rabbit.prototype.eats = false;
    
    alert( rabbit.eats ); // ?
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  3. 像这样呢(修改了一行)?

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    delete rabbit.eats;
    
    alert( rabbit.eats ); // ?
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  4. 最后一种变体:

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    delete Rabbit.prototype.eats;
    
    alert( rabbit.eats ); // ?
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

# 解决方案

答案:

  1. true。

    Rabbit.prototype 的赋值操作为新对象设置了 [[Prototype]],但它不影响已有的对象。

  2. false。

    对象通过引用被赋值。来自 Rabbit.prototype 的对象并没有被复制,它仍然是被 Rabbit.prototype 和 rabbit 的 [[Prototype]] 引用的单个对象。

    所以当我们通过一个引用更改其内容时,它对其他引用也是可见的。

  3. true。

    所有 delete 操作都直接应用于对象。这里的 delete rabbit.eats 试图从 rabbit 中删除 eats 属性,但 rabbit 对象并没有 eats 属性。所以这个操作不会有任何影响。

  4. undefined。

    属性 eats 被从 prototype 中删除,prototype 中就没有这个属性了。

# 使用相同的构造函数创建一个对象

想象一下,我们有一个由构造函数创建的对象 obj —— 我们不知道使用的是哪个构造函数,但是我们想使用它创建一个新对象。

我们可以这样做吗?

let obj2 = new obj.constructor();
1

请给出一个可以使这样的代码正常工作的 obj 的构造函数的例子。再给出会导致这样的代码无法正确工作的例子。

# 解决方案

如果我们确信 "constructor" 属性具有正确的值,那么就可以使用这种方法。

例如,如果我们不触碰默认的 "prototype",那么这段代码肯定可以正常运行:

function User(name) {
  this.name = name;
}

let user = new User('John');
let user2 = new user.constructor('Pete');

alert( user2.name ); // Pete (worked!)
1
2
3
4
5
6
7
8

它起作用了,因为 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
1
2
3
4
5
6
7
8
9

为什么 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')。内建的 Object 构造函数会忽略参数,它总是创建一个类似于 let user2 = {} 的空对象,这就是最后我们在 user2 中拥有的东西。

编辑 (opens new window)
#现代JavaScript
上次更新: 2021/08/22, 01:09:59
原生的原型
原型方法,没有 __proto__ 的对象

← 原生的原型 原型方法,没有 __proto__ 的对象→

最近更新
01
gsap动画库学习笔记 - 持续~
06-05
02
远程组件加载方案笔记
05-03
03
小程序使用笔记
03-29
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CD | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式