原型继承那些事

2025-1-12

原型继承那些事,零散的总结

原型链

我的理解:

一个普通对象的创建 const a = {},其本质是通过 Object 构造函数创建的,即 const a = new Object(),所以 a实例 的隐式原型(即__proto__)指向 Object 的原型(即Object.prototype), obj 的隐式原型(__proto__)指向的 Object 的原型(Object.prototype),这里特殊所以 Object.prototype 的隐式原型(即__proto__)指向的是一个 null

话说回来,Object 的构造函数其实就是 new Function() 创建的,所以 Object(即Function new出来的实例) 的隐式原型(__proto__)指向的是 Function 的显示原型,因为 Function 的原型(Function.prototype)其实是一个对象也就是 new Object 创建的,所以 Function 的隐式原型(__proto__)指向 Object 的原型(即Object.prototype)

特殊最后 Function 的构造函数的隐式原型指向的是 Function 原型

注意点:

  • 误区 1:Function.prototype 是 new Object() 创建的

  • 纠正:Function.prototype 是由引擎内部直接创建的,它是一个空函数对象(虽然 typeof Function.prototype 返回 “function”),但它的隐式原型指向 Object.prototype。

  • 误区 2:Object 构造函数是 new Function() 创建的

  • 纠正:Object 构造函数是引擎原生实现的,但它的行为等价于通过 Function 构造函数创建(即 Object.proto === Function.prototype)。

上面那段话是为了解释所以才说是由xx创建的,但是底层肯定不是简单的new Object或new Function创建,但是行为很类似。

原型对象和隐式原型(__proto__)区别

prose-td:text-center prose-td:align-middle

概念本质作用示例
原型对象一个实际存在的对象存储公共属性和方法供继承Object.prototype
隐式原型(proto一个属性(指向原型对象的引用)建立对象与原型对象之间的链接(原型链)a.__proto__ 指向 Object.prototype

注意

  • prototype 属性 vs __proto__ 属性

    • prototype 是函数特有的属性,用于定义实例的原型。

    • __proto__ 是所有对象的属性,指向其原型对象。

创建a对象原型链图全貌

创建a对象以及添加user实例 原型链图全貌

解释了控制台中__proto__和[[prototype]]的区别

[[prototype]]这只是 Chrome 控制台的开发者决定采取的行为。事实上,有一段时间,Chrome 控制台甚至没有 [[Prototype]] ,而是用 __proto__ 来表示现在所用的 [[Prototype]] 。这很令人困惑,因为它们并不相同。每个对象都有一个 [[Prototype]] (即使为 null),但并非所有对象都有 __proto____proto__ 是从 Object.prototype 继承的 getter,如果你没有从那个继承,你就没有它。

基本上,这个想法是,至少就获取器而言,在控制台上下文中, [[Prototype]] 的层次结构更多的是一种分组,而不是一个层次路径。您在 [[Prototype]] 下看到的值仍然属于根对象;它们只是被隐藏在一个默认情况下隐藏它们的 [[Prototype]] 分组中。这种分组恰好也反映了实际的原型层次结构。所以看到

newPerson
    name
    [[Prototype]]
        constructor
        [[Prototype]]
            constructor
            __proto__
newPerson
  name
  constructor
  __proto__

与组。所以访问 proto (通过所有 [[Prototype]] )就像访问 newPerson.proto

https://www.reddit.com/r/learnjavascript/comments/z6z7mi/why_does_the_console_show_prototype_and_proto_in/

出现[[prototype]]在 chrome 控制台是 chrome92 版本,2021 年 4 月份左右,以便更好的跟随 firefox 开发者工具

原型语法反映了它是一个内部槽位(也在这里),仅由调试器暴露,而不是某个正常属性。使用 proto 作为别名会引起一些混淆,因为对象实际上可以有那个名称的正常属性,而且还有一个旧的已弃用的 Object.prototype.proto getter/setter,它会访问[[原型]]。

https://stackoverflow.com/questions/75718019/display-of-proto-vs-prototype-in-chrome-developer-tools

https://issues.chromium.org/issues/40565034 https://issues.chromium.org/issues/40759936 https://docs.google.com/document/d/1Xetnc9s6r0yy4LnPbqeCwsnsOtBlvJsV4OCdXMZ1wCM/edit?tab=t.0

javascript 一切皆为对象?

这句话其实不够准确,更准确的说法是:

JavaScript 中的值可以分为原始类型和对象类型

原始类型包括:boolean、number、string、null、undefined、symbol、bigint

原始类型有对应的包装对象(除了 null 和 undefined)

当你调用原始类型的方法时,JavaScript 会临时创建一个包装对象

// 临时包装对象的过程
const str = "hello";
str.toUpperCase(); // JavaScript临时创建String对象,调用方法后销毁

// 等同于
new String("hello").toUpperCase();

这就是为什么原始类型看起来”像对象”,但实际上它们不是对象。

给数组添加属性可取吗?

数组本质其实就是一个特殊的对象,所以自然也能给数组添加属性,但是这种方式好不好?

不推荐在生产环境大量使用

  • 可能导致代码难以理解
  • 影响代码可维护性
  • 可能造成性能问题

new Boolean

new Boolean(true) === true 打印结果是 false 常用的解释式一个是包装类型一个是基本类型,所以两者不相等,我们可以通过 typeof 来查看

typeof new Boolean(true); // "object"
typeof Boolean(true); // "boolean"
typeof true; // "boolean"

但是值得注意的是

const b = new Boolean(true);

if (b) {
  console.log("true");
} else {
  console.log("false");
}

// 这里会打印true,也就是说在执行的时候Boolean对象会被自动转换为原始布尔值

在打印 new Boolean(true)时在控制台中,你可以看到[[prototype]]展开出现[[prototype]]和一个[[PrimitiveValue]]

new Boolean()打印结果图

本次使用 chrome 浏览器

那[[PrimitiveValue]]这个是什么呢

ecma 官方解释:

Internal state information associated with this object. Of the standard built-in ECMAScript objects, only Boolean, Date, Number, and String objects implement [[PrimitiveValue]].

译文:与该对象关联的内部状态信息。在标准内置 ECMAScript 对象中,只有 Boolean、Date、Number 和 String 对象实现了[[PrimitiveValue]]。

参考 https://262.ecma-international.org/5.1/

说到继承不得不提到es6的class

但从es6的class继承方式来说本质还是原型继承,只是写法上通过class以及extends关键字来实现。

但从其他角度来说的话,它们也存在一些差异

  1. es5构造函数可以直接调用,但是es6的class必须使用new关键字

  2. es6的class里的方法不可枚举,而es5的构造函数里的方法是可枚举的


class Example {
  method() {}
}
console.log(Object.getOwnPropertyDescriptor(Example.prototype, 'method').enumerable); // false


function example2() {

}

example2.prototype.method = function() {}

console.log(Object.getOwnPropertyDescriptor(example2.prototype, 'method').enumerable); // true
  1. es5很难继承内置对象,而es6的class可以
  • 如果想要继承内置对象,采用es5的方法将会很复杂,而es6就很简单

class MyArray extends Array {

}

function myArray2() {

}
Object.setPrototypeOf(myArray2, Array.prototype);

console.log(Array.isArray(new MyArray())); // true
console.log(Array.isArray(new myArray2())); // true
  • 特殊行为无法继承

function MyArray() {}
MyArray.prototype = Object.create(Array.prototype);

// class MyArray extends Array {}

const myArr = new MyArray();
console.log(myArr.length); // 0
myArr[0] = 'a';
myArr[1] = 'b';
console.log(myArr.length); // 还是 0,而不是 2

如果一定要将es5继承内置对象方法,查看一下方法

function MyArray() {
  var arr = Array.prototype.constructor.apply(null, arguments);

  Object.setPrototypeOf(arr, MyArray.prototype);

  return arr;
}

MyArray.prototype = Object.create(Array.prototype);
MyArray.prototype.constructor = MyArray;

// 测试
var arr = new MyArray(1, 2, 3);
console.log(Array.isArray(arr)); // true
console.log(arr instanceof Array); // true
console.log(arr instanceof MyArray); // true
console.log(arr.length); // 3

arr.length = 0;

console.log(arr[0]); // undefined