探索原型链的神秘力量

理解原型链

JavaScript在设计之初,作为一种网页脚本语言,没有设计得很复杂,这种语言只要能够完成一些简单操作就够了。Javascript里面所有的数据类型都是对象(object)。在ES6之前,js中是没有Class的概念的(ES6中的类也是语法糖,本质还是基于原型),为了实现实例对象的属性和方法共享 ,就给function设计了一个prototype的概念。每个对象都有一个原型属性,原型属性指向另一个对象,而原型属性中的对象是会被其他对象所继承的,也就是共用的,对象自身的有些属性和方法则不可以。原型对象(prototype)也有一个自己的原型对象(proto),普通属性并非为对象

prototype和__proto__

在传统的 OOP 中,首先定义"类",此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制------而是在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。

  • prototype:

    1. 函数的一个属性
    2. 是个对象
    3. 创建函数时会默认添加propertype这个属性
  • proto

    1. 对象的一个属性:
      • Object.prototype的__proto__属性是一个访问器属性(一个getter函数和一个setter函数)
    2. 指向构造函数prototype
      • 公开访问它的对象的内部[[Prototype]](对象或null)
js 复制代码
function ptt() {
    this.a = 1
}
const obj = new ptt()

// 函数:
console.dir(ptt)
console.log(ptt.prototype)
// 既然prototype是个对象,那么就有__proto__
console.log(ptt.prototype.__proto__)
console.log(ptt.prototype.__proto__ === Object.prototype)// true

// 对象:
console.dir(obj)
// 既然是对象,那么就有__proto__
console.log(obj.__proto__)
console.log(obj.__proto__ === ptt.prototype)//true
console.log(Object.prototype.__proto__)//null
ptt.prototype.b = 2

Object.prototype.c = 3

console.log(ptt.prototype.b)// 2
console.log(obj.c)// 3

以上代码生成的原型链结构如下:

js 复制代码
obj {
  a:1
  __proto__:ptt.prototype = {
    b:2,
    __proto__:Object.prototype = f{
      c:3
      __proto__:null
    }
  }
}

在Chrome浏览器的开发者工具(F12)打印console.dir(obj)后如图:

实际上,Chrome 控制台打印的 [[Prototype]] 即表示对象的原型,并不需要一层层的去寻找__proto__:

难点在于......构造函数也是对象!

如果没有 prototype,那么共用属性就没有立足之地,

prototype 指向一块内存,这个内存里面有共用属性;

proto 指向同一块内存;

如果没有__proto__,那么一个对象就不知道自己的共用属性有哪些。上面所说__proto__属性是一个访问器属性,通俗来说,prototype就是用__proto__来查找有哪些对象是可继承,且继承了的。

  1. prototype存储共用的属性和方法,
  2. __proto__用来将对象与该对象的原型相连,
  3. constuctor是用来将原型对象指向关联的构造函数。

原型链

通过原型链从而找到toString()方法,而其他对象同样具有此方法,所以并不是对象会把这些方法和属性复制过来,只是通过原型(prototype)找到被继承的对象的原型。

当我们创建一个对象后,就可以通过"点"方法名的方式调用一些并不是我们写的方法了,例如:

js 复制代码
let num=123
num.toString()
console.log(num.toString === Number.prototype.toString); // true
console.log(Object.getPrototypeOf(num) === Number.prototype); // 输出: true

其实我们调用的是Number.prototype.toString。现在是不是对JavaScript是基于原型的语言这句 话有些理解了。

其实不止Number是这样的,Array、String、Object等都是这样的原理。

"JavaScript-一切皆对象"

构造函数的prototype属性与通过该构造函数创建的对象实例的原型是同一个对象,因此Object.getPrototypeOf(new Foobar()) === Foobar.prototype

原型的实践

create()

可以使用Object.create()的方法,创建原型对象,例如:

js 复制代码
let person = {
  name: 'Rarrot'
};
var person1 = Object.create(person);

console.log(person1.__proto__);

创建了一个新的对象 person1,它的原型被设置为之前定义的 person 对象。通过 Object.create(person),person1 继承了 person 的所有属性和方法。

注意,虽然使用 proto 属性来直接访问一个对象的原型在很多情况下都能工作,但这并不是推荐的做法,因为它并不是所有JavaScript环境都支持的标准特性。更标准的方式是使用 Object.getPrototypeOf() 方法来获取一个对象的原型。例如:

js 复制代码
console.log(Object.getPrototypeOf(person1));// { name: 'Rarrot' }

constructor属性

每个实例对象都从原型中继承了一个constructor属性,该属性指向了用于该构造此实例对象的构造函数,例如:

js 复制代码
//如果构造此对象的是person函数,返回person(),没有的话就返回Object()
console.log(person1.constructor); // ƒ Object() { [native code] }

如果想要创建一个新的实例,但是拿不到实例的引用,可以使用这个属性来找到构造函数,并使用 new 关键字来创建一个新的实例:

js 复制代码
var person3 = new person1.constructor('Rarrot2');

修改原型

通过在构造函数的 prototype 上定义方法,可以实现一个强大的继承机制,其中通过这个构造函数创建的所有对象实例都可以访问这些方法。例如,假设有一个构造函数 Person,可以为它的原型添加一个 joke 方法:

js 复制代码
function Person(firstName) {
    this.name = { first: firstName };
}

Person.prototype.joke = function () {
    alert(this.name.first + ' ahhh!');
};

var person = new Person('Rarrot');
person.joke(); // 弹出信息 "Rarrot ahhh!"

在这个例子中,任何由 Person 构造函数创建的实例(如 person3)都可以调用 joke 方法。

证明了先前的原型链模型这种继承模型下,上游对象的方法不会复制到下游的对象实例中;下游对象本身虽然没有定义这些方法,但浏览器会通过上溯原型链、从上游对象中找到它们。这种继承模型提供了一个强大而可扩展的功能系统。

一种常见的定义模型

在构造器(函数体)中定义属性、在 prototype 属性上定义方法。如此,构造器只包含属性定义,而方法则分装在不同的代码块,代码更具可读性。

js 复制代码
// 构造器及其属性定义
function Test(a,b,c,d) {
  // 属性定义
};
// 定义第一个方法
Test.prototype.x = function () { ... }

// 定义第二个方法
Test.prototype.y = function () { ... }

典型的例子:school plan app

力扣

可以去力扣上找设计题目类型的题来理解原型链,例如:

384.打乱数组

155.最小栈

参考链接:

理解JS的prototype

小满zs 原型链

相关推荐
腾讯TNTWeb前端团队2 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰6 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪6 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪6 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy7 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom7 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom7 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom7 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom8 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom8 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试